4 * refclock_zyfer - clock driver for the Zyfer GPSTarplus Clock
6 * Harlan Stenn, Jan 2002
13 #if defined(REFCLOCK) && defined(CLOCK_ZYFER)
17 #include "ntp_refclock.h"
18 #include "ntp_stdlib.h"
19 #include "ntp_unixtime.h"
24 #ifdef HAVE_SYS_TERMIOS_H
25 # include <sys/termios.h>
27 #ifdef HAVE_SYS_PPSCLOCK_H
28 # include <sys/ppsclock.h>
32 * This driver provides support for the TOD serial port of a Zyfer GPStarplus.
33 * This clock also provides PPS as well as IRIG outputs.
34 * Precision is limited by the serial driver, etc.
36 * If I was really brave I'd hack/generalize the serial driver to deal
37 * with arbitrary on-time characters. This clock *begins* the stream with
38 * `!`, the on-time character, and the string is *not* EOL-terminated.
40 * Configure the beast for 9600, 8N1. While I see leap-second stuff
41 * in the documentation, the published specs on the TOD format only show
42 * the seconds going to '59'. I see no leap warning in the TOD format.
44 * The clock sends the following message once per second:
46 * !TIME,2002,017,07,59,32,2,4,1
47 * YYYY DDD HH MM SS m T O
51 * DDD 001-366 Day of Year
54 * SS 00-59 Second (probably 00-60)
58 * 3 = LGPS time (Local GPS)
59 * 4 = LUTC time (Local UTC)
61 * T 4-9 Time Figure Of Merit:
68 * O 0-4 Operation Mode:
78 * Interface definitions
80 #define DEVICE "/dev/zyfer%d" /* device name and unit */
81 #define SPEED232 B9600 /* uart speed (9600 baud) */
82 #define PRECISION (-20) /* precision assumed (about 1 us) */
83 #define REFID "GPS\0" /* reference ID */
84 #define DESCRIPTION "Zyfer GPStarplus" /* WRU */
86 #define LENZYFER 29 /* timecode length */
89 * Unit control structure
92 u_char Rcvbuf
[LENZYFER
+ 1];
93 u_char polled
; /* poll message flag */
95 l_fp tstamp
; /* timestamp of last poll */
100 * Function prototypes
102 static int zyfer_start
P((int, struct peer
*));
103 static void zyfer_shutdown
P((int, struct peer
*));
104 static void zyfer_receive
P((struct recvbuf
*));
105 static void zyfer_poll
P((int, struct peer
*));
110 struct refclock refclock_zyfer
= {
111 zyfer_start
, /* start up driver */
112 zyfer_shutdown
, /* shut down driver */
113 zyfer_poll
, /* transmit poll message */
114 noentry
, /* not used (old zyfer_control) */
115 noentry
, /* initialize driver (not used) */
116 noentry
, /* not used (old zyfer_buginfo) */
117 NOFLAGS
/* not used */
122 * zyfer_start - open the devices and initialize data for processing
130 register struct zyferunit
*up
;
131 struct refclockproc
*pp
;
137 * Something like LDISC_ACTS that looked for ! would be nice...
139 (void)sprintf(device
, DEVICE
, unit
);
140 if ( !(fd
= refclock_open(device
, SPEED232
, LDISC_RAW
)) )
143 msyslog(LOG_NOTICE
, "zyfer(%d) fd: %d dev <%s>", unit
, fd
, device
);
146 * Allocate and initialize unit structure
148 if (!(up
= (struct zyferunit
*)
149 emalloc(sizeof(struct zyferunit
)))) {
153 memset((char *)up
, 0, sizeof(struct zyferunit
));
155 pp
->io
.clock_recv
= zyfer_receive
;
156 pp
->io
.srcclock
= (caddr_t
)peer
;
159 if (!io_addclock(&pp
->io
)) {
164 pp
->unitptr
= (caddr_t
)up
;
167 * Initialize miscellaneous variables
169 peer
->precision
= PRECISION
;
170 pp
->clockdesc
= DESCRIPTION
;
171 memcpy((char *)&pp
->refid
, REFID
, 4);
173 up
->polled
= 0; /* May not be needed... */
180 * zyfer_shutdown - shut down the clock
188 register struct zyferunit
*up
;
189 struct refclockproc
*pp
;
192 up
= (struct zyferunit
*)pp
->unitptr
;
193 io_closeclock(&pp
->io
);
199 * zyfer_receive - receive data from the serial interface
203 struct recvbuf
*rbufp
206 register struct zyferunit
*up
;
207 struct refclockproc
*pp
;
209 int tmode
; /* Time mode */
210 int tfom
; /* Time Figure Of Merit */
211 int omode
; /* Operation mode */
214 struct ppsclockev ppsev
;
219 #ifdef HAVE_TIOCGPPSEV
220 request
= TIOCGPPSEV
;
224 peer
= (struct peer
*)rbufp
->recv_srcclock
;
226 up
= (struct zyferunit
*)pp
->unitptr
;
227 p
= (u_char
*) &rbufp
->recv_space
;
230 * - if *rbufp->recv_space is !
231 * - - call refclock_gtlin to get things going
233 * else stuff it on the end of lastcode
234 * If we don't have LENZYFER bytes
235 * - wait for more data
236 * Crack the beast, and if it's OK, process it.
238 * We use refclock_gtlin() because we might use LDISC_CLK.
240 * Under FreeBSD, we get the ! followed by two 14-byte packets.
243 if (pp
->lencode
>= LENZYFER
)
248 pp
->lencode
= refclock_gtlin(rbufp
, pp
->a_lastcode
,
253 memcpy(pp
->a_lastcode
+ pp
->lencode
, p
, rbufp
->recv_length
);
254 pp
->lencode
+= rbufp
->recv_length
;
255 pp
->a_lastcode
[pp
->lencode
] = '\0';
258 if (pp
->lencode
< LENZYFER
)
261 record_clock_stats(&peer
->srcadr
, pp
->a_lastcode
);
264 * We get down to business, check the timecode format and decode
265 * its contents. If the timecode has invalid length or is not in
266 * proper format, we declare bad format and exit.
269 if (pp
->lencode
!= LENZYFER
) {
270 refclock_report(peer
, CEVNT_BADTIME
);
275 * Timecode sample: "!TIME,2002,017,07,59,32,2,4,1"
277 if (sscanf(pp
->a_lastcode
, "!TIME,%4d,%3d,%2d,%2d,%2d,%d,%d,%d",
278 &pp
->year
, &pp
->day
, &pp
->hour
, &pp
->minute
, &pp
->second
,
279 &tmode
, &tfom
, &omode
) != 8) {
280 refclock_report(peer
, CEVNT_BADREPLY
);
285 refclock_report(peer
, CEVNT_BADTIME
);
289 /* Should we make sure tfom is 4? */
292 pp
->leap
= LEAP_NOTINSYNC
;
296 if(ioctl(fdpps
,request
,(caddr_t
) &ppsev
) >=0) {
297 ppsev
.tv
.tv_sec
+= (u_int32
) JAN_1970
;
298 TVTOTS(&ppsev
.tv
,&up
->tstamp
);
300 /* record the last ppsclock event time stamp */
301 pp
->lastrec
= up
->tstamp
;
303 if (!refclock_process(pp
)) {
304 refclock_report(peer
, CEVNT_BADTIME
);
309 * Good place for record_clock_stats()
315 refclock_receive(peer
);
321 * zyfer_poll - called by the transmit procedure
329 register struct zyferunit
*up
;
330 struct refclockproc
*pp
;
333 * We don't really do anything here, except arm the receiving
334 * side to capture a sample and check for timeouts.
337 up
= (struct zyferunit
*)pp
->unitptr
;
339 refclock_report(peer
, CEVNT_TIMEOUT
);
347 int refclock_zyfer_bs
;
348 #endif /* REFCLOCK */