2
* refclock_zyfer - clock driver for the Zyfer GPSTarplus Clock
4
* Harlan Stenn, Jan 2002
11
#if defined(REFCLOCK) && defined(CLOCK_ZYFER)
15
#include "ntp_refclock.h"
16
#include "ntp_stdlib.h"
17
#include "ntp_unixtime.h"
22
#ifdef HAVE_SYS_TERMIOS_H
23
# include <sys/termios.h>
25
#ifdef HAVE_SYS_PPSCLOCK_H
26
# include <sys/ppsclock.h>
30
* This driver provides support for the TOD serial port of a Zyfer GPStarplus.
31
* This clock also provides PPS as well as IRIG outputs.
32
* Precision is limited by the serial driver, etc.
34
* If I was really brave I'd hack/generalize the serial driver to deal
35
* with arbitrary on-time characters. This clock *begins* the stream with
36
* `!`, the on-time character, and the string is *not* EOL-terminated.
38
* Configure the beast for 9600, 8N1. While I see leap-second stuff
39
* in the documentation, the published specs on the TOD format only show
40
* the seconds going to '59'. I see no leap warning in the TOD format.
42
* The clock sends the following message once per second:
44
* !TIME,2002,017,07,59,32,2,4,1
45
* YYYY DDD HH MM SS m T O
49
* DDD 001-366 Day of Year
52
* SS 00-59 Second (probably 00-60)
56
* 3 = LGPS time (Local GPS)
57
* 4 = LUTC time (Local UTC)
59
* T 4-9 Time Figure Of Merit:
66
* O 0-4 Operation Mode:
76
* Interface definitions
78
#define DEVICE "/dev/zyfer%d" /* device name and unit */
79
#define SPEED232 B9600 /* uart speed (9600 baud) */
80
#define PRECISION (-20) /* precision assumed (about 1 us) */
81
#define REFID "GPS\0" /* reference ID */
82
#define DESCRIPTION "Zyfer GPStarplus" /* WRU */
84
#define LENZYFER 29 /* timecode length */
87
* Unit control structure
90
u_char Rcvbuf[LENZYFER + 1];
91
u_char polled; /* poll message flag */
93
l_fp tstamp; /* timestamp of last poll */
100
static int zyfer_start P((int, struct peer *));
101
static void zyfer_shutdown P((int, struct peer *));
102
static void zyfer_receive P((struct recvbuf *));
103
static void zyfer_poll P((int, struct peer *));
108
struct refclock refclock_zyfer = {
109
zyfer_start, /* start up driver */
110
zyfer_shutdown, /* shut down driver */
111
zyfer_poll, /* transmit poll message */
112
noentry, /* not used (old zyfer_control) */
113
noentry, /* initialize driver (not used) */
114
noentry, /* not used (old zyfer_buginfo) */
115
NOFLAGS /* not used */
120
* zyfer_start - open the devices and initialize data for processing
128
register struct zyferunit *up;
129
struct refclockproc *pp;
135
* Something like LDISC_ACTS that looked for ! would be nice...
137
(void)sprintf(device, DEVICE, unit);
138
if ( !(fd = refclock_open(device, SPEED232, LDISC_RAW)) )
141
msyslog(LOG_NOTICE, "zyfer(%d) fd: %d dev <%s>", unit, fd, device);
144
* Allocate and initialize unit structure
146
if (!(up = (struct zyferunit *)
147
emalloc(sizeof(struct zyferunit)))) {
151
memset((char *)up, 0, sizeof(struct zyferunit));
153
pp->io.clock_recv = zyfer_receive;
154
pp->io.srcclock = (caddr_t)peer;
157
if (!io_addclock(&pp->io)) {
162
pp->unitptr = (caddr_t)up;
165
* Initialize miscellaneous variables
167
peer->precision = PRECISION;
168
pp->clockdesc = DESCRIPTION;
169
memcpy((char *)&pp->refid, REFID, 4);
171
up->polled = 0; /* May not be needed... */
178
* zyfer_shutdown - shut down the clock
186
register struct zyferunit *up;
187
struct refclockproc *pp;
190
up = (struct zyferunit *)pp->unitptr;
191
io_closeclock(&pp->io);
197
* zyfer_receive - receive data from the serial interface
201
struct recvbuf *rbufp
204
register struct zyferunit *up;
205
struct refclockproc *pp;
207
int tmode; /* Time mode */
208
int tfom; /* Time Figure Of Merit */
209
int omode; /* Operation mode */
212
struct ppsclockev ppsev;
217
#ifdef HAVE_TIOCGPPSEV
218
request = TIOCGPPSEV;
222
peer = (struct peer *)rbufp->recv_srcclock;
224
up = (struct zyferunit *)pp->unitptr;
225
p = (u_char *) &rbufp->recv_space;
228
* - if *rbufp->recv_space is !
229
* - - call refclock_gtlin to get things going
231
* else stuff it on the end of lastcode
232
* If we don't have LENZYFER bytes
233
* - wait for more data
234
* Crack the beast, and if it's OK, process it.
236
* We use refclock_gtlin() because we might use LDISC_CLK.
238
* Under FreeBSD, we get the ! followed by two 14-byte packets.
241
if (pp->lencode >= LENZYFER)
246
pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode,
251
memcpy(pp->a_lastcode + pp->lencode, p, rbufp->recv_length);
252
pp->lencode += rbufp->recv_length;
253
pp->a_lastcode[pp->lencode] = '\0';
256
if (pp->lencode < LENZYFER)
259
record_clock_stats(&peer->srcadr, pp->a_lastcode);
262
* We get down to business, check the timecode format and decode
263
* its contents. If the timecode has invalid length or is not in
264
* proper format, we declare bad format and exit.
267
if (pp->lencode != LENZYFER) {
268
refclock_report(peer, CEVNT_BADTIME);
273
* Timecode sample: "!TIME,2002,017,07,59,32,2,4,1"
275
if (sscanf(pp->a_lastcode, "!TIME,%4d,%3d,%2d,%2d,%2d,%d,%d,%d",
276
&pp->year, &pp->day, &pp->hour, &pp->minute, &pp->second,
277
&tmode, &tfom, &omode) != 8) {
278
refclock_report(peer, CEVNT_BADREPLY);
283
refclock_report(peer, CEVNT_BADTIME);
287
/* Should we make sure tfom is 4? */
290
pp->leap = LEAP_NOTINSYNC;
294
if(ioctl(fdpps,request,(caddr_t) &ppsev) >=0) {
295
ppsev.tv.tv_sec += (u_int32) JAN_1970;
296
TVTOTS(&ppsev.tv,&up->tstamp);
298
/* record the last ppsclock event time stamp */
299
pp->lastrec = up->tstamp;
301
if (!refclock_process(pp)) {
302
refclock_report(peer, CEVNT_BADTIME);
307
* Good place for record_clock_stats()
313
refclock_receive(peer);
319
* zyfer_poll - called by the transmit procedure
327
register struct zyferunit *up;
328
struct refclockproc *pp;
331
* We don't really do anything here, except arm the receiving
332
* side to capture a sample and check for timeouts.
335
up = (struct zyferunit *)pp->unitptr;
337
refclock_report(peer, CEVNT_TIMEOUT);
345
int refclock_zyfer_bs;
346
#endif /* REFCLOCK */