154359Sroberto/* 254359Sroberto * refclock_pst - clock driver for PSTI/Traconex WWV/WWVH receivers 354359Sroberto */ 454359Sroberto 554359Sroberto#ifdef HAVE_CONFIG_H 654359Sroberto#include <config.h> 754359Sroberto#endif 854359Sroberto 954359Sroberto#if defined(REFCLOCK) && defined(CLOCK_PST) 1054359Sroberto 1154359Sroberto#include "ntpd.h" 1254359Sroberto#include "ntp_io.h" 1354359Sroberto#include "ntp_refclock.h" 1454359Sroberto#include "ntp_stdlib.h" 1554359Sroberto 1682498Sroberto#include <stdio.h> 1782498Sroberto#include <ctype.h> 1882498Sroberto 1954359Sroberto/* 2054359Sroberto * This driver supports the PSTI 1010 and Traconex 1020 WWV/WWVH 2154359Sroberto * Receivers. No specific claim of accuracy is made for these receiver, 2254359Sroberto * but actual experience suggests that 10 ms would be a conservative 2354359Sroberto * assumption. 2454359Sroberto * 2554359Sroberto * The DIPswitches should be set for 9600 bps line speed, 24-hour day- 2654359Sroberto * of-year format and UTC time zone. Automatic correction for DST should 2754359Sroberto * be disabled. It is very important that the year be set correctly in 2854359Sroberto * the DIPswitches; otherwise, the day of year will be incorrect after 2954359Sroberto * 28 April of a normal or leap year. The propagation delay DIPswitches 3054359Sroberto * should be set according to the distance from the transmitter for both 3154359Sroberto * WWV and WWVH, as described in the instructions. While the delay can 3254359Sroberto * be set only to within 11 ms, the fudge time1 parameter can be used 3354359Sroberto * for vernier corrections. 3454359Sroberto * 3554359Sroberto * Using the poll sequence QTQDQM, the response timecode is in three 3654359Sroberto * sections totalling 50 ASCII printing characters, as concatenated by 3754359Sroberto * the driver, in the following format: 3854359Sroberto * 3954359Sroberto * ahh:mm:ss.fffs<cr> yy/dd/mm/ddd<cr> frdzycchhSSFTttttuuxx<cr> 4054359Sroberto * 4154359Sroberto * on-time = first <cr> 4254359Sroberto * hh:mm:ss.fff = hours, minutes, seconds, milliseconds 4354359Sroberto * a = AM/PM indicator (' ' for 24-hour mode) 4454359Sroberto * yy = year (from internal switches) 4554359Sroberto * dd/mm/ddd = day of month, month, day of year 4654359Sroberto * s = daylight-saving indicator (' ' for 24-hour mode) 4754359Sroberto * f = frequency enable (O = all frequencies enabled) 4854359Sroberto * r = baud rate (3 = 1200, 6 = 9600) 4954359Sroberto * d = features indicator (@ = month/day display enabled) 5054359Sroberto * z = time zone (0 = UTC) 5154359Sroberto * y = year (5 = 91) 5254359Sroberto * cc = WWV propagation delay (52 = 22 ms) 5354359Sroberto * hh = WWVH propagation delay (81 = 33 ms) 5454359Sroberto * SS = status (80 or 82 = operating correctly) 5554359Sroberto * F = current receive frequency (4 = 15 MHz) 5654359Sroberto * T = transmitter (C = WWV, H = WWVH) 5754359Sroberto * tttt = time since last update (0000 = minutes) 5854359Sroberto * uu = flush character (03 = ^c) 5954359Sroberto * xx = 94 (unknown) 6054359Sroberto * 6154359Sroberto * The alarm condition is indicated by other than '8' at A, which occurs 6254359Sroberto * during initial synchronization and when received signal is lost for 6354359Sroberto * an extended period; unlock condition is indicated by other than 6454359Sroberto * "0000" in the tttt subfield at Q. 6554359Sroberto * 6654359Sroberto * Fudge Factors 6754359Sroberto * 6854359Sroberto * There are no special fudge factors other than the generic. 6954359Sroberto */ 7054359Sroberto 7154359Sroberto/* 7254359Sroberto * Interface definitions 7354359Sroberto */ 74132451Sroberto#define DEVICE "/dev/wwv%d" /* device name and unit */ 7554359Sroberto#define SPEED232 B9600 /* uart speed (9600 baud) */ 7654359Sroberto#define PRECISION (-10) /* precision assumed (about 1 ms) */ 7754359Sroberto#define WWVREFID "WWV\0" /* WWV reference ID */ 7854359Sroberto#define WWVHREFID "WWVH" /* WWVH reference ID */ 7954359Sroberto#define DESCRIPTION "PSTI/Traconex WWV/WWVH Receiver" /* WRU */ 8054359Sroberto#define PST_PHI (10e-6) /* max clock oscillator offset */ 8154359Sroberto#define LENPST 46 /* min timecode length */ 8254359Sroberto 8354359Sroberto/* 8454359Sroberto * Unit control structure 8554359Sroberto */ 8654359Srobertostruct pstunit { 87132451Sroberto int tcswitch; /* timecode switch */ 8854359Sroberto char *lastptr; /* pointer to timecode data */ 8954359Sroberto}; 9054359Sroberto 9154359Sroberto/* 9254359Sroberto * Function prototypes 9354359Sroberto */ 94285612Sdelphijstatic int pst_start (int, struct peer *); 95285612Sdelphijstatic void pst_shutdown (int, struct peer *); 96285612Sdelphijstatic void pst_receive (struct recvbuf *); 97285612Sdelphijstatic void pst_poll (int, struct peer *); 9854359Sroberto 9954359Sroberto/* 10054359Sroberto * Transfer vector 10154359Sroberto */ 10254359Srobertostruct refclock refclock_pst = { 10354359Sroberto pst_start, /* start up driver */ 10454359Sroberto pst_shutdown, /* shut down driver */ 10554359Sroberto pst_poll, /* transmit poll message */ 10654359Sroberto noentry, /* not used (old pst_control) */ 10754359Sroberto noentry, /* initialize driver */ 10854359Sroberto noentry, /* not used (old pst_buginfo) */ 10954359Sroberto NOFLAGS /* not used */ 11054359Sroberto}; 11154359Sroberto 11254359Sroberto 11354359Sroberto/* 11454359Sroberto * pst_start - open the devices and initialize data for processing 11554359Sroberto */ 11654359Srobertostatic int 11754359Srobertopst_start( 11854359Sroberto int unit, 11954359Sroberto struct peer *peer 12054359Sroberto ) 12154359Sroberto{ 12254359Sroberto register struct pstunit *up; 12354359Sroberto struct refclockproc *pp; 12454359Sroberto int fd; 12554359Sroberto char device[20]; 12654359Sroberto 12754359Sroberto /* 12854359Sroberto * Open serial port. Use CLK line discipline, if available. 12954359Sroberto */ 130285612Sdelphij snprintf(device, sizeof(device), DEVICE, unit); 131285612Sdelphij fd = refclock_open(device, SPEED232, LDISC_CLK); 132285612Sdelphij if (fd <= 0) 13354359Sroberto return (0); 13454359Sroberto 13554359Sroberto /* 13654359Sroberto * Allocate and initialize unit structure 13754359Sroberto */ 138285612Sdelphij up = emalloc_zero(sizeof(*up)); 13954359Sroberto pp = peer->procptr; 14054359Sroberto pp->io.clock_recv = pst_receive; 141285612Sdelphij pp->io.srcclock = peer; 14254359Sroberto pp->io.datalen = 0; 14354359Sroberto pp->io.fd = fd; 14454359Sroberto if (!io_addclock(&pp->io)) { 145285612Sdelphij close(fd); 146285612Sdelphij pp->io.fd = -1; 14754359Sroberto free(up); 14854359Sroberto return (0); 14954359Sroberto } 150285612Sdelphij pp->unitptr = up; 15154359Sroberto 15254359Sroberto /* 15354359Sroberto * Initialize miscellaneous variables 15454359Sroberto */ 15554359Sroberto peer->precision = PRECISION; 15654359Sroberto pp->clockdesc = DESCRIPTION; 15754359Sroberto memcpy((char *)&pp->refid, WWVREFID, 4); 15854359Sroberto return (1); 15954359Sroberto} 16054359Sroberto 16154359Sroberto 16254359Sroberto/* 16354359Sroberto * pst_shutdown - shut down the clock 16454359Sroberto */ 16554359Srobertostatic void 16654359Srobertopst_shutdown( 16754359Sroberto int unit, 16854359Sroberto struct peer *peer 16954359Sroberto ) 17054359Sroberto{ 17154359Sroberto register struct pstunit *up; 17254359Sroberto struct refclockproc *pp; 17354359Sroberto 17454359Sroberto pp = peer->procptr; 175285612Sdelphij up = pp->unitptr; 176285612Sdelphij if (-1 != pp->io.fd) 177285612Sdelphij io_closeclock(&pp->io); 178285612Sdelphij if (NULL != up) 179285612Sdelphij free(up); 18054359Sroberto} 18154359Sroberto 18254359Sroberto 18354359Sroberto/* 18454359Sroberto * pst_receive - receive data from the serial interface 18554359Sroberto */ 18654359Srobertostatic void 18754359Srobertopst_receive( 18854359Sroberto struct recvbuf *rbufp 18954359Sroberto ) 19054359Sroberto{ 19154359Sroberto register struct pstunit *up; 19254359Sroberto struct refclockproc *pp; 19354359Sroberto struct peer *peer; 19454359Sroberto l_fp trtmp; 19554359Sroberto u_long ltemp; 19654359Sroberto char ampmchar; /* AM/PM indicator */ 19754359Sroberto char daychar; /* standard/daylight indicator */ 19854359Sroberto char junque[10]; /* "yy/dd/mm/" discard */ 19954359Sroberto char info[14]; /* "frdzycchhSSFT" clock info */ 20054359Sroberto 20154359Sroberto /* 20254359Sroberto * Initialize pointers and read the timecode and timestamp 20354359Sroberto */ 204285612Sdelphij peer = rbufp->recv_peer; 20554359Sroberto pp = peer->procptr; 206285612Sdelphij up = pp->unitptr; 20754359Sroberto up->lastptr += refclock_gtlin(rbufp, up->lastptr, pp->a_lastcode 20854359Sroberto + BMAX - 2 - up->lastptr, &trtmp); 20954359Sroberto *up->lastptr++ = ' '; 21054359Sroberto *up->lastptr = '\0'; 21154359Sroberto 21254359Sroberto /* 21354359Sroberto * Note we get a buffer and timestamp for each <cr>, but only 21454359Sroberto * the first timestamp is retained. 21554359Sroberto */ 216132451Sroberto if (up->tcswitch == 0) 21754359Sroberto pp->lastrec = trtmp; 21854359Sroberto up->tcswitch++; 21954359Sroberto pp->lencode = up->lastptr - pp->a_lastcode; 22054359Sroberto if (up->tcswitch < 3) 22154359Sroberto return; 22254359Sroberto 22354359Sroberto /* 22454359Sroberto * We get down to business, check the timecode format and decode 22554359Sroberto * its contents. If the timecode has invalid length or is not in 22654359Sroberto * proper format, we declare bad format and exit. 22754359Sroberto */ 22854359Sroberto if (pp->lencode < LENPST) { 22954359Sroberto refclock_report(peer, CEVNT_BADREPLY); 23054359Sroberto return; 23154359Sroberto } 23254359Sroberto 23354359Sroberto /* 23454359Sroberto * Timecode format: 23554359Sroberto * "ahh:mm:ss.fffs yy/dd/mm/ddd frdzycchhSSFTttttuuxx" 23654359Sroberto */ 237132451Sroberto if (sscanf(pp->a_lastcode, 238132451Sroberto "%c%2d:%2d:%2d.%3ld%c %9s%3d%13s%4ld", 239132451Sroberto &mchar, &pp->hour, &pp->minute, &pp->second, &pp->nsec, 240132451Sroberto &daychar, junque, &pp->day, info, <emp) != 10) { 24154359Sroberto refclock_report(peer, CEVNT_BADREPLY); 24254359Sroberto return; 24354359Sroberto } 244132451Sroberto pp->nsec *= 1000000; 24554359Sroberto 24654359Sroberto /* 24754359Sroberto * Decode synchronization, quality and last update. If 24854359Sroberto * unsynchronized, set the leap bits accordingly and exit. Once 24954359Sroberto * synchronized, the dispersion depends only on when the clock 25054359Sroberto * was last heard, which depends on the time since last update, 25154359Sroberto * as reported by the clock. 25254359Sroberto */ 25354359Sroberto if (info[9] != '8') 25454359Sroberto pp->leap = LEAP_NOTINSYNC; 25554359Sroberto if (info[12] == 'H') 25654359Sroberto memcpy((char *)&pp->refid, WWVHREFID, 4); 25754359Sroberto else 25854359Sroberto memcpy((char *)&pp->refid, WWVREFID, 4); 25954359Sroberto if (peer->stratum <= 1) 26054359Sroberto peer->refid = pp->refid; 261132451Sroberto if (ltemp == 0) 262132451Sroberto pp->lastref = pp->lastrec; 263132451Sroberto pp->disp = PST_PHI * ltemp * 60; 26454359Sroberto 26554359Sroberto /* 26654359Sroberto * Process the new sample in the median filter and determine the 26754359Sroberto * timecode timestamp. 26854359Sroberto */ 26954359Sroberto if (!refclock_process(pp)) 27054359Sroberto refclock_report(peer, CEVNT_BADTIME); 271182007Sroberto else if (peer->disp > MAXDISTANCE) 272182007Sroberto refclock_receive(peer); 27354359Sroberto} 27454359Sroberto 27554359Sroberto 27654359Sroberto/* 27754359Sroberto * pst_poll - called by the transmit procedure 27854359Sroberto */ 27954359Srobertostatic void 28054359Srobertopst_poll( 28154359Sroberto int unit, 28254359Sroberto struct peer *peer 28354359Sroberto ) 28454359Sroberto{ 28554359Sroberto register struct pstunit *up; 28654359Sroberto struct refclockproc *pp; 28754359Sroberto 28854359Sroberto /* 28954359Sroberto * Time to poll the clock. The PSTI/Traconex clock responds to a 29054359Sroberto * "QTQDQMT" by returning a timecode in the format specified 291132451Sroberto * above. Note there is no checking on state, since this may not 292132451Sroberto * be the only customer reading the clock. Only one customer 293132451Sroberto * need poll the clock; all others just listen in. If the clock 294132451Sroberto * becomes unreachable, declare a timeout and keep going. 29554359Sroberto */ 29654359Sroberto pp = peer->procptr; 297285612Sdelphij up = pp->unitptr; 29854359Sroberto up->tcswitch = 0; 29954359Sroberto up->lastptr = pp->a_lastcode; 30054359Sroberto if (write(pp->io.fd, "QTQDQMT", 6) != 6) 30154359Sroberto refclock_report(peer, CEVNT_FAULT); 30254359Sroberto if (pp->coderecv == pp->codeproc) { 30354359Sroberto refclock_report(peer, CEVNT_TIMEOUT); 30454359Sroberto return; 30554359Sroberto } 306132451Sroberto refclock_receive(peer); 30754359Sroberto record_clock_stats(&peer->srcadr, pp->a_lastcode); 308132451Sroberto#ifdef DEBUG 309132451Sroberto if (debug) 310132451Sroberto printf("pst: timecode %d %s\n", pp->lencode, 311132451Sroberto pp->a_lastcode); 312132451Sroberto#endif 313132451Sroberto pp->polls++; 31454359Sroberto} 31554359Sroberto 31654359Sroberto#else 31754359Srobertoint refclock_pst_int; 31854359Sroberto#endif /* REFCLOCK */ 319