/src/ntp-dev/ntpd/refclock_pst.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * refclock_pst - clock driver for PSTI/Traconex WWV/WWVH receivers |
3 | | */ |
4 | | |
5 | | #ifdef HAVE_CONFIG_H |
6 | | #include <config.h> |
7 | | #endif |
8 | | |
9 | | #if defined(REFCLOCK) && defined(CLOCK_PST) |
10 | | |
11 | | #include "ntpd.h" |
12 | | #include "ntp_io.h" |
13 | | #include "ntp_refclock.h" |
14 | | #include "ntp_stdlib.h" |
15 | | |
16 | | #include <stdio.h> |
17 | | #include <ctype.h> |
18 | | |
19 | | /* |
20 | | * This driver supports the PSTI 1010 and Traconex 1020 WWV/WWVH |
21 | | * Receivers. No specific claim of accuracy is made for these receiver, |
22 | | * but actual experience suggests that 10 ms would be a conservative |
23 | | * assumption. |
24 | | * |
25 | | * The DIPswitches should be set for 9600 bps line speed, 24-hour day- |
26 | | * of-year format and UTC time zone. Automatic correction for DST should |
27 | | * be disabled. It is very important that the year be set correctly in |
28 | | * the DIPswitches; otherwise, the day of year will be incorrect after |
29 | | * 28 April of a normal or leap year. The propagation delay DIPswitches |
30 | | * should be set according to the distance from the transmitter for both |
31 | | * WWV and WWVH, as described in the instructions. While the delay can |
32 | | * be set only to within 11 ms, the fudge time1 parameter can be used |
33 | | * for vernier corrections. |
34 | | * |
35 | | * Using the poll sequence QTQDQM, the response timecode is in three |
36 | | * sections totalling 50 ASCII printing characters, as concatenated by |
37 | | * the driver, in the following format: |
38 | | * |
39 | | * ahh:mm:ss.fffs<cr> yy/dd/mm/ddd<cr> frdzycchhSSFTttttuuxx<cr> |
40 | | * |
41 | | * on-time = first <cr> |
42 | | * hh:mm:ss.fff = hours, minutes, seconds, milliseconds |
43 | | * a = AM/PM indicator (' ' for 24-hour mode) |
44 | | * yy = year (from internal switches) |
45 | | * dd/mm/ddd = day of month, month, day of year |
46 | | * s = daylight-saving indicator (' ' for 24-hour mode) |
47 | | * f = frequency enable (O = all frequencies enabled) |
48 | | * r = baud rate (3 = 1200, 6 = 9600) |
49 | | * d = features indicator (@ = month/day display enabled) |
50 | | * z = time zone (0 = UTC) |
51 | | * y = year (5 = 91) |
52 | | * cc = WWV propagation delay (52 = 22 ms) |
53 | | * hh = WWVH propagation delay (81 = 33 ms) |
54 | | * SS = status (80 or 82 = operating correctly) |
55 | | * F = current receive frequency (4 = 15 MHz) |
56 | | * T = transmitter (C = WWV, H = WWVH) |
57 | | * tttt = time since last update (0000 = minutes) |
58 | | * uu = flush character (03 = ^c) |
59 | | * xx = 94 (unknown) |
60 | | * |
61 | | * The alarm condition is indicated by other than '8' at A, which occurs |
62 | | * during initial synchronization and when received signal is lost for |
63 | | * an extended period; unlock condition is indicated by other than |
64 | | * "0000" in the tttt subfield at Q. |
65 | | * |
66 | | * Fudge Factors |
67 | | * |
68 | | * There are no special fudge factors other than the generic. |
69 | | */ |
70 | | |
71 | | /* |
72 | | * Interface definitions |
73 | | */ |
74 | | #define DEVICE "/dev/wwv%d" /* device name and unit */ |
75 | 0 | #define SPEED232 B9600 /* uart speed (9600 baud) */ |
76 | 0 | #define PRECISION (-10) /* precision assumed (about 1 ms) */ |
77 | 0 | #define WWVREFID "WWV\0" /* WWV reference ID */ |
78 | 0 | #define WWVHREFID "WWVH" /* WWVH reference ID */ |
79 | 0 | #define DESCRIPTION "PSTI/Traconex WWV/WWVH Receiver" /* WRU */ |
80 | 0 | #define PST_PHI (10e-6) /* max clock oscillator offset */ |
81 | 0 | #define LENPST 46 /* min timecode length */ |
82 | | |
83 | | /* |
84 | | * Unit control structure |
85 | | */ |
86 | | struct pstunit { |
87 | | int tcswitch; /* timecode switch */ |
88 | | char *lastptr; /* pointer to timecode data */ |
89 | | }; |
90 | | |
91 | | /* |
92 | | * Function prototypes |
93 | | */ |
94 | | static int pst_start (int, struct peer *); |
95 | | static void pst_shutdown (int, struct peer *); |
96 | | static void pst_receive (struct recvbuf *); |
97 | | static void pst_poll (int, struct peer *); |
98 | | |
99 | | /* |
100 | | * Transfer vector |
101 | | */ |
102 | | struct refclock refclock_pst = { |
103 | | pst_start, /* start up driver */ |
104 | | pst_shutdown, /* shut down driver */ |
105 | | pst_poll, /* transmit poll message */ |
106 | | noentry, /* not used (old pst_control) */ |
107 | | noentry, /* initialize driver */ |
108 | | noentry, /* not used (old pst_buginfo) */ |
109 | | NOFLAGS /* not used */ |
110 | | }; |
111 | | |
112 | | |
113 | | /* |
114 | | * pst_start - open the devices and initialize data for processing |
115 | | */ |
116 | | static int |
117 | | pst_start( |
118 | | int unit, |
119 | | struct peer *peer |
120 | | ) |
121 | 0 | { |
122 | 0 | register struct pstunit *up; |
123 | 0 | struct refclockproc *pp; |
124 | 0 | int fd; |
125 | 0 | char device[20]; |
126 | | |
127 | | /* |
128 | | * Open serial port. Use CLK line discipline, if available. |
129 | | */ |
130 | 0 | snprintf(device, sizeof(device), DEVICE, unit); |
131 | 0 | fd = refclock_open(device, SPEED232, LDISC_CLK); |
132 | 0 | if (fd <= 0) |
133 | 0 | return (0); |
134 | | |
135 | | /* |
136 | | * Allocate and initialize unit structure |
137 | | */ |
138 | 0 | up = emalloc_zero(sizeof(*up)); |
139 | 0 | pp = peer->procptr; |
140 | 0 | pp->io.clock_recv = pst_receive; |
141 | 0 | pp->io.srcclock = peer; |
142 | 0 | pp->io.datalen = 0; |
143 | 0 | pp->io.fd = fd; |
144 | 0 | if (!io_addclock(&pp->io)) { |
145 | 0 | close(fd); |
146 | 0 | pp->io.fd = -1; |
147 | 0 | free(up); |
148 | 0 | return (0); |
149 | 0 | } |
150 | 0 | pp->unitptr = up; |
151 | | |
152 | | /* |
153 | | * Initialize miscellaneous variables |
154 | | */ |
155 | 0 | peer->precision = PRECISION; |
156 | 0 | pp->clockdesc = DESCRIPTION; |
157 | 0 | memcpy((char *)&pp->refid, WWVREFID, 4); |
158 | 0 | return (1); |
159 | 0 | } |
160 | | |
161 | | |
162 | | /* |
163 | | * pst_shutdown - shut down the clock |
164 | | */ |
165 | | static void |
166 | | pst_shutdown( |
167 | | int unit, |
168 | | struct peer *peer |
169 | | ) |
170 | 0 | { |
171 | 0 | register struct pstunit *up; |
172 | 0 | struct refclockproc *pp; |
173 | |
|
174 | 0 | pp = peer->procptr; |
175 | 0 | up = pp->unitptr; |
176 | 0 | if (-1 != pp->io.fd) |
177 | 0 | io_closeclock(&pp->io); |
178 | 0 | if (NULL != up) |
179 | 0 | free(up); |
180 | 0 | } |
181 | | |
182 | | |
183 | | /* |
184 | | * pst_receive - receive data from the serial interface |
185 | | */ |
186 | | static void |
187 | | pst_receive( |
188 | | struct recvbuf *rbufp |
189 | | ) |
190 | 0 | { |
191 | 0 | register struct pstunit *up; |
192 | 0 | struct refclockproc *pp; |
193 | 0 | struct peer *peer; |
194 | 0 | l_fp trtmp; |
195 | 0 | u_long ltemp; |
196 | 0 | char ampmchar; /* AM/PM indicator */ |
197 | 0 | char daychar; /* standard/daylight indicator */ |
198 | 0 | char junque[10]; /* "yy/dd/mm/" discard */ |
199 | 0 | char info[14]; /* "frdzycchhSSFT" clock info */ |
200 | | |
201 | | /* |
202 | | * Initialize pointers and read the timecode and timestamp |
203 | | */ |
204 | 0 | peer = rbufp->recv_peer; |
205 | 0 | pp = peer->procptr; |
206 | 0 | up = pp->unitptr; |
207 | 0 | up->lastptr += refclock_gtlin(rbufp, up->lastptr, pp->a_lastcode |
208 | 0 | + BMAX - 2 - up->lastptr, &trtmp); |
209 | 0 | *up->lastptr++ = ' '; |
210 | 0 | *up->lastptr = '\0'; |
211 | | |
212 | | /* |
213 | | * Note we get a buffer and timestamp for each <cr>, but only |
214 | | * the first timestamp is retained. |
215 | | */ |
216 | 0 | if (up->tcswitch == 0) |
217 | 0 | pp->lastrec = trtmp; |
218 | 0 | up->tcswitch++; |
219 | 0 | pp->lencode = up->lastptr - pp->a_lastcode; |
220 | 0 | if (up->tcswitch < 3) |
221 | 0 | return; |
222 | | |
223 | | /* |
224 | | * We get down to business, check the timecode format and decode |
225 | | * its contents. If the timecode has invalid length or is not in |
226 | | * proper format, we declare bad format and exit. |
227 | | */ |
228 | 0 | if (pp->lencode < LENPST) { |
229 | 0 | refclock_report(peer, CEVNT_BADREPLY); |
230 | 0 | return; |
231 | 0 | } |
232 | | |
233 | | /* |
234 | | * Timecode format: |
235 | | * "ahh:mm:ss.fffs yy/dd/mm/ddd frdzycchhSSFTttttuuxx" |
236 | | */ |
237 | 0 | if (sscanf(pp->a_lastcode, |
238 | 0 | "%c%2d:%2d:%2d.%3ld%c %9s%3d%13s%4ld", |
239 | 0 | &mchar, &pp->hour, &pp->minute, &pp->second, &pp->nsec, |
240 | 0 | &daychar, junque, &pp->day, info, <emp) != 10) { |
241 | 0 | refclock_report(peer, CEVNT_BADREPLY); |
242 | 0 | return; |
243 | 0 | } |
244 | 0 | pp->nsec *= 1000000; |
245 | | |
246 | | /* |
247 | | * Decode synchronization, quality and last update. If |
248 | | * unsynchronized, set the leap bits accordingly and exit. Once |
249 | | * synchronized, the dispersion depends only on when the clock |
250 | | * was last heard, which depends on the time since last update, |
251 | | * as reported by the clock. |
252 | | */ |
253 | 0 | if (info[9] != '8') |
254 | 0 | pp->leap = LEAP_NOTINSYNC; |
255 | 0 | if (info[12] == 'H') |
256 | 0 | memcpy((char *)&pp->refid, WWVHREFID, 4); |
257 | 0 | else |
258 | 0 | memcpy((char *)&pp->refid, WWVREFID, 4); |
259 | 0 | if (peer->stratum <= 1) |
260 | 0 | peer->refid = pp->refid; |
261 | 0 | if (ltemp == 0) |
262 | 0 | pp->lastref = pp->lastrec; |
263 | 0 | pp->disp = PST_PHI * ltemp * 60; |
264 | | |
265 | | /* |
266 | | * Process the new sample in the median filter and determine the |
267 | | * timecode timestamp. |
268 | | */ |
269 | 0 | if (!refclock_process(pp)) |
270 | 0 | refclock_report(peer, CEVNT_BADTIME); |
271 | 0 | else if (peer->disp > MAXDISTANCE) |
272 | 0 | refclock_receive(peer); |
273 | 0 | } |
274 | | |
275 | | |
276 | | /* |
277 | | * pst_poll - called by the transmit procedure |
278 | | */ |
279 | | static void |
280 | | pst_poll( |
281 | | int unit, |
282 | | struct peer *peer |
283 | | ) |
284 | 0 | { |
285 | 0 | register struct pstunit *up; |
286 | 0 | struct refclockproc *pp; |
287 | | |
288 | | /* |
289 | | * Time to poll the clock. The PSTI/Traconex clock responds to a |
290 | | * "QTQDQMT" by returning a timecode in the format specified |
291 | | * above. Note there is no checking on state, since this may not |
292 | | * be the only customer reading the clock. Only one customer |
293 | | * need poll the clock; all others just listen in. If the clock |
294 | | * becomes unreachable, declare a timeout and keep going. |
295 | | */ |
296 | 0 | pp = peer->procptr; |
297 | 0 | up = pp->unitptr; |
298 | 0 | up->tcswitch = 0; |
299 | 0 | up->lastptr = pp->a_lastcode; |
300 | 0 | if (write(pp->io.fd, "QTQDQMT", 6) != 6) |
301 | 0 | refclock_report(peer, CEVNT_FAULT); |
302 | 0 | if (pp->coderecv == pp->codeproc) { |
303 | 0 | refclock_report(peer, CEVNT_TIMEOUT); |
304 | 0 | return; |
305 | 0 | } |
306 | 0 | refclock_receive(peer); |
307 | 0 | record_clock_stats(&peer->srcadr, pp->a_lastcode); |
308 | 0 | #ifdef DEBUG |
309 | 0 | if (debug) |
310 | 0 | printf("pst: timecode %d %s\n", pp->lencode, |
311 | 0 | pp->a_lastcode); |
312 | 0 | #endif |
313 | 0 | pp->polls++; |
314 | 0 | } |
315 | | |
316 | | #else |
317 | | int refclock_pst_int; |
318 | | #endif /* REFCLOCK */ |