refclock_ulink.c revision 290001
1130803Smarcel/*
2130803Smarcel * refclock_ulink - clock driver for Ultralink  WWVB receiver
3130803Smarcel */
4130803Smarcel
5130803Smarcel#ifdef HAVE_CONFIG_H
6130803Smarcel#include <config.h>
7130803Smarcel#endif
8130803Smarcel
9130803Smarcel#if defined(REFCLOCK) && defined(CLOCK_ULINK)
10130803Smarcel
11130803Smarcel#include <stdio.h>
12130803Smarcel#include <ctype.h>
13130803Smarcel
14130803Smarcel#include "ntpd.h"
15130803Smarcel#include "ntp_io.h"
16130803Smarcel#include "ntp_refclock.h"
17130803Smarcel#include "ntp_stdlib.h"
18130803Smarcel
19130803Smarcel/* This driver supports ultralink Model 320,325,330,331,332 WWVB radios
20130803Smarcel *
21130803Smarcel * this driver was based on the refclock_wwvb.c driver
22130803Smarcel * in the ntp distribution.
23130803Smarcel *
24130803Smarcel * Fudge Factors
25130803Smarcel *
26130803Smarcel * fudge flag1 0 don't poll clock
27130803Smarcel *             1 send poll character
28130803Smarcel *
29130803Smarcel * revision history:
30130803Smarcel *		99/9/09 j.c.lang	original edit's
31130803Smarcel *		99/9/11 j.c.lang	changed timecode parse to
32130803Smarcel *                                      match what the radio actually
33130803Smarcel *                                      sends.
34130803Smarcel *              99/10/11 j.c.lang       added support for continous
35130803Smarcel *                                      time code mode (dipsw2)
36130803Smarcel *		99/11/26 j.c.lang	added support for 320 decoder
37130803Smarcel *                                      (taken from Dave Strout's
38130803Smarcel *                                      Model 320 driver)
39130803Smarcel *		99/11/29 j.c.lang	added fudge flag 1 to control
40130803Smarcel *					clock polling
41130803Smarcel *		99/12/15 j.c.lang	fixed 320 quality flag
42130803Smarcel *		01/02/21 s.l.smith	fixed 33x quality flag
43130803Smarcel *					added more debugging stuff
44130803Smarcel *					updated 33x time code explanation
45130803Smarcel *		04/01/23 frank migge	added support for 325 decoder
46130803Smarcel *                                      (tested with ULM325.F)
47130803Smarcel *
48130803Smarcel * Questions, bugs, ideas send to:
49130803Smarcel *	Joseph C. Lang
50130803Smarcel *	tcnojl1@earthlink.net
51130803Smarcel *
52130803Smarcel *	Dave Strout
53130803Smarcel *	dstrout@linuxfoundry.com
54130803Smarcel *
55130803Smarcel *      Frank Migge
56130803Smarcel *      frank.migge@oracle.com
57130803Smarcel *
58130803Smarcel *
59130803Smarcel * on the Ultralink model 33X decoder Dip switch 2 controls
60130803Smarcel * polled or continous timecode
61130803Smarcel * set fudge flag1 if using polled (needed for model 320 and 325)
62130803Smarcel * dont set fudge flag1 if dip switch 2 is set on model 33x decoder
63130803Smarcel*/
64130803Smarcel
65130803Smarcel
66130803Smarcel/*
67130803Smarcel * Interface definitions
68130803Smarcel */
69130803Smarcel#define	DEVICE		"/dev/wwvb%d" /* device name and unit */
70130803Smarcel#define	SPEED232	B9600	/* uart speed (9600 baud) */
71130803Smarcel#define	PRECISION	(-10)	/* precision assumed (about 10 ms) */
72130803Smarcel#define	REFID		"WWVB"	/* reference ID */
73130803Smarcel#define	DESCRIPTION	"Ultralink WWVB Receiver" /* WRU */
74130803Smarcel
75130803Smarcel#define	LEN33X		32	/* timecode length Model 33X and 325 */
76130803Smarcel#define LEN320		24	/* timecode length Model 320 */
77130803Smarcel
78130803Smarcel#define	SIGLCHAR33x	'S'	/* signal strength identifier char 325 */
79130803Smarcel#define	SIGLCHAR325	'R'	/* signal strength identifier char 33x */
80130803Smarcel
81130803Smarcel/*
82130803Smarcel *  unit control structure
83130803Smarcel */
84130803Smarcelstruct ulinkunit {
85130803Smarcel	u_char	tcswitch;	/* timecode switch */
86130803Smarcel	l_fp	laststamp;	/* last receive timestamp */
87130803Smarcel};
88130803Smarcel
89130803Smarcel/*
90130803Smarcel * Function prototypes
91 */
92static	int	ulink_start	(int, struct peer *);
93static	void	ulink_shutdown	(int, struct peer *);
94static	void	ulink_receive	(struct recvbuf *);
95static	void	ulink_poll	(int, struct peer *);
96
97/*
98 * Transfer vector
99 */
100struct	refclock refclock_ulink = {
101	ulink_start,		/* start up driver */
102	ulink_shutdown,		/* shut down driver */
103	ulink_poll,		/* transmit poll message */
104	noentry,		/* not used  */
105	noentry,		/* not used  */
106	noentry,		/* not used  */
107	NOFLAGS
108};
109
110
111/*
112 * ulink_start - open the devices and initialize data for processing
113 */
114static int
115ulink_start(
116	int unit,
117	struct peer *peer
118	)
119{
120	register struct ulinkunit *up;
121	struct refclockproc *pp;
122	int fd;
123	char device[20];
124
125	/*
126	 * Open serial port. Use CLK line discipline, if available.
127	 */
128	snprintf(device, sizeof(device), DEVICE, unit);
129	fd = refclock_open(device, SPEED232, LDISC_CLK);
130	if (fd <= 0)
131		return (0);
132
133	/*
134	 * Allocate and initialize unit structure
135	 */
136	up = emalloc(sizeof(struct ulinkunit));
137	memset(up, 0, sizeof(struct ulinkunit));
138	pp = peer->procptr;
139	pp->io.clock_recv = ulink_receive;
140	pp->io.srcclock = peer;
141	pp->io.datalen = 0;
142	pp->io.fd = fd;
143	if (!io_addclock(&pp->io)) {
144		close(fd);
145		pp->io.fd = -1;
146		free(up);
147		return (0);
148	}
149	pp->unitptr = up;
150
151	/*
152	 * Initialize miscellaneous variables
153	 */
154	peer->precision = PRECISION;
155	pp->clockdesc = DESCRIPTION;
156	memcpy((char *)&pp->refid, REFID, 4);
157	return (1);
158}
159
160
161/*
162 * ulink_shutdown - shut down the clock
163 */
164static void
165ulink_shutdown(
166	int unit,
167	struct peer *peer
168	)
169{
170	register struct ulinkunit *up;
171	struct refclockproc *pp;
172
173	pp = peer->procptr;
174	up = pp->unitptr;
175	if (pp->io.fd != -1)
176		io_closeclock(&pp->io);
177	if (up != NULL)
178		free(up);
179}
180
181
182/*
183 * ulink_receive - receive data from the serial interface
184 */
185static void
186ulink_receive(
187	struct recvbuf *rbufp
188	)
189{
190	struct ulinkunit *up;
191	struct refclockproc *pp;
192	struct peer *peer;
193
194	l_fp	trtmp;			/* arrival timestamp */
195	int	quality = INT_MAX;	/* quality indicator */
196	int	temp;			/* int temp */
197	char	syncchar;		/* synchronization indicator */
198	char	leapchar;		/* leap indicator */
199	char	modechar;		/* model 320 mode flag */
200        char	siglchar;		/* model difference between 33x/325 */
201	char	char_quality[2];	/* temp quality flag */
202
203	/*
204	 * Initialize pointers and read the timecode and timestamp
205	 */
206	peer = rbufp->recv_peer;
207	pp = peer->procptr;
208	up = pp->unitptr;
209	temp = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp);
210
211	/*
212	 * Note we get a buffer and timestamp for both a <cr> and <lf>,
213	 * but only the <cr> timestamp is retained.
214	 */
215	if (temp == 0) {
216		if (up->tcswitch == 0) {
217			up->tcswitch = 1;
218			up->laststamp = trtmp;
219		} else
220		    up->tcswitch = 0;
221		return;
222	}
223	pp->lencode = temp;
224	pp->lastrec = up->laststamp;
225	up->laststamp = trtmp;
226	up->tcswitch = 1;
227#ifdef DEBUG
228	if (debug)
229		printf("ulink: timecode %d %s\n", pp->lencode,
230		    pp->a_lastcode);
231#endif
232
233	/*
234	 * We get down to business, check the timecode format and decode
235	 * its contents. If the timecode has invalid length or is not in
236	 * proper format, we declare bad format and exit.
237	 */
238	syncchar = leapchar = modechar = siglchar = ' ';
239	switch (pp->lencode ) {
240		case LEN33X:
241
242		/*
243                 * First we check if the format is 33x or 325:
244		 *   <CR><LF>S9+D 00 YYYY+DDDUTCS HH:MM:SSL+5 (33x)
245		 *   <CR><LF>R5_1C00LYYYY+DDDUTCS HH:MM:SSL+5 (325)
246		 * simply by comparing if the signal level is 'S' or 'R'
247                 */
248
249                 if (sscanf(pp->a_lastcode, "%c%*31c",
250                            &siglchar) == 1) {
251
252                    if(siglchar == SIGLCHAR325) {
253
254       		   /*
255		    * decode for a Model 325 decoder.
256		    * Timecode format from January 23, 2004 datasheet is:
257                    *
258		    *   <CR><LF>R5_1C00LYYYY+DDDUTCS HH:MM:SSL+5
259                    *
260		    *   R      WWVB decodersignal readability R1 - R5
261		    *   5      R1 is unreadable, R5 is best
262		    *   space  a space (0x20)
263		    *   1      Data bit 0, 1, M (pos mark), or ? (unknown).
264		    *   C      Reception from either (C)olorado or (H)awaii
265		    *   00     Hours since last good WWVB frame sync. Will
266		    *          be 00-99
267		    *   space  Space char (0x20) or (0xa5) if locked to wwvb
268		    *   YYYY   Current year, 2000-2099
269		    *   +      Leap year indicator. '+' if a leap year,
270		    *          a space (0x20) if not.
271		    *   DDD    Day of year, 000 - 365.
272		    *   UTC    Timezone (always 'UTC').
273		    *   S      Daylight savings indicator
274		    *             S - standard time (STD) in effect
275		    *             O - during STD to DST day 0000-2400
276		    *             D - daylight savings time (DST) in effect
277		    *             I - during DST to STD day 0000-2400
278		    *   space  Space character (0x20)
279		    *   HH     Hours 00-23
280		    *   :      This is the REAL in sync indicator (: = insync)
281		    *   MM     Minutes 00-59
282		    *   :      : = in sync ? = NOT in sync
283		    *   SS     Seconds 00-59
284		    *   L      Leap second flag. Changes from space (0x20)
285		    *          to 'I' or 'D' during month preceding leap
286		    *          second adjustment. (I)nsert or (D)elete
287		    *   +5     UT1 correction (sign + digit ))
288		    */
289
290   		       if (sscanf(pp->a_lastcode,
291                          "%*2c %*2c%2c%*c%4d%*c%3d%*4c %2d%c%2d:%2d%c%*2c",
292   		          char_quality, &pp->year, &pp->day,
293                          &pp->hour, &syncchar, &pp->minute, &pp->second,
294                          &leapchar) == 8) {
295
296   			  if (char_quality[0] == '0') {
297   				quality = 0;
298   			  } else if (char_quality[0] == '0') {
299   				quality = (char_quality[1] & 0x0f);
300   			  } else  {
301   				quality = 99;
302   			  }
303
304   		          if (leapchar == 'I' ) leapchar = '+';
305   		          if (leapchar == 'D' ) leapchar = '-';
306
307		          /*
308		          #ifdef DEBUG
309		          if (debug) {
310		             printf("ulink: char_quality %c %c\n",
311                                    char_quality[0], char_quality[1]);
312			     printf("ulink: quality %d\n", quality);
313			     printf("ulink: syncchar %x\n", syncchar);
314			     printf("ulink: leapchar %x\n", leapchar);
315                          }
316                          #endif
317                          */
318
319                       }
320
321                    }
322                    if(siglchar == SIGLCHAR33x) {
323
324		   /*
325		    * We got a Model 33X decoder.
326		    * Timecode format from January 29, 2001 datasheet is:
327		    *   <CR><LF>S9+D 00 YYYY+DDDUTCS HH:MM:SSL+5
328		    *   S      WWVB decoder sync indicator. S for in-sync(?)
329		    *          or N for noisy signal.
330		    *   9+     RF signal level in S-units, 0-9 followed by
331		    *          a space (0x20). The space turns to '+' if the
332		    *          level is over 9.
333		    *   D      Data bit 0, 1, 2 (position mark), or
334		    *          3 (unknown).
335		    *   space  Space character (0x20)
336		    *   00     Hours since last good WWVB frame sync. Will
337		    *          be 00-23 hrs, or '1d' to '7d'. Will be 'Lk'
338                    *          if currently in sync.
339		    *   space  Space character (0x20)
340		    *   YYYY   Current year, 1990-2089
341		    *   +      Leap year indicator. '+' if a leap year,
342		    *          a space (0x20) if not.
343		    *   DDD    Day of year, 001 - 366.
344		    *   UTC    Timezone (always 'UTC').
345		    *   S      Daylight savings indicator
346		    *             S - standard time (STD) in effect
347		    *             O - during STD to DST day 0000-2400
348		    *             D - daylight savings time (DST) in effect
349		    *             I - during DST to STD day 0000-2400
350		    *   space  Space character (0x20)
351		    *   HH     Hours 00-23
352		    *   :      This is the REAL in sync indicator (: = insync)
353		    *   MM     Minutes 00-59
354		    *   :      : = in sync ? = NOT in sync
355		    *   SS     Seconds 00-59
356		    *   L      Leap second flag. Changes from space (0x20)
357		    *          to '+' or '-' during month preceding leap
358		    *          second adjustment.
359		    *   +5     UT1 correction (sign + digit ))
360		    */
361
362		       if (sscanf(pp->a_lastcode,
363                           "%*4c %2c %4d%*c%3d%*4c %2d%c%2d:%2d%c%*2c",
364		           char_quality, &pp->year, &pp->day,
365                           &pp->hour, &syncchar, &pp->minute, &pp->second,
366                           &leapchar) == 8) {
367
368			   if (char_quality[0] == 'L') {
369				quality = 0;
370			   } else if (char_quality[0] == '0') {
371				quality = (char_quality[1] & 0x0f);
372			   } else  {
373				quality = 99;
374		           }
375
376                           /*
377                           #ifdef DEBUG
378         		   if (debug) {
379         			printf("ulink: char_quality %c %c\n",
380                                        char_quality[0], char_quality[1]);
381         			printf("ulink: quality %d\n", quality);
382         			printf("ulink: syncchar %x\n", syncchar);
383         			printf("ulink: leapchar %x\n", leapchar);
384                           }
385                           #endif
386                           */
387
388		        }
389                    }
390		    break;
391		}
392
393		case LEN320:
394
395	        /*
396		 * Model 320 Decoder
397		 * The timecode format is:
398		 *
399		 *  <cr><lf>SQRYYYYDDD+HH:MM:SS.mmLT<cr>
400		 *
401		 * where:
402		 *
403		 * S = 'S' -- sync'd in last hour,
404		 *     '0'-'9' - hours x 10 since last update,
405		 *     '?' -- not in sync
406		 * Q = Number of correlating time-frames, from 0 to 5
407		 * R = 'R' -- reception in progress,
408		 *     'N' -- Noisy reception,
409		 *     ' ' -- standby mode
410		 * YYYY = year from 1990 to 2089
411		 * DDD = current day from 1 to 366
412		 * + = '+' if current year is a leap year, else ' '
413		 * HH = UTC hour 0 to 23
414		 * MM = Minutes of current hour from 0 to 59
415		 * SS = Seconds of current minute from 0 to 59
416		 * mm = 10's milliseconds of the current second from 00 to 99
417		 * L  = Leap second pending at end of month
418		 *     'I' = insert, 'D'= delete
419		 * T  = DST <-> STD transition indicators
420		 *
421        	 */
422
423		if (sscanf(pp->a_lastcode, "%c%1d%c%4d%3d%*c%2d:%2d:%2d.%2ld%c",
424	               &syncchar, &quality, &modechar, &pp->year, &pp->day,
425        	       &pp->hour, &pp->minute, &pp->second,
426			&pp->nsec, &leapchar) == 10) {
427		pp->nsec *= 10000000; /* M320 returns 10's of msecs */
428		if (leapchar == 'I' ) leapchar = '+';
429		if (leapchar == 'D' ) leapchar = '-';
430		if (syncchar != '?' ) syncchar = ':';
431
432 		break;
433		}
434
435		default:
436		refclock_report(peer, CEVNT_BADREPLY);
437		return;
438	}
439
440	/*
441	 * Decode quality indicator
442	 * For the 325 & 33x series, the lower the number the "better"
443	 * the time is. I used the dispersion as the measure of time
444	 * quality. The quality indicator in the 320 is the number of
445	 * correlating time frames (the more the better)
446	 */
447
448	/*
449	 * The spec sheet for the 325 & 33x series states the clock will
450	 * maintain +/-0.002 seconds accuracy when locked to WWVB. This
451	 * is indicated by 'Lk' in the quality portion of the incoming
452	 * string. When not in lock, a drift of +/-0.015 seconds should
453	 * be allowed for.
454	 * With the quality indicator decoding scheme above, the 'Lk'
455	 * condition will produce a quality value of 0. If the quality
456	 * indicator starts with '0' then the second character is the
457	 * number of hours since we were last locked. If the first
458	 * character is anything other than 'L' or '0' then we have been
459	 * out of lock for more than 9 hours so we assume the worst and
460	 * force a quality value that selects the 'default' maximum
461	 * dispersion. The dispersion values below are what came with the
462	 * driver. They're not unreasonable so they've not been changed.
463	 */
464
465	if (pp->lencode == LEN33X) {
466		switch (quality) {
467			case 0 :
468				pp->disp=.002;
469				break;
470			case 1 :
471				pp->disp=.02;
472				break;
473			case 2 :
474				pp->disp=.04;
475				break;
476			case 3 :
477				pp->disp=.08;
478				break;
479			default:
480				pp->disp=MAXDISPERSE;
481				break;
482		}
483	} else {
484		switch (quality) {
485			case 5 :
486				pp->disp=.002;
487				break;
488			case 4 :
489				pp->disp=.02;
490				break;
491			case 3 :
492				pp->disp=.04;
493				break;
494			case 2 :
495				pp->disp=.08;
496				break;
497			case 1 :
498				pp->disp=.16;
499				break;
500			default:
501				pp->disp=MAXDISPERSE;
502				break;
503		}
504
505	}
506
507	/*
508	 * Decode synchronization, and leap characters. If
509	 * unsynchronized, set the leap bits accordingly and exit.
510	 * Otherwise, set the leap bits according to the leap character.
511	 */
512
513	if (syncchar != ':')
514		pp->leap = LEAP_NOTINSYNC;
515	else if (leapchar == '+')
516		pp->leap = LEAP_ADDSECOND;
517	else if (leapchar == '-')
518		pp->leap = LEAP_DELSECOND;
519	else
520		pp->leap = LEAP_NOWARNING;
521
522	/*
523	 * Process the new sample in the median filter and determine the
524	 * timecode timestamp.
525	 */
526	if (!refclock_process(pp)) {
527		refclock_report(peer, CEVNT_BADTIME);
528	}
529
530}
531
532/*
533 * ulink_poll - called by the transmit procedure
534 */
535
536static void
537ulink_poll(
538	int unit,
539	struct peer *peer
540	)
541{
542        struct refclockproc *pp;
543        char pollchar;
544
545        pp = peer->procptr;
546        pollchar = 'T';
547	if (pp->sloppyclockflag & CLK_FLAG1) {
548	        if (write(pp->io.fd, &pollchar, 1) != 1)
549        	        refclock_report(peer, CEVNT_FAULT);
550        	else
551      	            pp->polls++;
552	}
553	else
554      	            pp->polls++;
555
556        if (pp->coderecv == pp->codeproc) {
557                refclock_report(peer, CEVNT_TIMEOUT);
558                return;
559        }
560        pp->lastref = pp->lastrec;
561	refclock_receive(peer);
562	record_clock_stats(&peer->srcadr, pp->a_lastcode);
563
564}
565
566#else
567int refclock_ulink_bs;
568#endif /* REFCLOCK */
569