154359Sroberto/* 2182007Sroberto * refclock_acts - clock driver for the NIST/USNO/PTB/NPL Computer Time 3182007Sroberto * Services 454359Sroberto */ 554359Sroberto#ifdef HAVE_CONFIG_H 654359Sroberto#include <config.h> 754359Sroberto#endif 854359Sroberto 9280849Scy#if defined(REFCLOCK) && defined(CLOCK_ACTS) 1054359Sroberto 1154359Sroberto#include "ntpd.h" 1254359Sroberto#include "ntp_io.h" 1354359Sroberto#include "ntp_unixtime.h" 1454359Sroberto#include "ntp_refclock.h" 1554359Sroberto#include "ntp_stdlib.h" 1654359Sroberto#include "ntp_control.h" 1754359Sroberto 1882498Sroberto#include <stdio.h> 1982498Sroberto#include <ctype.h> 2082498Sroberto#ifdef HAVE_SYS_IOCTL_H 2182498Sroberto# include <sys/ioctl.h> 2282498Sroberto#endif /* HAVE_SYS_IOCTL_H */ 2382498Sroberto 24280849Scy#ifdef SYS_WINNT 25280849Scy#undef write /* ports/winnt/include/config.h: #define write _write */ 26280849Scyextern int async_write(int, const void *, unsigned int); 27280849Scy#define write(fd, data, octets) async_write(fd, data, octets) 28280849Scy#endif 29280849Scy 3054359Sroberto/* 31182007Sroberto * This driver supports the US (NIST, USNO) and European (PTB, NPL, 32182007Sroberto * etc.) modem time services, as well as Spectracom GPS and WWVB 33182007Sroberto * receivers connected via a modem. The driver periodically dials a 34182007Sroberto * number from a telephone list, receives the timecode data and 35182007Sroberto * calculates the local clock correction. It is designed primarily for 36182007Sroberto * use as backup when neither a radio clock nor connectivity to Internet 37182007Sroberto * time servers is available. 3854359Sroberto * 39182007Sroberto * This driver requires a modem with a Hayes-compatible command set and 40182007Sroberto * control over the modem data terminal ready (DTR) control line. The 41182007Sroberto * modem setup string is hard-coded in the driver and may require 42280849Scy * changes for nonstandard modems or special circumstances. 4354359Sroberto * 44280849Scy * When enabled, the calling program dials the first number in the 45280849Scy * phones file. If that call fails, it dials the second number and 46280849Scy * so on. The phone number is specified by the Hayes ATDT prefix 47280849Scy * followed by the number itself, including the long-distance prefix 48280849Scy * and delay code, if necessary. The calling program is enabled 49280849Scy * when (a) fudge flag1 is set by ntpdc, (b) at each poll interval 50280849Scy * when no other synchronization sources are present, and (c) at each 51280849Scy * poll interval whether or not other synchronization sources are 52280849Scy * present. The calling program disconnects if (a) the called party 53280849Scy * is busy or does not answer, (b) the called party disconnects 54280849Scy * before a sufficient nuimber of timecodes have been received. 5554359Sroberto * 56182007Sroberto * The driver is transparent to each of the modem time services and 57182007Sroberto * Spectracom radios. It selects the parsing algorithm depending on the 58182007Sroberto * message length. There is some hazard should the message be corrupted. 59182007Sroberto * However, the data format is checked carefully and only if all checks 60182007Sroberto * succeed is the message accepted. Corrupted lines are discarded 61182007Sroberto * without complaint. 6254359Sroberto * 63182007Sroberto * Fudge controls 6454359Sroberto * 65182007Sroberto * flag1 force a call in manual mode 66182007Sroberto * flag2 enable port locking (not verified) 67280849Scy * flag3 not used 68182007Sroberto * flag4 not used 6954359Sroberto * 70182007Sroberto * time1 offset adjustment (s) 7154359Sroberto * 72280849Scy * Ordinarily, the serial port is connected to a modem and the phones 73280849Scy * list is defined. If no phones list is defined, the port can be 74280849Scy * connected directly to a device or another computer. In this case the 75280849Scy * driver will send a single character 'T' at each poll event. If 76280849Scy * fudge flag2 is enabled, port locking allows the modem to be shared 77280849Scy * when not in use by this driver. 7854359Sroberto */ 7954359Sroberto/* 80182007Sroberto * National Institute of Science and Technology (NIST) 8154359Sroberto * 82182007Sroberto * Phone: (303) 494-4774 (Boulder, CO); (808) 335-4721 (Hawaii) 8354359Sroberto * 84182007Sroberto * Data Format 85182007Sroberto * 8654359Sroberto * National Institute of Standards and Technology 8754359Sroberto * Telephone Time Service, Generator 3B 8854359Sroberto * Enter question mark "?" for HELP 8954359Sroberto * D L D 9054359Sroberto * MJD YR MO DA H M S ST S UT1 msADV <OTM> 91182007Sroberto * 47999 90-04-18 21:39:15 50 0 +.1 045.0 UTC(NIST) *<CR><LF> 92182007Sroberto * ... 9354359Sroberto * 94182007Sroberto * MJD, DST, DUT1 and UTC are not used by this driver. The "*" or "#" is 95182007Sroberto * the on-time markers echoed by the driver and used by NIST to measure 96280849Scy * and correct for the propagation delay. Note: the ACTS timecode has 97280849Scy * recently been changed to eliminate the * on-time indicator. The 98280849Scy * reason for this and the long term implications are not clear. 9954359Sroberto * 100182007Sroberto * US Naval Observatory (USNO) 10154359Sroberto * 102182007Sroberto * Phone: (202) 762-1594 (Washington, DC); (719) 567-6742 (Boulder, CO) 10354359Sroberto * 104182007Sroberto * Data Format (two lines, repeating at one-second intervals) 10554359Sroberto * 106182007Sroberto * jjjjj nnn hhmmss UTC<CR><LF> 107182007Sroberto * *<CR><LF> 10854359Sroberto * 109182007Sroberto * jjjjj modified Julian day number (not used) 110182007Sroberto * nnn day of year 111182007Sroberto * hhmmss second of day 112182007Sroberto * * on-time marker for previous timecode 113182007Sroberto * ... 11454359Sroberto * 115182007Sroberto * USNO does not correct for the propagation delay. A fudge time1 of 116182007Sroberto * about .06 s is advisable. 11754359Sroberto * 118182007Sroberto * European Services (PTB, NPL, etc.) 11954359Sroberto * 120182007Sroberto * PTB: +49 531 512038 (Germany) 121182007Sroberto * NPL: 0906 851 6333 (UK only) 12254359Sroberto * 123182007Sroberto * Data format (see the documentation for phone numbers and formats.) 12454359Sroberto * 125182007Sroberto * 1995-01-23 20:58:51 MEZ 10402303260219950123195849740+40000500<CR><LF> 12654359Sroberto * 127182007Sroberto * Spectracom GPS and WWVB Receivers 12854359Sroberto * 129182007Sroberto * If a modem is connected to a Spectracom receiver, this driver will 130182007Sroberto * call it up and retrieve the time in one of two formats. As this 131182007Sroberto * driver does not send anything, the radio will have to either be 132182007Sroberto * configured in continuous mode or be polled by another local driver. 13354359Sroberto */ 13454359Sroberto/* 13554359Sroberto * Interface definitions 13654359Sroberto */ 137182007Sroberto#define DEVICE "/dev/acts%d" /* device name and unit */ 138280849Scy#define SPEED232 B19200 /* uart speed (19200 bps) */ 13954359Sroberto#define PRECISION (-10) /* precision assumed (about 1 ms) */ 140280849Scy#define LOCKFILE "/var/spool/lock/LCK..cua%d" 141182007Sroberto#define DESCRIPTION "Automated Computer Time Service" /* WRU */ 142182007Sroberto#define REFID "NONE" /* default reference ID */ 143182007Sroberto#define MSGCNT 20 /* max message count */ 144280849Scy#define MAXPHONE 10 /* max number of phone numbers */ 145182007Sroberto 146182007Sroberto/* 147280849Scy * Calling program modes (mode) 148182007Sroberto */ 149280849Scy#define MODE_BACKUP 0 /* backup mode */ 150280849Scy#define MODE_AUTO 1 /* automatic mode */ 15154359Sroberto#define MODE_MANUAL 2 /* manual mode */ 15254359Sroberto 153182007Sroberto/* 154280849Scy * Service identifiers (message length) 155182007Sroberto */ 156182007Sroberto#define REFACTS "NIST" /* NIST reference ID */ 157280849Scy#define LENACTS 50 /* NIST format A */ 158182007Sroberto#define REFUSNO "USNO" /* USNO reference ID */ 159182007Sroberto#define LENUSNO 20 /* USNO */ 160182007Sroberto#define REFPTB "PTB\0" /* PTB/NPL reference ID */ 161182007Sroberto#define LENPTB 78 /* PTB/NPL format */ 162182007Sroberto#define REFWWVB "WWVB" /* WWVB reference ID */ 163182007Sroberto#define LENWWVB0 22 /* WWVB format 0 */ 164182007Sroberto#define LENWWVB2 24 /* WWVB format 2 */ 165182007Sroberto#define LF 0x0a /* ASCII LF */ 16654359Sroberto 16754359Sroberto/* 168280849Scy * Modem setup strings. These may have to be changed for 169280849Scy * some modems. 17054359Sroberto * 17154359Sroberto * AT command prefix 172182007Sroberto * B1 US answer tone 173182007Sroberto * &C0 disable carrier detect 17454359Sroberto * &D2 hang up and return to command mode on DTR transition 17554359Sroberto * E0 modem command echo disabled 176280849Scy * L1 set modem speaker volume to low level 177182007Sroberto * M1 speaker enabled until carrier detect 17854359Sroberto * Q0 return result codes 17954359Sroberto * V1 return result codes as English words 180280849Scy * Y1 enable long-space disconnect 18154359Sroberto */ 182280849Scyconst char def_modem_setup[] = "ATB1&C0&D2E0L1M1Q0V1Y1"; 183280849Scyconst char *modem_setup = def_modem_setup; 18454359Sroberto 18554359Sroberto/* 186182007Sroberto * Timeouts (all in seconds) 18754359Sroberto */ 188182007Sroberto#define SETUP 3 /* setup timeout */ 189280849Scy#define REDIAL 30 /* redial timeout */ 190182007Sroberto#define ANSWER 60 /* answer timeout */ 191280849Scy#define TIMECODE 60 /* message timeout */ 192280849Scy#define MAXCODE 20 /* max timecodes */ 19354359Sroberto 19454359Sroberto/* 195182007Sroberto * State machine codes 19654359Sroberto */ 197280849Scytypedef enum { 198280849Scy S_IDLE, /* wait for poll */ 199280849Scy S_SETUP, /* send modem setup */ 200280849Scy S_CONNECT, /* wait for answer */ 201280849Scy S_MSG /* wait for timecode */ 202280849Scy} teModemState; 20354359Sroberto 20454359Sroberto/* 20554359Sroberto * Unit control structure 20654359Sroberto */ 20754359Srobertostruct actsunit { 208182007Sroberto int unit; /* unit number */ 20954359Sroberto int state; /* the first one was Delaware */ 210182007Sroberto int timer; /* timeout counter */ 211182007Sroberto int retry; /* retry index */ 212182007Sroberto int msgcnt; /* count of messages received */ 213182007Sroberto l_fp tstamp; /* on-time timestamp */ 214280849Scy char *bufptr; /* next incoming char stored here */ 215280849Scy char buf[BMAX]; /* bufptr roams within buf[] */ 21654359Sroberto}; 21754359Sroberto 21854359Sroberto/* 21954359Sroberto * Function prototypes 22054359Sroberto */ 221280849Scystatic int acts_start (int, struct peer *); 222280849Scystatic void acts_shutdown (int, struct peer *); 223280849Scystatic void acts_receive (struct recvbuf *); 224280849Scystatic void acts_message (struct peer *, const char *); 225280849Scystatic void acts_timecode (struct peer *, const char *); 226280849Scystatic void acts_poll (int, struct peer *); 227280849Scystatic void acts_timeout (struct peer *, teModemState); 228280849Scystatic void acts_timer (int, struct peer *); 229280849Scystatic void acts_close (struct peer *); 23054359Sroberto 23154359Sroberto/* 23254359Sroberto * Transfer vector (conditional structure name) 23354359Sroberto */ 234280849Scystruct refclock refclock_acts = { 23554359Sroberto acts_start, /* start up driver */ 23654359Sroberto acts_shutdown, /* shut down driver */ 23754359Sroberto acts_poll, /* transmit poll message */ 238182007Sroberto noentry, /* not used */ 239182007Sroberto noentry, /* not used */ 240182007Sroberto noentry, /* not used */ 241182007Sroberto acts_timer /* housekeeping timer */ 24254359Sroberto}; 24354359Sroberto 24454359Sroberto/* 245182007Sroberto * Initialize data for processing 24654359Sroberto */ 24754359Srobertostatic int 248280849Scyacts_start( 249182007Sroberto int unit, 25054359Sroberto struct peer *peer 25154359Sroberto ) 25254359Sroberto{ 253182007Sroberto struct actsunit *up; 25454359Sroberto struct refclockproc *pp; 255280849Scy const char *setup; 25654359Sroberto 25754359Sroberto /* 258182007Sroberto * Allocate and initialize unit structure 25954359Sroberto */ 260280849Scy up = emalloc_zero(sizeof(struct actsunit)); 261182007Sroberto up->unit = unit; 26254359Sroberto pp = peer->procptr; 263280849Scy pp->unitptr = up; 26454359Sroberto pp->io.clock_recv = acts_receive; 265280849Scy pp->io.srcclock = peer; 26654359Sroberto pp->io.datalen = 0; 267280849Scy pp->io.fd = -1; 26854359Sroberto 26954359Sroberto /* 27054359Sroberto * Initialize miscellaneous variables 27154359Sroberto */ 27254359Sroberto peer->precision = PRECISION; 27354359Sroberto pp->clockdesc = DESCRIPTION; 274280849Scy memcpy(&pp->refid, REFID, 4); 27554359Sroberto peer->sstclktype = CTL_SST_TS_TELEPHONE; 276280849Scy up->bufptr = up->buf; 277280849Scy if (def_modem_setup == modem_setup) { 278280849Scy setup = get_ext_sys_var("modemsetup"); 279280849Scy if (setup != NULL) 280280849Scy modem_setup = estrdup(setup); 281280849Scy } 282280849Scy 28354359Sroberto return (1); 28454359Sroberto} 28554359Sroberto 28654359Sroberto 28754359Sroberto/* 28854359Sroberto * acts_shutdown - shut down the clock 28954359Sroberto */ 29054359Srobertostatic void 291280849Scyacts_shutdown( 292182007Sroberto int unit, 29354359Sroberto struct peer *peer 29454359Sroberto ) 29554359Sroberto{ 296182007Sroberto struct actsunit *up; 29754359Sroberto struct refclockproc *pp; 29854359Sroberto 299182007Sroberto /* 300182007Sroberto * Warning: do this only when a call is not in progress. 301182007Sroberto */ 30254359Sroberto pp = peer->procptr; 303280849Scy up = pp->unitptr; 304280849Scy acts_close(peer); 30554359Sroberto free(up); 30654359Sroberto} 30754359Sroberto 30854359Sroberto 30954359Sroberto/* 31054359Sroberto * acts_receive - receive data from the serial interface 31154359Sroberto */ 31254359Srobertostatic void 313280849Scyacts_receive( 31454359Sroberto struct recvbuf *rbufp 31554359Sroberto ) 31654359Sroberto{ 317182007Sroberto struct actsunit *up; 31854359Sroberto struct refclockproc *pp; 31954359Sroberto struct peer *peer; 320280849Scy char tbuf[sizeof(up->buf)]; 321280849Scy char * tptr; 322280849Scy int octets; 323182007Sroberto 32454359Sroberto /* 325182007Sroberto * Initialize pointers and read the timecode and timestamp. Note 326182007Sroberto * we are in raw mode and victim of whatever the terminal 327182007Sroberto * interface kicks up; so, we have to reassemble messages from 328182007Sroberto * arbitrary fragments. Capture the timecode at the beginning of 329182007Sroberto * the message and at the '*' and '#' on-time characters. 33054359Sroberto */ 331280849Scy peer = rbufp->recv_peer; 33254359Sroberto pp = peer->procptr; 333280849Scy up = pp->unitptr; 334280849Scy octets = sizeof(up->buf) - (up->bufptr - up->buf); 335280849Scy refclock_gtraw(rbufp, tbuf, octets, &pp->lastrec); 336182007Sroberto for (tptr = tbuf; *tptr != '\0'; tptr++) { 337182007Sroberto if (*tptr == LF) { 338280849Scy if (up->bufptr == up->buf) { 339182007Sroberto up->tstamp = pp->lastrec; 340182007Sroberto continue; 341182007Sroberto } else { 342182007Sroberto *up->bufptr = '\0'; 343280849Scy up->bufptr = up->buf; 344280849Scy acts_message(peer, up->buf); 345182007Sroberto } 346280849Scy } else if (!iscntrl((unsigned char)*tptr)) { 347182007Sroberto *up->bufptr++ = *tptr; 348182007Sroberto if (*tptr == '*' || *tptr == '#') { 349182007Sroberto up->tstamp = pp->lastrec; 350280849Scy if (write(pp->io.fd, tptr, 1) < 0) 351280849Scy msyslog(LOG_ERR, "acts: write echo fails %m"); 352182007Sroberto } 353182007Sroberto } 35454359Sroberto } 355182007Sroberto} 356182007Sroberto 357182007Sroberto 358182007Sroberto/* 359182007Sroberto * acts_message - process message 360182007Sroberto */ 361182007Srobertovoid 362182007Srobertoacts_message( 363280849Scy struct peer *peer, 364280849Scy const char *msg 365182007Sroberto ) 366182007Sroberto{ 367182007Sroberto struct actsunit *up; 368182007Sroberto struct refclockproc *pp; 369280849Scy char tbuf[BMAX]; 370280849Scy int dtr = TIOCM_DTR; 371182007Sroberto 372280849Scy DPRINTF(1, ("acts: %d %s\n", (int)strlen(msg), msg)); 373280849Scy 374182007Sroberto /* 375182007Sroberto * What to do depends on the state and the first token in the 376280849Scy * message. 377182007Sroberto */ 378182007Sroberto pp = peer->procptr; 379280849Scy up = pp->unitptr; 380280849Scy 381280849Scy /* 382280849Scy * Extract the first token in the line. 383280849Scy */ 384280849Scy strlcpy(tbuf, msg, sizeof(tbuf)); 385182007Sroberto strtok(tbuf, " "); 386280849Scy switch (up->state) { 38754359Sroberto 388182007Sroberto /* 389182007Sroberto * We are waiting for the OK response to the modem setup 390280849Scy * command. When this happens, dial the number followed. 391280849Scy * If anything other than OK is received, just ignore it 392280849Scy * and wait for timeoue. 393182007Sroberto */ 394280849Scy case S_SETUP: 395182007Sroberto if (strcmp(tbuf, "OK") != 0) { 396280849Scy /* 397280849Scy * We disable echo with MODEM_SETUP's E0 but 398280849Scy * if the modem was previously E1, we will 399280849Scy * see MODEM_SETUP echoed before the OK/ERROR. 400280849Scy * Ignore it. 401280849Scy */ 402280849Scy if (!strcmp(tbuf, modem_setup)) 403280849Scy return; 404280849Scy break; 405182007Sroberto } 406280849Scy 407280849Scy mprintf_event(PEVNT_CLOCK, peer, "DIAL #%d %s", 408280849Scy up->retry, sys_phone[up->retry]); 409280849Scy if (ioctl(pp->io.fd, TIOCMBIS, &dtr) < 0) 410280849Scy msyslog(LOG_ERR, "acts: ioctl(TIOCMBIS) failed: %m"); 411280849Scy if (write(pp->io.fd, sys_phone[up->retry], 412280849Scy strlen(sys_phone[up->retry])) < 0) 413280849Scy msyslog(LOG_ERR, "acts: write DIAL fails %m"); 414280849Scy write(pp->io.fd, "\r", 1); 415280849Scy up->retry++; 416280849Scy up->state = S_CONNECT; 417280849Scy up->timer = ANSWER; 41854359Sroberto return; 41954359Sroberto 420182007Sroberto /* 421280849Scy * We are waiting for the CONNECT response to the dial 422280849Scy * command. When this happens, listen for timecodes. If 423280849Scy * somthing other than CONNECT is received, like BUSY 424280849Scy * or NO CARRIER, abort the call. 425182007Sroberto */ 426182007Sroberto case S_CONNECT: 427280849Scy if (strcmp(tbuf, "CONNECT") != 0) 428280849Scy break; 429280849Scy 430280849Scy report_event(PEVNT_CLOCK, peer, msg); 431280849Scy up->state = S_MSG; 432280849Scy up->timer = TIMECODE; 433280849Scy return; 434280849Scy 435280849Scy /* 436280849Scy * We are waiting for a timecode response. Pass it to 437280849Scy * the parser. If NO CARRIER is received, save the 438280849Scy * messages and abort the call. 439280849Scy */ 440280849Scy case S_MSG: 441280849Scy if (strcmp(tbuf, "NO") == 0) 442280849Scy report_event(PEVNT_CLOCK, peer, msg); 443280849Scy if (up->msgcnt < MAXCODE) 444280849Scy acts_timecode(peer, msg); 445280849Scy else 446280849Scy acts_timeout(peer, S_MSG); 447280849Scy return; 448280849Scy } 449280849Scy 450280849Scy /* 451280849Scy * Other response. Tell us about it. 452280849Scy */ 453280849Scy report_event(PEVNT_CLOCK, peer, msg); 454280849Scy acts_close(peer); 455280849Scy} 456280849Scy 457280849Scy 458280849Scy/* 459280849Scy * acts_timeout - called on timeout 460280849Scy */ 461280849Scystatic void 462280849Scyacts_timeout( 463280849Scy struct peer *peer, 464280849Scy teModemState dstate 465280849Scy ) 466280849Scy{ 467280849Scy struct actsunit *up; 468280849Scy struct refclockproc *pp; 469280849Scy int fd; 470280849Scy int rc; 471280849Scy char device[20]; 472280849Scy char lockfile[128], pidbuf[8]; 473280849Scy 474280849Scy /* 475280849Scy * The state machine is driven by messages from the modem, 476280849Scy * when first started and at timeout. 477280849Scy */ 478280849Scy pp = peer->procptr; 479280849Scy up = pp->unitptr; 480280849Scy switch (dstate) { 481280849Scy 482280849Scy /* 483280849Scy * System poll event. Lock the modem port, open the device 484280849Scy * and send the setup command. 485280849Scy */ 486280849Scy case S_IDLE: 487280849Scy if (-1 != pp->io.fd) 488280849Scy return; /* port is already open */ 489280849Scy 490280849Scy /* 491280849Scy * Lock the modem port. If busy, retry later. Note: if 492280849Scy * something fails between here and the close, the lock 493280849Scy * file may not be removed. 494280849Scy */ 495280849Scy if (pp->sloppyclockflag & CLK_FLAG2) { 496280849Scy snprintf(lockfile, sizeof(lockfile), LOCKFILE, 497280849Scy up->unit); 498280849Scy fd = open(lockfile, O_WRONLY | O_CREAT | O_EXCL, 499280849Scy 0644); 500280849Scy if (fd < 0) { 501280849Scy report_event(PEVNT_CLOCK, peer, "acts: port busy"); 502280849Scy return; 503280849Scy } 504280849Scy snprintf(pidbuf, sizeof(pidbuf), "%d\n", 505280849Scy (u_int)getpid()); 506280849Scy if (write(fd, pidbuf, strlen(pidbuf)) < 0) 507280849Scy msyslog(LOG_ERR, "acts: write lock fails %m"); 508280849Scy close(fd); 509280849Scy } 510280849Scy 511280849Scy /* 512280849Scy * Open the device in raw mode and link the I/O. 513280849Scy */ 514280849Scy snprintf(device, sizeof(device), DEVICE, 515280849Scy up->unit); 516280849Scy fd = refclock_open(device, SPEED232, LDISC_ACTS | 517280849Scy LDISC_RAW | LDISC_REMOTE); 518280849Scy if (fd < 0) { 519280849Scy msyslog(LOG_ERR, "acts: open fails %m"); 52054359Sroberto return; 52154359Sroberto } 522280849Scy pp->io.fd = fd; 523280849Scy if (!io_addclock(&pp->io)) { 524280849Scy msyslog(LOG_ERR, "acts: addclock fails"); 525280849Scy close(fd); 526280849Scy pp->io.fd = -1; 527280849Scy return; 528280849Scy } 529280849Scy up->msgcnt = 0; 530280849Scy up->bufptr = up->buf; 531280849Scy 532280849Scy /* 533280849Scy * If the port is directly connected to the device, skip 534280849Scy * the modem business and send 'T' for Spectrabum. 535280849Scy */ 536280849Scy if (sys_phone[up->retry] == NULL) { 537280849Scy if (write(pp->io.fd, "T", 1) < 0) 538280849Scy msyslog(LOG_ERR, "acts: write T fails %m"); 539280849Scy up->state = S_MSG; 540280849Scy up->timer = TIMECODE; 541280849Scy return; 542280849Scy } 543280849Scy 544280849Scy /* 545280849Scy * Initialize the modem. This works with Hayes- 546280849Scy * compatible modems. 547280849Scy */ 548280849Scy mprintf_event(PEVNT_CLOCK, peer, "SETUP %s", 549280849Scy modem_setup); 550280849Scy rc = write(pp->io.fd, modem_setup, strlen(modem_setup)); 551280849Scy if (rc < 0) 552280849Scy msyslog(LOG_ERR, "acts: write SETUP fails %m"); 553280849Scy write(pp->io.fd, "\r", 1); 554280849Scy up->state = S_SETUP; 555280849Scy up->timer = SETUP; 55654359Sroberto return; 55754359Sroberto 558182007Sroberto /* 559280849Scy * In SETUP state the modem did not respond OK to setup string. 560182007Sroberto */ 561280849Scy case S_SETUP: 562280849Scy report_event(PEVNT_CLOCK, peer, "no modem"); 563280849Scy break; 564280849Scy 565280849Scy /* 566280849Scy * In CONNECT state the call did not complete. Abort the call. 567280849Scy */ 568280849Scy case S_CONNECT: 569280849Scy report_event(PEVNT_CLOCK, peer, "no answer"); 570280849Scy break; 571280849Scy 572280849Scy /* 573280849Scy * In MSG states no further timecodes are expected. If any 574280849Scy * timecodes have arrived, update the clock. In any case, 575280849Scy * terminate the call. 576280849Scy */ 577182007Sroberto case S_MSG: 578280849Scy if (up->msgcnt == 0) { 579280849Scy report_event(PEVNT_CLOCK, peer, "no timecodes"); 580280849Scy } else { 581280849Scy pp->lastref = pp->lastrec; 582280849Scy record_clock_stats(&peer->srcadr, pp->a_lastcode); 583280849Scy refclock_receive(peer); 584280849Scy } 585182007Sroberto break; 58654359Sroberto } 587280849Scy acts_close(peer); 588182007Sroberto} 58954359Sroberto 590280849Scy 591182007Sroberto/* 592280849Scy * acts_close - close and prepare for next call. 593280849Scy * 594280849Scy * In ClOSE state no further protocol actions are required 595280849Scy * other than to close and release the device and prepare to 596280849Scy * dial the next number if necessary. 597280849Scy */ 598280849Scyvoid 599280849Scyacts_close( 600280849Scy struct peer *peer 601280849Scy ) 602280849Scy{ 603280849Scy struct actsunit *up; 604280849Scy struct refclockproc *pp; 605280849Scy char lockfile[128]; 606280849Scy int dtr; 607280849Scy 608280849Scy pp = peer->procptr; 609280849Scy up = pp->unitptr; 610280849Scy if (pp->io.fd != -1) { 611280849Scy report_event(PEVNT_CLOCK, peer, "close"); 612280849Scy dtr = TIOCM_DTR; 613280849Scy if (ioctl(pp->io.fd, TIOCMBIC, &dtr) < 0) 614280849Scy msyslog(LOG_ERR, "acts: ioctl(TIOCMBIC) failed: %m"); 615280849Scy io_closeclock(&pp->io); 616280849Scy pp->io.fd = -1; 617280849Scy } 618280849Scy if (pp->sloppyclockflag & CLK_FLAG2) { 619280849Scy snprintf(lockfile, sizeof(lockfile), 620280849Scy LOCKFILE, up->unit); 621280849Scy unlink(lockfile); 622280849Scy } 623280849Scy if (up->msgcnt == 0 && up->retry > 0) { 624280849Scy if (sys_phone[up->retry] != NULL) { 625280849Scy up->state = S_IDLE; 626280849Scy up->timer = REDIAL; 627280849Scy return; 628280849Scy } 629280849Scy } 630280849Scy up->state = S_IDLE; 631280849Scy up->timer = 0; 632280849Scy} 633280849Scy 634280849Scy 635280849Scy/* 636280849Scy * acts_poll - called by the transmit routine 637280849Scy */ 638280849Scystatic void 639280849Scyacts_poll( 640280849Scy int unit, 641280849Scy struct peer *peer 642280849Scy ) 643280849Scy{ 644280849Scy struct actsunit *up; 645280849Scy struct refclockproc *pp; 646280849Scy 647280849Scy /* 648280849Scy * This routine is called at every system poll. All it does is 649280849Scy * set flag1 under certain conditions. The real work is done by 650280849Scy * the timeout routine and state machine. 651280849Scy */ 652280849Scy pp = peer->procptr; 653280849Scy up = pp->unitptr; 654280849Scy switch (peer->ttl) { 655280849Scy 656280849Scy /* 657280849Scy * In manual mode the calling program is activated by the ntpdc 658280849Scy * program using the enable flag (fudge flag1), either manually 659280849Scy * or by a cron job. 660280849Scy */ 661280849Scy case MODE_MANUAL: 662280849Scy return; 663280849Scy 664280849Scy /* 665280849Scy * In automatic mode the calling program runs continuously at 666280849Scy * intervals determined by the poll event or specified timeout. 667280849Scy */ 668280849Scy case MODE_AUTO: 669280849Scy break; 670280849Scy 671280849Scy /* 672280849Scy * In backup mode the calling program runs continuously as long 673280849Scy * as either no peers are available or this peer is selected. 674280849Scy */ 675280849Scy case MODE_BACKUP: 676280849Scy if (!(sys_peer == NULL || sys_peer == peer)) 677280849Scy return; 678280849Scy 679280849Scy break; 680280849Scy } 681280849Scy pp->polls++; 682280849Scy if (S_IDLE == up->state) { 683280849Scy up->retry = 0; 684280849Scy acts_timeout(peer, S_IDLE); 685280849Scy } 686280849Scy} 687280849Scy 688280849Scy 689280849Scy/* 690280849Scy * acts_timer - called at one-second intervals 691280849Scy */ 692280849Scystatic void 693280849Scyacts_timer( 694280849Scy int unit, 695280849Scy struct peer *peer 696280849Scy ) 697280849Scy{ 698280849Scy struct actsunit *up; 699280849Scy struct refclockproc *pp; 700280849Scy 701280849Scy /* 702280849Scy * This routine implments a timeout which runs for a programmed 703280849Scy * interval. The counter is initialized by the state machine and 704280849Scy * counts down to zero. Upon reaching zero, the state machine is 705280849Scy * called. If flag1 is set while timer is zero, force a call. 706280849Scy */ 707280849Scy pp = peer->procptr; 708280849Scy up = pp->unitptr; 709280849Scy if (up->timer == 0) { 710280849Scy if (pp->sloppyclockflag & CLK_FLAG1) { 711280849Scy pp->sloppyclockflag &= ~CLK_FLAG1; 712280849Scy acts_timeout(peer, S_IDLE); 713280849Scy } 714280849Scy } else { 715280849Scy up->timer--; 716280849Scy if (up->timer == 0) 717280849Scy acts_timeout(peer, up->state); 718280849Scy } 719280849Scy} 720280849Scy 721280849Scy/* 722182007Sroberto * acts_timecode - identify the service and parse the timecode message 723182007Sroberto */ 724182007Srobertovoid 725182007Srobertoacts_timecode( 726280849Scy struct peer * peer, /* peer structure pointer */ 727280849Scy const char * str /* timecode string */ 728182007Sroberto ) 729182007Sroberto{ 730182007Sroberto struct actsunit *up; 731182007Sroberto struct refclockproc *pp; 732182007Sroberto int day; /* day of the month */ 733182007Sroberto int month; /* month of the year */ 734182007Sroberto u_long mjd; /* Modified Julian Day */ 735182007Sroberto double dut1; /* DUT adjustment */ 736182007Sroberto 737182007Sroberto u_int dst; /* ACTS daylight/standard time */ 738182007Sroberto u_int leap; /* ACTS leap indicator */ 739182007Sroberto double msADV; /* ACTS transmit advance (ms) */ 740182007Sroberto char utc[10]; /* ACTS timescale */ 741182007Sroberto char flag; /* ACTS on-time character (* or #) */ 742182007Sroberto 743182007Sroberto char synchar; /* WWVB synchronized indicator */ 744182007Sroberto char qualchar; /* WWVB quality indicator */ 745182007Sroberto char leapchar; /* WWVB leap indicator */ 746182007Sroberto char dstchar; /* WWVB daylight/savings indicator */ 747182007Sroberto int tz; /* WWVB timezone */ 748182007Sroberto 749280849Scy int leapmonth; /* PTB/NPL month of leap */ 750182007Sroberto char leapdir; /* PTB/NPL leap direction */ 751182007Sroberto 75254359Sroberto /* 753182007Sroberto * The parser selects the modem format based on the message 754182007Sroberto * length. Since the data are checked carefully, occasional 755182007Sroberto * errors due noise are forgivable. 75654359Sroberto */ 757182007Sroberto pp = peer->procptr; 758280849Scy up = pp->unitptr; 759182007Sroberto pp->nsec = 0; 760280849Scy switch (strlen(str)) { 76154359Sroberto 76254359Sroberto /* 763182007Sroberto * For USNO format on-time character '*', which is on a line by 764182007Sroberto * itself. Be sure a timecode has been received. 76554359Sroberto */ 766182007Sroberto case 1: 767182007Sroberto if (*str == '*' && up->msgcnt > 0) 768182007Sroberto break; 769182007Sroberto 77054359Sroberto return; 771182007Sroberto 77254359Sroberto /* 773280849Scy * ACTS format A: "jjjjj yy-mm-dd hh:mm:ss ds l uuu aaaaa 774280849Scy * UTC(NIST) *". 77554359Sroberto */ 776182007Sroberto case LENACTS: 777182007Sroberto if (sscanf(str, 778182007Sroberto "%5ld %2d-%2d-%2d %2d:%2d:%2d %2d %1d %3lf %5lf %9s %c", 779182007Sroberto &mjd, &pp->year, &month, &day, &pp->hour, 780182007Sroberto &pp->minute, &pp->second, &dst, &leap, &dut1, 781182007Sroberto &msADV, utc, &flag) != 13) { 782182007Sroberto refclock_report(peer, CEVNT_BADREPLY); 783182007Sroberto return; 784182007Sroberto } 785182007Sroberto pp->day = ymd2yd(pp->year, month, day); 786182007Sroberto pp->leap = LEAP_NOWARNING; 787182007Sroberto if (leap == 1) 788280849Scy pp->leap = LEAP_ADDSECOND; 789280849Scy else if (leap == 2) 790280849Scy pp->leap = LEAP_DELSECOND; 791182007Sroberto memcpy(&pp->refid, REFACTS, 4); 792182007Sroberto up->msgcnt++; 793280849Scy if (flag != '#' && up->msgcnt < 10) 794280849Scy return; 795280849Scy 796182007Sroberto break; 797182007Sroberto 79854359Sroberto /* 799182007Sroberto * USNO format: "jjjjj nnn hhmmss UTC" 80054359Sroberto */ 801182007Sroberto case LENUSNO: 802182007Sroberto if (sscanf(str, "%5ld %3d %2d%2d%2d %3s", 803182007Sroberto &mjd, &pp->day, &pp->hour, &pp->minute, 804182007Sroberto &pp->second, utc) != 6) { 805182007Sroberto refclock_report(peer, CEVNT_BADREPLY); 806182007Sroberto return; 807182007Sroberto } 80854359Sroberto 809182007Sroberto /* 810182007Sroberto * Wait for the on-time character, which follows in a 811182007Sroberto * separate message. There is no provision for leap 812182007Sroberto * warning. 813182007Sroberto */ 814182007Sroberto pp->leap = LEAP_NOWARNING; 815182007Sroberto memcpy(&pp->refid, REFUSNO, 4); 816182007Sroberto up->msgcnt++; 817280849Scy break; 818182007Sroberto 81954359Sroberto /* 820182007Sroberto * PTB/NPL format: "yyyy-mm-dd hh:mm:ss MEZ" 82154359Sroberto */ 822182007Sroberto case LENPTB: 823182007Sroberto if (sscanf(str, 824182007Sroberto "%*4d-%*2d-%*2d %*2d:%*2d:%2d %*5c%*12c%4d%2d%2d%2d%2d%5ld%2lf%c%2d%3lf%*15c%c", 825182007Sroberto &pp->second, &pp->year, &month, &day, &pp->hour, 826182007Sroberto &pp->minute, &mjd, &dut1, &leapdir, &leapmonth, 827182007Sroberto &msADV, &flag) != 12) { 828182007Sroberto refclock_report(peer, CEVNT_BADREPLY); 829182007Sroberto return; 830182007Sroberto } 831182007Sroberto pp->leap = LEAP_NOWARNING; 832182007Sroberto if (leapmonth == month) { 833182007Sroberto if (leapdir == '+') 834280849Scy pp->leap = LEAP_ADDSECOND; 835182007Sroberto else if (leapdir == '-') 836280849Scy pp->leap = LEAP_DELSECOND; 837182007Sroberto } 838182007Sroberto pp->day = ymd2yd(pp->year, month, day); 839182007Sroberto memcpy(&pp->refid, REFPTB, 4); 840182007Sroberto up->msgcnt++; 841182007Sroberto break; 84282498Sroberto 843182007Sroberto 84482498Sroberto /* 845182007Sroberto * WWVB format 0: "I ddd hh:mm:ss DTZ=nn" 84682498Sroberto */ 847182007Sroberto case LENWWVB0: 848182007Sroberto if (sscanf(str, "%c %3d %2d:%2d:%2d %cTZ=%2d", 849182007Sroberto &synchar, &pp->day, &pp->hour, &pp->minute, 850182007Sroberto &pp->second, &dstchar, &tz) != 7) { 851182007Sroberto refclock_report(peer, CEVNT_BADREPLY); 85254359Sroberto return; 85354359Sroberto } 854182007Sroberto pp->leap = LEAP_NOWARNING; 855182007Sroberto if (synchar != ' ') 856182007Sroberto pp->leap = LEAP_NOTINSYNC; 857182007Sroberto memcpy(&pp->refid, REFWWVB, 4); 858182007Sroberto up->msgcnt++; 859182007Sroberto break; 860182007Sroberto 861182007Sroberto /* 862182007Sroberto * WWVB format 2: "IQyy ddd hh:mm:ss.mmm LD" 863182007Sroberto */ 864182007Sroberto case LENWWVB2: 865182007Sroberto if (sscanf(str, "%c%c%2d %3d %2d:%2d:%2d.%3ld%c%c%c", 866182007Sroberto &synchar, &qualchar, &pp->year, &pp->day, 867182007Sroberto &pp->hour, &pp->minute, &pp->second, &pp->nsec, 868182007Sroberto &dstchar, &leapchar, &dstchar) != 11) { 869182007Sroberto refclock_report(peer, CEVNT_BADREPLY); 87054359Sroberto return; 87154359Sroberto } 872182007Sroberto pp->nsec *= 1000000; 873182007Sroberto pp->leap = LEAP_NOWARNING; 874182007Sroberto if (synchar != ' ') 875182007Sroberto pp->leap = LEAP_NOTINSYNC; 876182007Sroberto else if (leapchar == 'L') 877182007Sroberto pp->leap = LEAP_ADDSECOND; 878182007Sroberto memcpy(&pp->refid, REFWWVB, 4); 879182007Sroberto up->msgcnt++; 880182007Sroberto break; 88154359Sroberto 88254359Sroberto /* 883182007Sroberto * None of the above. Just forget about it and wait for the next 884182007Sroberto * message or timeout. 88554359Sroberto */ 886182007Sroberto default: 887182007Sroberto return; 88854359Sroberto } 88954359Sroberto 89054359Sroberto /* 891182007Sroberto * We have a valid timecode. The fudge time1 value is added to 892182007Sroberto * each sample by the main line routines. Note that in current 893182007Sroberto * telephone networks the propatation time can be different for 894182007Sroberto * each call and can reach 200 ms for some calls. 89554359Sroberto */ 896182007Sroberto peer->refid = pp->refid; 897182007Sroberto pp->lastrec = up->tstamp; 898280849Scy if (up->msgcnt == 0) 899280849Scy return; 900280849Scy 901280849Scy strlcpy(pp->a_lastcode, str, sizeof(pp->a_lastcode)); 902280849Scy pp->lencode = strlen(pp->a_lastcode); 903182007Sroberto if (!refclock_process(pp)) { 904182007Sroberto refclock_report(peer, CEVNT_BADTIME); 905182007Sroberto return; 906280849Scy } 907132451Sroberto pp->lastref = pp->lastrec; 90854359Sroberto} 90954359Sroberto#else 91054359Srobertoint refclock_acts_bs; 91154359Sroberto#endif /* REFCLOCK */ 912