/contrib/ntp/ntpd/refclock_hpgps.c

https://bitbucket.org/freebsd/freebsd-head/ · C · 619 lines · 358 code · 67 blank · 194 comment · 78 complexity · 8470fef4c27f1d2d8329376550af9cef MD5 · raw file

  1. /*
  2. * refclock_hpgps - clock driver for HP 58503A GPS receiver
  3. */
  4. #ifdef HAVE_CONFIG_H
  5. # include <config.h>
  6. #endif
  7. #if defined(REFCLOCK) && defined(CLOCK_HPGPS)
  8. #include "ntpd.h"
  9. #include "ntp_io.h"
  10. #include "ntp_refclock.h"
  11. #include "ntp_stdlib.h"
  12. #include <stdio.h>
  13. #include <ctype.h>
  14. /* Version 0.1 April 1, 1995
  15. * 0.2 April 25, 1995
  16. * tolerant of missing timecode response prompt and sends
  17. * clear status if prompt indicates error;
  18. * can use either local time or UTC from receiver;
  19. * can get receiver status screen via flag4
  20. *
  21. * WARNING!: This driver is UNDER CONSTRUCTION
  22. * Everything in here should be treated with suspicion.
  23. * If it looks wrong, it probably is.
  24. *
  25. * Comments and/or questions to: Dave Vitanye
  26. * Hewlett Packard Company
  27. * dave@scd.hp.com
  28. * (408) 553-2856
  29. *
  30. * Thanks to the author of the PST driver, which was the starting point for
  31. * this one.
  32. *
  33. * This driver supports the HP 58503A Time and Frequency Reference Receiver.
  34. * This receiver uses HP SmartClock (TM) to implement an Enhanced GPS receiver.
  35. * The receiver accuracy when locked to GPS in normal operation is better
  36. * than 1 usec. The accuracy when operating in holdover is typically better
  37. * than 10 usec. per day.
  38. *
  39. * The same driver also handles the HP Z3801A which is available surplus
  40. * from the cell phone industry. It's popular with hams.
  41. * It needs a different line setup: 19200 baud, 7 data bits, odd parity
  42. * That is selected by adding "mode 1" to the server line in ntp.conf
  43. * HP Z3801A code from Jeff Mock added by Hal Murray, Sep 2005
  44. *
  45. *
  46. * The receiver should be operated with factory default settings.
  47. * Initial driver operation: expects the receiver to be already locked
  48. * to GPS, configured and able to output timecode format 2 messages.
  49. *
  50. * The driver uses the poll sequence :PTIME:TCODE? to get a response from
  51. * the receiver. The receiver responds with a timecode string of ASCII
  52. * printing characters, followed by a <cr><lf>, followed by a prompt string
  53. * issued by the receiver, in the following format:
  54. * T#yyyymmddhhmmssMFLRVcc<cr><lf>scpi >
  55. *
  56. * The driver processes the response at the <cr> and <lf>, so what the
  57. * driver sees is the prompt from the previous poll, followed by this
  58. * timecode. The prompt from the current poll is (usually) left unread until
  59. * the next poll. So (except on the very first poll) the driver sees this:
  60. *
  61. * scpi > T#yyyymmddhhmmssMFLRVcc<cr><lf>
  62. *
  63. * The T is the on-time character, at 980 msec. before the next 1PPS edge.
  64. * The # is the timecode format type. We look for format 2.
  65. * Without any of the CLK or PPS stuff, then, the receiver buffer timestamp
  66. * at the <cr> is 24 characters later, which is about 25 msec. at 9600 bps,
  67. * so the first approximation for fudge time1 is nominally -0.955 seconds.
  68. * This number probably needs adjusting for each machine / OS type, so far:
  69. * -0.955000 on an HP 9000 Model 712/80 HP-UX 9.05
  70. * -0.953175 on an HP 9000 Model 370 HP-UX 9.10
  71. *
  72. * This receiver also provides a 1PPS signal, but I haven't figured out
  73. * how to deal with any of the CLK or PPS stuff yet. Stay tuned.
  74. *
  75. */
  76. /*
  77. * Fudge Factors
  78. *
  79. * Fudge time1 is used to accomodate the timecode serial interface adjustment.
  80. * Fudge flag4 can be set to request a receiver status screen summary, which
  81. * is recorded in the clockstats file.
  82. */
  83. /*
  84. * Interface definitions
  85. */
  86. #define DEVICE "/dev/hpgps%d" /* device name and unit */
  87. #define SPEED232 B9600 /* uart speed (9600 baud) */
  88. #define SPEED232Z B19200 /* uart speed (19200 baud) */
  89. #define PRECISION (-10) /* precision assumed (about 1 ms) */
  90. #define REFID "GPS\0" /* reference ID */
  91. #define DESCRIPTION "HP 58503A GPS Time and Frequency Reference Receiver"
  92. #define SMAX 23*80+1 /* for :SYSTEM:PRINT? status screen response */
  93. #define MTZONE 2 /* number of fields in timezone reply */
  94. #define MTCODET2 12 /* number of fields in timecode format T2 */
  95. #define NTCODET2 21 /* number of chars to checksum in format T2 */
  96. /*
  97. * Tables to compute the day of year from yyyymmdd timecode.
  98. * Viva la leap.
  99. */
  100. static int day1tab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
  101. static int day2tab[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
  102. /*
  103. * Unit control structure
  104. */
  105. struct hpgpsunit {
  106. int pollcnt; /* poll message counter */
  107. int tzhour; /* timezone offset, hours */
  108. int tzminute; /* timezone offset, minutes */
  109. int linecnt; /* set for expected multiple line responses */
  110. char *lastptr; /* pointer to receiver response data */
  111. char statscrn[SMAX]; /* receiver status screen buffer */
  112. };
  113. /*
  114. * Function prototypes
  115. */
  116. static int hpgps_start P((int, struct peer *));
  117. static void hpgps_shutdown P((int, struct peer *));
  118. static void hpgps_receive P((struct recvbuf *));
  119. static void hpgps_poll P((int, struct peer *));
  120. /*
  121. * Transfer vector
  122. */
  123. struct refclock refclock_hpgps = {
  124. hpgps_start, /* start up driver */
  125. hpgps_shutdown, /* shut down driver */
  126. hpgps_poll, /* transmit poll message */
  127. noentry, /* not used (old hpgps_control) */
  128. noentry, /* initialize driver */
  129. noentry, /* not used (old hpgps_buginfo) */
  130. NOFLAGS /* not used */
  131. };
  132. /*
  133. * hpgps_start - open the devices and initialize data for processing
  134. */
  135. static int
  136. hpgps_start(
  137. int unit,
  138. struct peer *peer
  139. )
  140. {
  141. register struct hpgpsunit *up;
  142. struct refclockproc *pp;
  143. int fd;
  144. char device[20];
  145. /*
  146. * Open serial port. Use CLK line discipline, if available.
  147. * Default is HP 58503A, mode arg selects HP Z3801A
  148. */
  149. (void)sprintf(device, DEVICE, unit);
  150. /* mode parameter to server config line shares ttl slot */
  151. if ((peer->ttl == 1)) {
  152. if (!(fd = refclock_open(device, SPEED232Z,
  153. LDISC_CLK | LDISC_7O1)))
  154. return (0);
  155. } else {
  156. if (!(fd = refclock_open(device, SPEED232, LDISC_CLK)))
  157. return (0);
  158. }
  159. /*
  160. * Allocate and initialize unit structure
  161. */
  162. if (!(up = (struct hpgpsunit *)
  163. emalloc(sizeof(struct hpgpsunit)))) {
  164. (void) close(fd);
  165. return (0);
  166. }
  167. memset((char *)up, 0, sizeof(struct hpgpsunit));
  168. pp = peer->procptr;
  169. pp->io.clock_recv = hpgps_receive;
  170. pp->io.srcclock = (caddr_t)peer;
  171. pp->io.datalen = 0;
  172. pp->io.fd = fd;
  173. if (!io_addclock(&pp->io)) {
  174. (void) close(fd);
  175. free(up);
  176. return (0);
  177. }
  178. pp->unitptr = (caddr_t)up;
  179. /*
  180. * Initialize miscellaneous variables
  181. */
  182. peer->precision = PRECISION;
  183. pp->clockdesc = DESCRIPTION;
  184. memcpy((char *)&pp->refid, REFID, 4);
  185. up->tzhour = 0;
  186. up->tzminute = 0;
  187. *up->statscrn = '\0';
  188. up->lastptr = up->statscrn;
  189. up->pollcnt = 2;
  190. /*
  191. * Get the identifier string, which is logged but otherwise ignored,
  192. * and get the local timezone information
  193. */
  194. up->linecnt = 1;
  195. if (write(pp->io.fd, "*IDN?\r:PTIME:TZONE?\r", 20) != 20)
  196. refclock_report(peer, CEVNT_FAULT);
  197. return (1);
  198. }
  199. /*
  200. * hpgps_shutdown - shut down the clock
  201. */
  202. static void
  203. hpgps_shutdown(
  204. int unit,
  205. struct peer *peer
  206. )
  207. {
  208. register struct hpgpsunit *up;
  209. struct refclockproc *pp;
  210. pp = peer->procptr;
  211. up = (struct hpgpsunit *)pp->unitptr;
  212. io_closeclock(&pp->io);
  213. free(up);
  214. }
  215. /*
  216. * hpgps_receive - receive data from the serial interface
  217. */
  218. static void
  219. hpgps_receive(
  220. struct recvbuf *rbufp
  221. )
  222. {
  223. register struct hpgpsunit *up;
  224. struct refclockproc *pp;
  225. struct peer *peer;
  226. l_fp trtmp;
  227. char tcodechar1; /* identifies timecode format */
  228. char tcodechar2; /* identifies timecode format */
  229. char timequal; /* time figure of merit: 0-9 */
  230. char freqqual; /* frequency figure of merit: 0-3 */
  231. char leapchar; /* leapsecond: + or 0 or - */
  232. char servchar; /* request for service: 0 = no, 1 = yes */
  233. char syncchar; /* time info is invalid: 0 = no, 1 = yes */
  234. short expectedsm; /* expected timecode byte checksum */
  235. short tcodechksm; /* computed timecode byte checksum */
  236. int i,m,n;
  237. int month, day, lastday;
  238. char *tcp; /* timecode pointer (skips over the prompt) */
  239. char prompt[BMAX]; /* prompt in response from receiver */
  240. /*
  241. * Initialize pointers and read the receiver response
  242. */
  243. peer = (struct peer *)rbufp->recv_srcclock;
  244. pp = peer->procptr;
  245. up = (struct hpgpsunit *)pp->unitptr;
  246. *pp->a_lastcode = '\0';
  247. pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp);
  248. #ifdef DEBUG
  249. if (debug)
  250. printf("hpgps: lencode: %d timecode:%s\n",
  251. pp->lencode, pp->a_lastcode);
  252. #endif
  253. /*
  254. * If there's no characters in the reply, we can quit now
  255. */
  256. if (pp->lencode == 0)
  257. return;
  258. /*
  259. * If linecnt is greater than zero, we are getting information only,
  260. * such as the receiver identification string or the receiver status
  261. * screen, so put the receiver response at the end of the status
  262. * screen buffer. When we have the last line, write the buffer to
  263. * the clockstats file and return without further processing.
  264. *
  265. * If linecnt is zero, we are expecting either the timezone
  266. * or a timecode. At this point, also write the response
  267. * to the clockstats file, and go on to process the prompt (if any),
  268. * timezone, or timecode and timestamp.
  269. */
  270. if (up->linecnt-- > 0) {
  271. if ((int)(pp->lencode + 2) <= (SMAX - (up->lastptr - up->statscrn))) {
  272. *up->lastptr++ = '\n';
  273. (void)strcpy(up->lastptr, pp->a_lastcode);
  274. up->lastptr += pp->lencode;
  275. }
  276. if (up->linecnt == 0)
  277. record_clock_stats(&peer->srcadr, up->statscrn);
  278. return;
  279. }
  280. record_clock_stats(&peer->srcadr, pp->a_lastcode);
  281. pp->lastrec = trtmp;
  282. up->lastptr = up->statscrn;
  283. *up->lastptr = '\0';
  284. up->pollcnt = 2;
  285. /*
  286. * We get down to business: get a prompt if one is there, issue
  287. * a clear status command if it contains an error indication.
  288. * Next, check for either the timezone reply or the timecode reply
  289. * and decode it. If we don't recognize the reply, or don't get the
  290. * proper number of decoded fields, or get an out of range timezone,
  291. * or if the timecode checksum is bad, then we declare bad format
  292. * and exit.
  293. *
  294. * Timezone format (including nominal prompt):
  295. * scpi > -H,-M<cr><lf>
  296. *
  297. * Timecode format (including nominal prompt):
  298. * scpi > T2yyyymmddhhmmssMFLRVcc<cr><lf>
  299. *
  300. */
  301. (void)strcpy(prompt,pp->a_lastcode);
  302. tcp = strrchr(pp->a_lastcode,'>');
  303. if (tcp == NULL)
  304. tcp = pp->a_lastcode;
  305. else
  306. tcp++;
  307. prompt[tcp - pp->a_lastcode] = '\0';
  308. while ((*tcp == ' ') || (*tcp == '\t')) tcp++;
  309. /*
  310. * deal with an error indication in the prompt here
  311. */
  312. if (strrchr(prompt,'E') > strrchr(prompt,'s')){
  313. #ifdef DEBUG
  314. if (debug)
  315. printf("hpgps: error indicated in prompt: %s\n", prompt);
  316. #endif
  317. if (write(pp->io.fd, "*CLS\r\r", 6) != 6)
  318. refclock_report(peer, CEVNT_FAULT);
  319. }
  320. /*
  321. * make sure we got a timezone or timecode format and
  322. * then process accordingly
  323. */
  324. m = sscanf(tcp,"%c%c", &tcodechar1, &tcodechar2);
  325. if (m != 2){
  326. #ifdef DEBUG
  327. if (debug)
  328. printf("hpgps: no format indicator\n");
  329. #endif
  330. refclock_report(peer, CEVNT_BADREPLY);
  331. return;
  332. }
  333. switch (tcodechar1) {
  334. case '+':
  335. case '-':
  336. m = sscanf(tcp,"%d,%d", &up->tzhour, &up->tzminute);
  337. if (m != MTZONE) {
  338. #ifdef DEBUG
  339. if (debug)
  340. printf("hpgps: only %d fields recognized in timezone\n", m);
  341. #endif
  342. refclock_report(peer, CEVNT_BADREPLY);
  343. return;
  344. }
  345. if ((up->tzhour < -12) || (up->tzhour > 13) ||
  346. (up->tzminute < -59) || (up->tzminute > 59)){
  347. #ifdef DEBUG
  348. if (debug)
  349. printf("hpgps: timezone %d, %d out of range\n",
  350. up->tzhour, up->tzminute);
  351. #endif
  352. refclock_report(peer, CEVNT_BADREPLY);
  353. return;
  354. }
  355. return;
  356. case 'T':
  357. break;
  358. default:
  359. #ifdef DEBUG
  360. if (debug)
  361. printf("hpgps: unrecognized reply format %c%c\n",
  362. tcodechar1, tcodechar2);
  363. #endif
  364. refclock_report(peer, CEVNT_BADREPLY);
  365. return;
  366. } /* end of tcodechar1 switch */
  367. switch (tcodechar2) {
  368. case '2':
  369. m = sscanf(tcp,"%*c%*c%4d%2d%2d%2d%2d%2d%c%c%c%c%c%2hx",
  370. &pp->year, &month, &day, &pp->hour, &pp->minute, &pp->second,
  371. &timequal, &freqqual, &leapchar, &servchar, &syncchar,
  372. &expectedsm);
  373. n = NTCODET2;
  374. if (m != MTCODET2){
  375. #ifdef DEBUG
  376. if (debug)
  377. printf("hpgps: only %d fields recognized in timecode\n", m);
  378. #endif
  379. refclock_report(peer, CEVNT_BADREPLY);
  380. return;
  381. }
  382. break;
  383. default:
  384. #ifdef DEBUG
  385. if (debug)
  386. printf("hpgps: unrecognized timecode format %c%c\n",
  387. tcodechar1, tcodechar2);
  388. #endif
  389. refclock_report(peer, CEVNT_BADREPLY);
  390. return;
  391. } /* end of tcodechar2 format switch */
  392. /*
  393. * Compute and verify the checksum.
  394. * Characters are summed starting at tcodechar1, ending at just
  395. * before the expected checksum. Bail out if incorrect.
  396. */
  397. tcodechksm = 0;
  398. while (n-- > 0) tcodechksm += *tcp++;
  399. tcodechksm &= 0x00ff;
  400. if (tcodechksm != expectedsm) {
  401. #ifdef DEBUG
  402. if (debug)
  403. printf("hpgps: checksum %2hX doesn't match %2hX expected\n",
  404. tcodechksm, expectedsm);
  405. #endif
  406. refclock_report(peer, CEVNT_BADREPLY);
  407. return;
  408. }
  409. /*
  410. * Compute the day of year from the yyyymmdd format.
  411. */
  412. if (month < 1 || month > 12 || day < 1) {
  413. refclock_report(peer, CEVNT_BADTIME);
  414. return;
  415. }
  416. if ( ! isleap_4(pp->year) ) { /* Y2KFixes */
  417. /* not a leap year */
  418. if (day > day1tab[month - 1]) {
  419. refclock_report(peer, CEVNT_BADTIME);
  420. return;
  421. }
  422. for (i = 0; i < month - 1; i++) day += day1tab[i];
  423. lastday = 365;
  424. } else {
  425. /* a leap year */
  426. if (day > day2tab[month - 1]) {
  427. refclock_report(peer, CEVNT_BADTIME);
  428. return;
  429. }
  430. for (i = 0; i < month - 1; i++) day += day2tab[i];
  431. lastday = 366;
  432. }
  433. /*
  434. * Deal with the timezone offset here. The receiver timecode is in
  435. * local time = UTC + :PTIME:TZONE, so SUBTRACT the timezone values.
  436. * For example, Pacific Standard Time is -8 hours , 0 minutes.
  437. * Deal with the underflows and overflows.
  438. */
  439. pp->minute -= up->tzminute;
  440. pp->hour -= up->tzhour;
  441. if (pp->minute < 0) {
  442. pp->minute += 60;
  443. pp->hour--;
  444. }
  445. if (pp->minute > 59) {
  446. pp->minute -= 60;
  447. pp->hour++;
  448. }
  449. if (pp->hour < 0) {
  450. pp->hour += 24;
  451. day--;
  452. if (day < 1) {
  453. pp->year--;
  454. if ( isleap_4(pp->year) ) /* Y2KFixes */
  455. day = 366;
  456. else
  457. day = 365;
  458. }
  459. }
  460. if (pp->hour > 23) {
  461. pp->hour -= 24;
  462. day++;
  463. if (day > lastday) {
  464. pp->year++;
  465. day = 1;
  466. }
  467. }
  468. pp->day = day;
  469. /*
  470. * Decode the MFLRV indicators.
  471. * NEED TO FIGURE OUT how to deal with the request for service,
  472. * time quality, and frequency quality indicators some day.
  473. */
  474. if (syncchar != '0') {
  475. pp->leap = LEAP_NOTINSYNC;
  476. }
  477. else {
  478. switch (leapchar) {
  479. case '+':
  480. pp->leap = LEAP_ADDSECOND;
  481. break;
  482. case '0':
  483. pp->leap = LEAP_NOWARNING;
  484. break;
  485. case '-':
  486. pp->leap = LEAP_DELSECOND;
  487. break;
  488. default:
  489. #ifdef DEBUG
  490. if (debug)
  491. printf("hpgps: unrecognized leap indicator: %c\n",
  492. leapchar);
  493. #endif
  494. refclock_report(peer, CEVNT_BADTIME);
  495. return;
  496. } /* end of leapchar switch */
  497. }
  498. /*
  499. * Process the new sample in the median filter and determine the
  500. * reference clock offset and dispersion. We use lastrec as both
  501. * the reference time and receive time in order to avoid being
  502. * cute, like setting the reference time later than the receive
  503. * time, which may cause a paranoid protocol module to chuck out
  504. * the data.
  505. */
  506. if (!refclock_process(pp)) {
  507. refclock_report(peer, CEVNT_BADTIME);
  508. return;
  509. }
  510. pp->lastref = pp->lastrec;
  511. refclock_receive(peer);
  512. /*
  513. * If CLK_FLAG4 is set, ask for the status screen response.
  514. */
  515. if (pp->sloppyclockflag & CLK_FLAG4){
  516. up->linecnt = 22;
  517. if (write(pp->io.fd, ":SYSTEM:PRINT?\r", 15) != 15)
  518. refclock_report(peer, CEVNT_FAULT);
  519. }
  520. }
  521. /*
  522. * hpgps_poll - called by the transmit procedure
  523. */
  524. static void
  525. hpgps_poll(
  526. int unit,
  527. struct peer *peer
  528. )
  529. {
  530. register struct hpgpsunit *up;
  531. struct refclockproc *pp;
  532. /*
  533. * Time to poll the clock. The HP 58503A responds to a
  534. * ":PTIME:TCODE?" by returning a timecode in the format specified
  535. * above. If nothing is heard from the clock for two polls,
  536. * declare a timeout and keep going.
  537. */
  538. pp = peer->procptr;
  539. up = (struct hpgpsunit *)pp->unitptr;
  540. if (up->pollcnt == 0)
  541. refclock_report(peer, CEVNT_TIMEOUT);
  542. else
  543. up->pollcnt--;
  544. if (write(pp->io.fd, ":PTIME:TCODE?\r", 14) != 14) {
  545. refclock_report(peer, CEVNT_FAULT);
  546. }
  547. else
  548. pp->polls++;
  549. }
  550. #else
  551. int refclock_hpgps_bs;
  552. #endif /* REFCLOCK */