1*eabc0478Schristos /* $NetBSD: refclock_fg.c,v 1.6 2024/08/18 20:47:18 christos Exp $ */ 2abb0f93cSkardel 3abb0f93cSkardel /* 4abb0f93cSkardel * refclock_fg - clock driver for the Forum Graphic GPS datating station 5abb0f93cSkardel */ 6abb0f93cSkardel 7abb0f93cSkardel #ifdef HAVE_CONFIG_H 8abb0f93cSkardel # include <config.h> 9abb0f93cSkardel #endif 10abb0f93cSkardel 11abb0f93cSkardel #if defined(REFCLOCK) && defined(CLOCK_FG) 12abb0f93cSkardel 13abb0f93cSkardel #include "ntpd.h" 14abb0f93cSkardel #include "ntp_io.h" 15abb0f93cSkardel #include "ntp_refclock.h" 16abb0f93cSkardel #include "ntp_calendar.h" 17abb0f93cSkardel #include "ntp_stdlib.h" 18abb0f93cSkardel 19abb0f93cSkardel /* 20abb0f93cSkardel * This driver supports the Forum Graphic GPS dating station. 21abb0f93cSkardel * More information about FG GPS is available on http://www.forumgraphic.com 22abb0f93cSkardel * Contact das@amt.ru for any question about this driver. 23abb0f93cSkardel */ 24abb0f93cSkardel 25abb0f93cSkardel /* 26abb0f93cSkardel * Interface definitions 27abb0f93cSkardel */ 28abb0f93cSkardel #define DEVICE "/dev/fgclock%d" 29abb0f93cSkardel #define PRECISION (-10) /* precision assumed (about 1 ms) */ 30abb0f93cSkardel #define REFID "GPS" 31abb0f93cSkardel #define DESCRIPTION "Forum Graphic GPS dating station" 32abb0f93cSkardel #define LENFG 26 /* timecode length */ 33abb0f93cSkardel #define SPEED232 B9600 /* uart speed (9600 baud) */ 34abb0f93cSkardel 35abb0f93cSkardel /* 36abb0f93cSkardel * Function prototypes 37abb0f93cSkardel */ 38abb0f93cSkardel static int fg_init (int); 39abb0f93cSkardel static int fg_start (int, struct peer *); 40abb0f93cSkardel static void fg_shutdown (int, struct peer *); 41abb0f93cSkardel static void fg_poll (int, struct peer *); 42abb0f93cSkardel static void fg_receive (struct recvbuf *); 43abb0f93cSkardel 44abb0f93cSkardel /* 45abb0f93cSkardel * Forum Graphic unit control structure 46abb0f93cSkardel */ 47abb0f93cSkardel 48abb0f93cSkardel struct fgunit { 49abb0f93cSkardel int pollnum; /* Use peer.poll instead? */ 50abb0f93cSkardel int status; /* Hug to check status information on GPS */ 51abb0f93cSkardel int y2kwarn; /* Y2K bug */ 52abb0f93cSkardel }; 53abb0f93cSkardel 54abb0f93cSkardel /* 55abb0f93cSkardel * Queries definition 56abb0f93cSkardel */ 57abb0f93cSkardel static char fginit[] = { 0x10, 0x48, 0x10, 0x0D, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 58abb0f93cSkardel 0, 0, 0, 0, 0, 0, 0, 0, 0 }; 59abb0f93cSkardel static char fgdate[] = { 0x10, 0x44, 0x10, 0x0D, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 60abb0f93cSkardel 0, 0, 0, 0, 0, 0, 0, 0, 0 }; 61abb0f93cSkardel 62abb0f93cSkardel /* 63abb0f93cSkardel * Transfer vector 64abb0f93cSkardel */ 65abb0f93cSkardel struct refclock refclock_fg = { 66abb0f93cSkardel fg_start, /* start up driver */ 67abb0f93cSkardel fg_shutdown, /* shut down driver */ 68abb0f93cSkardel fg_poll, /* transmit poll message */ 69abb0f93cSkardel noentry, /* not used */ 70abb0f93cSkardel noentry, /* initialize driver (not used) */ 71abb0f93cSkardel noentry, /* not used */ 72abb0f93cSkardel NOFLAGS /* not used */ 73abb0f93cSkardel }; 74abb0f93cSkardel 75abb0f93cSkardel /* 76abb0f93cSkardel * fg_init - Initialization of FG GPS. 77abb0f93cSkardel */ 78abb0f93cSkardel 79abb0f93cSkardel static int 80abb0f93cSkardel fg_init( 81abb0f93cSkardel int fd 82abb0f93cSkardel ) 83abb0f93cSkardel { 84abb0f93cSkardel if (write(fd, fginit, LENFG) != LENFG) 85abb0f93cSkardel return 0; 86abb0f93cSkardel 878585484eSchristos return 1; 88abb0f93cSkardel } 89abb0f93cSkardel 90abb0f93cSkardel /* 91abb0f93cSkardel * fg_start - open the device and initialize data for processing 92abb0f93cSkardel */ 93abb0f93cSkardel static int 94abb0f93cSkardel fg_start( 95abb0f93cSkardel int unit, 96abb0f93cSkardel struct peer *peer 97abb0f93cSkardel ) 98abb0f93cSkardel { 99abb0f93cSkardel struct refclockproc *pp; 100abb0f93cSkardel struct fgunit *up; 101abb0f93cSkardel int fd; 102abb0f93cSkardel char device[20]; 103abb0f93cSkardel 104abb0f93cSkardel 105abb0f93cSkardel /* 106abb0f93cSkardel * Open device file for reading. 107abb0f93cSkardel */ 1088585484eSchristos snprintf(device, sizeof(device), DEVICE, unit); 109abb0f93cSkardel 1108585484eSchristos DPRINTF(1, ("starting FG with device %s\n",device)); 1118585484eSchristos 112*eabc0478Schristos fd = refclock_open(&peer->srcadr, device, SPEED232, LDISC_CLK); 1138585484eSchristos if (fd <= 0) 114abb0f93cSkardel return (0); 115abb0f93cSkardel 116abb0f93cSkardel /* 117abb0f93cSkardel * Allocate and initialize unit structure 118abb0f93cSkardel */ 119abb0f93cSkardel 1208585484eSchristos up = emalloc(sizeof(struct fgunit)); 1218585484eSchristos memset(up, 0, sizeof(struct fgunit)); 122abb0f93cSkardel pp = peer->procptr; 1238585484eSchristos pp->unitptr = up; 124abb0f93cSkardel pp->io.clock_recv = fg_receive; 1258585484eSchristos pp->io.srcclock = peer; 126abb0f93cSkardel pp->io.datalen = 0; 127abb0f93cSkardel pp->io.fd = fd; 128abb0f93cSkardel if (!io_addclock(&pp->io)) { 1298585484eSchristos close(fd); 1308585484eSchristos pp->io.fd = -1; 1318585484eSchristos return 0; 132abb0f93cSkardel } 133abb0f93cSkardel 134abb0f93cSkardel 135abb0f93cSkardel /* 136abb0f93cSkardel * Initialize miscellaneous variables 137abb0f93cSkardel */ 138abb0f93cSkardel peer->precision = PRECISION; 139abb0f93cSkardel pp->clockdesc = DESCRIPTION; 1408585484eSchristos memcpy(&pp->refid, REFID, 3); 141abb0f93cSkardel up->pollnum = 0; 142abb0f93cSkardel 143abb0f93cSkardel /* 144abb0f93cSkardel * Setup dating station to use GPS receiver. 145abb0f93cSkardel * GPS receiver should work before this operation. 146abb0f93cSkardel */ 147abb0f93cSkardel if(!fg_init(pp->io.fd)) 148abb0f93cSkardel refclock_report(peer, CEVNT_FAULT); 149abb0f93cSkardel 150abb0f93cSkardel return (1); 151abb0f93cSkardel } 152abb0f93cSkardel 153abb0f93cSkardel 154abb0f93cSkardel /* 155abb0f93cSkardel * fg_shutdown - shut down the clock 156abb0f93cSkardel */ 157abb0f93cSkardel static void 158abb0f93cSkardel fg_shutdown( 159abb0f93cSkardel int unit, 160abb0f93cSkardel struct peer *peer 161abb0f93cSkardel ) 162abb0f93cSkardel { 163abb0f93cSkardel struct refclockproc *pp; 164abb0f93cSkardel struct fgunit *up; 165abb0f93cSkardel 166abb0f93cSkardel pp = peer->procptr; 1678585484eSchristos up = pp->unitptr; 1688585484eSchristos if (pp->io.fd != -1) 169abb0f93cSkardel io_closeclock(&pp->io); 1708585484eSchristos if (up != NULL) 171abb0f93cSkardel free(up); 172abb0f93cSkardel } 173abb0f93cSkardel 174abb0f93cSkardel 175abb0f93cSkardel /* 176abb0f93cSkardel * fg_poll - called by the transmit procedure 177abb0f93cSkardel */ 178abb0f93cSkardel static void 179abb0f93cSkardel fg_poll( 180abb0f93cSkardel int unit, 181abb0f93cSkardel struct peer *peer 182abb0f93cSkardel ) 183abb0f93cSkardel { 184abb0f93cSkardel struct refclockproc *pp; 185abb0f93cSkardel 186abb0f93cSkardel pp = peer->procptr; 187abb0f93cSkardel 188abb0f93cSkardel /* 189abb0f93cSkardel * Time to poll the clock. The FG clock responds to a 190abb0f93cSkardel * "<DLE>D<DLE><CR>" by returning a timecode in the format specified 191abb0f93cSkardel * above. If nothing is heard from the clock for two polls, 192abb0f93cSkardel * declare a timeout and keep going. 193abb0f93cSkardel */ 194abb0f93cSkardel 195abb0f93cSkardel if (write(pp->io.fd, fgdate, LENFG) != LENFG) 196abb0f93cSkardel refclock_report(peer, CEVNT_FAULT); 197abb0f93cSkardel else 198abb0f93cSkardel pp->polls++; 199abb0f93cSkardel 200abb0f93cSkardel /* 201abb0f93cSkardel if (pp->coderecv == pp->codeproc) { 202abb0f93cSkardel refclock_report(peer, CEVNT_TIMEOUT); 203abb0f93cSkardel return; 204abb0f93cSkardel } 205abb0f93cSkardel */ 206abb0f93cSkardel 207abb0f93cSkardel record_clock_stats(&peer->srcadr, pp->a_lastcode); 208abb0f93cSkardel 209abb0f93cSkardel return; 210abb0f93cSkardel 211abb0f93cSkardel } 212abb0f93cSkardel 213abb0f93cSkardel /* 214abb0f93cSkardel * fg_receive - receive data from the serial interface 215abb0f93cSkardel */ 216abb0f93cSkardel static void 217abb0f93cSkardel fg_receive( 218abb0f93cSkardel struct recvbuf *rbufp 219abb0f93cSkardel ) 220abb0f93cSkardel { 221abb0f93cSkardel struct refclockproc *pp; 222abb0f93cSkardel struct fgunit *up; 223abb0f93cSkardel struct peer *peer; 224abb0f93cSkardel char *bpt; 225abb0f93cSkardel 226abb0f93cSkardel /* 227abb0f93cSkardel * Initialize pointers and read the timecode and timestamp 228abb0f93cSkardel * We can't use gtlin function because we need bynary data in buf */ 229abb0f93cSkardel 2308585484eSchristos peer = rbufp->recv_peer; 231abb0f93cSkardel pp = peer->procptr; 2328585484eSchristos up = pp->unitptr; 233abb0f93cSkardel 234abb0f93cSkardel /* 235abb0f93cSkardel * Below hug to implement receiving of status information 236abb0f93cSkardel */ 2378585484eSchristos if(!up->pollnum) { 238abb0f93cSkardel up->pollnum++; 239abb0f93cSkardel return; 240abb0f93cSkardel } 241abb0f93cSkardel 242abb0f93cSkardel 2438585484eSchristos if (rbufp->recv_length < (LENFG - 2)) { 244abb0f93cSkardel refclock_report(peer, CEVNT_BADREPLY); 245abb0f93cSkardel return; /* The reply is invalid discard it. */ 246abb0f93cSkardel } 247abb0f93cSkardel 248abb0f93cSkardel /* Below I trying to find a correct reply in buffer. 249*eabc0478Schristos * Sometime GPS reply located in the beginning of buffer, 250abb0f93cSkardel * sometime you can find it with some offset. 251abb0f93cSkardel */ 252abb0f93cSkardel 253*eabc0478Schristos bpt = (char *)rbufp->recv_buffer; 2548585484eSchristos while (*bpt != '\x10') 255abb0f93cSkardel bpt++; 256abb0f93cSkardel 257abb0f93cSkardel #define BP2(x) ( bpt[x] & 15 ) 258abb0f93cSkardel #define BP1(x) (( bpt[x] & 240 ) >> 4) 259abb0f93cSkardel 260abb0f93cSkardel pp->year = BP1(2) * 10 + BP2(2); 261abb0f93cSkardel 2628585484eSchristos if (pp->year == 94) { 263abb0f93cSkardel refclock_report(peer, CEVNT_BADREPLY); 264abb0f93cSkardel if (!fg_init(pp->io.fd)) 265abb0f93cSkardel refclock_report(peer, CEVNT_FAULT); 266abb0f93cSkardel return; 267abb0f93cSkardel /* GPS is just powered up. The date is invalid - 268abb0f93cSkardel discarding it. Initilize GPS one more time */ 269abb0f93cSkardel /* Sorry - this driver will broken in 2094 ;) */ 270abb0f93cSkardel } 271abb0f93cSkardel 272abb0f93cSkardel if (pp->year < 99) 273abb0f93cSkardel pp->year += 100; 274abb0f93cSkardel 275abb0f93cSkardel pp->year += 1900; 276abb0f93cSkardel pp->day = 100 * BP2(3) + 10 * BP1(4) + BP2(4); 277abb0f93cSkardel 278abb0f93cSkardel /* 279abb0f93cSkardel After Jan, 10 2000 Forum Graphic GPS receiver had a very strange 280abb0f93cSkardel benahour. It doubles day number for an hours in replys after 10:10:10 UTC 281abb0f93cSkardel and doubles min every hour at HH:10:ss for a minute. 282abb0f93cSkardel Hope it is a problem of my unit only and not a Y2K problem of FG GPS. 283abb0f93cSkardel Below small code to avoid such situation. 284abb0f93cSkardel */ 285abb0f93cSkardel if (up->y2kwarn > 10) 286abb0f93cSkardel pp->hour = BP1(6)*10 + BP2(6); 287abb0f93cSkardel else 288abb0f93cSkardel pp->hour = BP1(5)*10 + BP2(5); 289abb0f93cSkardel 2908585484eSchristos if ((up->y2kwarn > 10) && (pp->hour == 10)) { 291abb0f93cSkardel pp->minute = BP1(7)*10 + BP2(7); 292abb0f93cSkardel pp->second = BP1(8)*10 + BP2(8); 293abb0f93cSkardel pp->nsec = (BP1(9)*10 + BP2(9)) * 1000000; 294abb0f93cSkardel pp->nsec += BP1(10) * 1000; 295abb0f93cSkardel } else { 296abb0f93cSkardel pp->hour = BP1(5)*10 + BP2(5); 297abb0f93cSkardel pp->minute = BP1(6)*10 + BP2(6); 298abb0f93cSkardel pp->second = BP1(7)*10 + BP2(7); 299abb0f93cSkardel pp->nsec = (BP1(8)*10 + BP2(8)) * 1000000; 300abb0f93cSkardel pp->nsec += BP1(9) * 1000; 301abb0f93cSkardel } 302abb0f93cSkardel 3038585484eSchristos if ((pp->hour == 10) && (pp->minute == 10)) { 304abb0f93cSkardel up->y2kwarn++; 305abb0f93cSkardel } 306abb0f93cSkardel 3078585484eSchristos snprintf(pp->a_lastcode, sizeof(pp->a_lastcode), 3088585484eSchristos "%d %d %d %d %d", pp->year, pp->day, pp->hour, 3098585484eSchristos pp->minute, pp->second); 310abb0f93cSkardel pp->lencode = strlen(pp->a_lastcode); 311abb0f93cSkardel /*get_systime(&pp->lastrec);*/ 312abb0f93cSkardel 313abb0f93cSkardel #ifdef DEBUG 314abb0f93cSkardel if (debug) 315abb0f93cSkardel printf("fg: time is %04d/%03d %02d:%02d:%02d UTC\n", 316abb0f93cSkardel pp->year, pp->day, pp->hour, pp->minute, pp->second); 317abb0f93cSkardel #endif 318abb0f93cSkardel pp->disp = (10e-6); 319abb0f93cSkardel pp->lastrec = rbufp->recv_time; /* Is it better than get_systime()? */ 320abb0f93cSkardel /* pp->leap = LEAP_NOWARNING; */ 321abb0f93cSkardel 322abb0f93cSkardel /* 323abb0f93cSkardel * Process the new sample in the median filter and determine the 324abb0f93cSkardel * timecode timestamp. 325abb0f93cSkardel */ 326abb0f93cSkardel 327abb0f93cSkardel if (!refclock_process(pp)) 328abb0f93cSkardel refclock_report(peer, CEVNT_BADTIME); 329abb0f93cSkardel pp->lastref = pp->lastrec; 330abb0f93cSkardel refclock_receive(peer); 331abb0f93cSkardel return; 332abb0f93cSkardel } 333abb0f93cSkardel 334abb0f93cSkardel 335abb0f93cSkardel #else 336*eabc0478Schristos NONEMPTY_TRANSLATION_UNIT 337abb0f93cSkardel #endif /* REFCLOCK */ 338