2 * Handle the Rockwell binary packet format supported by the old Zodiac chipset
4 * This file is Copyright (c) 2010 by the GPSD project
5 * BSD terms apply: see the file COPYING in the distribution root for details.
13 #endif /* S_SPLINT_S */
29 static unsigned short zodiac_checksum(unsigned short *w, int n)
31 unsigned short csum = 0;
38 /* zodiac_spew - Takes a message type, an array of data words, and a length
39 for the array, and prepends a 5 word header (including checksum).
40 The data words are expected to be checksummed */
41 #if defined (WORDS_BIGENDIAN)
42 /* data is assumed to contain len/2 unsigned short words
43 * we change the endianness to little, when needed.
45 static int end_write(int fd, void *d, int len)
49 char *data = (char *)d;
50 size_t n = (size_t) len;
58 return write(fd, buf, len);
61 #define end_write write
62 #endif /* WORDS_BIGENDIAN */
64 static ssize_t zodiac_spew(struct gps_device_t *session, unsigned short type,
65 unsigned short *dat, int dlen)
72 h.id = (unsigned short)type;
73 h.ndata = (unsigned short)(dlen - 1);
75 h.csum = zodiac_checksum((unsigned short *)&h, 4);
77 if (session->gpsdata.gps_fd != -1) {
80 datlen = sizeof(unsigned short) * dlen;
81 if (end_write(session->gpsdata.gps_fd, &h, hlen) != (ssize_t) hlen ||
82 end_write(session->gpsdata.gps_fd, dat,
83 datlen) != (ssize_t) datlen) {
84 gpsd_report(LOG_RAW, "Reconfigure write failed\n");
89 (void)snprintf(buf, sizeof(buf),
90 "%04x %04x %04x %04x %04x",
91 h.sync, h.id, h.ndata, h.flags, h.csum);
92 for (i = 0; i < dlen; i++)
93 (void)snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
96 gpsd_report(LOG_RAW, "Sent Zodiac packet: %s\n", buf);
101 static void send_rtcm(struct gps_device_t *session,
102 char *rtcmbuf, size_t rtcmbytes)
104 unsigned short data[34];
105 int n = 1 + (int)(rtcmbytes / 2 + rtcmbytes % 2);
107 if (session->driver.zodiac.sn++ > 32767)
108 session->driver.zodiac.sn = 0;
110 memset(data, 0, sizeof(data));
111 data[0] = session->driver.zodiac.sn; /* sequence number */
112 memcpy(&data[1], rtcmbuf, rtcmbytes);
113 data[n] = zodiac_checksum(data, n);
115 (void)zodiac_spew(session, 1351, data, n + 1);
118 static ssize_t zodiac_send_rtcm(struct gps_device_t *session,
119 char *rtcmbuf, size_t rtcmbytes)
123 while (rtcmbytes > 0) {
124 len = (size_t) (rtcmbytes > 64 ? 64 : rtcmbytes);
125 send_rtcm(session, rtcmbuf, len);
132 #define getzword(n) getwordz(session->packet.outbuffer, n)
133 #define getzlong(n) getlongz(session->packet.outbuffer, n)
135 static gps_mask_t handle1000(struct gps_device_t *session)
136 /* time-position-velocity report */
140 struct tm unpacked_date;
141 /* ticks = getzlong(6); */
142 /* sequence = getzword(8); */
143 /* measurement_sequence = getzword(9); */
144 /*@ -boolops -predboolothers @*/
145 session->gpsdata.status = (getzword(10) & 0x1c) ? 0 : 1;
146 if (session->gpsdata.status != 0)
147 session->newdata.mode = (getzword(10) & 1) ? MODE_2D : MODE_3D;
149 session->newdata.mode = MODE_NO_FIX;
150 /*@ +boolops -predboolothers @*/
152 /* solution_type = getzword(11); */
153 session->gpsdata.satellites_used = (int)getzword(12);
154 /* polar_navigation = getzword(13); */
155 session->context->gps_week = (unsigned short)getzword(14);
156 /* gps_seconds = getzlong(15); */
157 /* gps_nanoseconds = getzlong(17); */
158 unpacked_date.tm_mday = (int)getzword(19);
159 unpacked_date.tm_mon = (int)getzword(20) - 1;
160 unpacked_date.tm_year = (int)getzword(21) - 1900;
161 unpacked_date.tm_hour = (int)getzword(22);
162 unpacked_date.tm_min = (int)getzword(23);
163 unpacked_date.tm_sec = (int)getzword(24);
164 subseconds = (int)getzlong(25) / 1e9;
166 session->newdata.time = (double)mkgmtime(&unpacked_date) + subseconds;
169 session->newdata.latitude = ((long)getzlong(27)) * RAD_2_DEG * 1e-8;
170 session->newdata.longitude = ((long)getzlong(29)) * RAD_2_DEG * 1e-8;
172 * The Rockwell Jupiter TU30-D140 reports altitude as uncorrected height
173 * above WGS84 geoid. The Zodiac binary protocol manual does not
174 * specify whether word 31 is geodetic or WGS 84.
176 session->newdata.altitude = ((long)getzlong(31)) * 1e-2;
178 session->gpsdata.separation = ((short)getzword(33)) * 1e-2;
179 session->newdata.altitude -= session->gpsdata.separation;
180 session->newdata.speed = (int)getzlong(34) * 1e-2;
181 session->newdata.track = (int)getzword(36) * RAD_2_DEG * 1e-3;
182 session->mag_var = ((short)getzword(37)) * RAD_2_DEG * 1e-4;
183 session->newdata.climb = ((short)getzword(38)) * 1e-2;
184 /* map_datum = getzword(39); */
186 * The manual says these are 1-sigma. Device reports only eph, circular
187 * error; no harm in assigning it to both x and y components.
189 session->newdata.epx = session->newdata.epy =
190 (int)getzlong(40) * 1e-2 * (1 / sqrt(2)) * GPSD_CONFIDENCE;
191 session->newdata.epv = (int)getzlong(42) * 1e-2 * GPSD_CONFIDENCE;
192 session->newdata.ept = (int)getzlong(44) * 1e-2 * GPSD_CONFIDENCE;
193 session->newdata.eps = (int)getzword(46) * 1e-2 * GPSD_CONFIDENCE;
194 /* clock_bias = (int)getzlong(47) * 1e-2; */
195 /* clock_bias_sd = (int)getzlong(49) * 1e-2; */
196 /* clock_drift = (int)getzlong(51) * 1e-2; */
197 /* clock_drift_sd = (int)getzlong(53) * 1e-2; */
200 TIME_IS | LATLON_IS | ALTITUDE_IS | CLIMB_IS | SPEED_IS | TRACK_IS |
202 gpsd_report(LOG_DATA,
203 "1000: time=%.2f lat=%.2f lon=%.2f alt=%.2f track=%.2f speed=%.2f climb=%.2f mode=%d status=%d mask=%s\n",
204 session->newdata.time, session->newdata.latitude,
205 session->newdata.longitude, session->newdata.altitude,
206 session->newdata.track, session->newdata.speed,
207 session->newdata.climb, session->newdata.mode,
208 session->gpsdata.status, gpsd_maskdump(mask));
212 static gps_mask_t handle1002(struct gps_device_t *session)
213 /* satellite signal quality report */
215 int i, j, status, prn;
217 /* ticks = getzlong(6); */
218 /* sequence = getzword(8); */
219 /* measurement_sequence = getzword(9); */
221 int gps_week = getzword(10);
222 int gps_seconds = getzlong(11);
223 /* gps_nanoseconds = getzlong(13); */
225 session->context->gps_week = (unsigned short)gps_week;
226 session->gpsdata.satellites_used = 0;
227 memset(session->gpsdata.used, 0, sizeof(session->gpsdata.used));
228 for (i = 0; i < ZODIAC_CHANNELS; i++) {
230 session->driver.zodiac.Zv[i] = status = (int)getzword(15 + (3 * i));
231 session->driver.zodiac.Zs[i] = prn = (int)getzword(16 + (3 * i));
235 session->gpsdata.used[session->gpsdata.satellites_used++] = prn;
236 for (j = 0; j < ZODIAC_CHANNELS; j++) {
237 if (session->gpsdata.PRN[j] != prn)
239 session->gpsdata.ss[j] = (float)getzword(17 + (3 * i));
243 session->context->gps_week = (unsigned short)gps_week;
244 session->context->gps_tow = (double)gps_seconds;
245 session->gpsdata.skyview_time =
246 gpstime_to_unix(gps_week, session->context->gps_tow);
247 gpsd_report(LOG_DATA, "1002: visible=%d used=%d mask={SATELLITE|USED}\n",
248 session->gpsdata.satellites_visible,
249 session->gpsdata.satellites_used);
250 return SATELLITE_IS | USED_IS;
253 static gps_mask_t handle1003(struct gps_device_t *session)
258 /* The Polaris (and probably the DAGR) emit some strange variant of
259 * this message which causes gpsd to crash filtering on impossible
260 * number of satellites avoids this */
261 n = (int)getzword(14);
262 if ((n < 0) || (n > 12))
265 gpsd_zero_satellites(&session->gpsdata);
267 /* ticks = getzlong(6); */
268 /* sequence = getzword(8); */
269 session->gpsdata.dop.gdop = (unsigned int)getzword(9) * 1e-2;
270 session->gpsdata.dop.pdop = (unsigned int)getzword(10) * 1e-2;
271 session->gpsdata.dop.hdop = (unsigned int)getzword(11) * 1e-2;
272 session->gpsdata.dop.vdop = (unsigned int)getzword(12) * 1e-2;
273 session->gpsdata.dop.tdop = (unsigned int)getzword(13) * 1e-2;
274 session->gpsdata.satellites_visible = n;
276 for (i = 0; i < ZODIAC_CHANNELS; i++) {
277 if (i < session->gpsdata.satellites_visible) {
278 session->gpsdata.PRN[i] = (int)getzword(15 + (3 * i));
279 session->gpsdata.azimuth[i] =
280 (int)(((short)getzword(16 + (3 * i))) * RAD_2_DEG * 1e-4);
281 if (session->gpsdata.azimuth[i] < 0)
282 session->gpsdata.azimuth[i] += 360;
283 session->gpsdata.elevation[i] =
284 (int)(((short)getzword(17 + (3 * i))) * RAD_2_DEG * 1e-4);
286 session->gpsdata.PRN[i] = 0;
287 session->gpsdata.azimuth[i] = 0;
288 session->gpsdata.elevation[i] = 0;
291 session->gpsdata.skyview_time = NAN;
292 gpsd_report(LOG_DATA, "NAVDOP: visible=%d gdop=%.2f pdop=%.2f "
293 "hdop=%.2f vdop=%.2f tdop=%.2f mask={SATELLITE|DOP}\n",
294 session->gpsdata.satellites_visible,
295 session->gpsdata.dop.gdop,
296 session->gpsdata.dop.hdop,
297 session->gpsdata.dop.vdop,
298 session->gpsdata.dop.pdop, session->gpsdata.dop.tdop);
299 return SATELLITE_IS | DOP_IS;
302 static void handle1005(struct gps_device_t *session UNUSED)
303 /* fix quality report */
305 /* ticks = getzlong(6); */
306 /* sequence = getzword(8); */
307 int numcorrections = (int)getzword(12);
309 if (session->newdata.mode == MODE_NO_FIX)
310 session->gpsdata.status = STATUS_NO_FIX;
311 else if (numcorrections == 0)
312 session->gpsdata.status = STATUS_FIX;
314 session->gpsdata.status = STATUS_DGPS_FIX;
317 static gps_mask_t handle1011(struct gps_device_t *session)
321 * This is UNTESTED -- but harmless if buggy. Added to support
322 * client querying of the ID with firmware version in 2006.
323 * The Zodiac is supposed to send one of these messages on startup.
325 getstringz(session->subtype, session->packet.outbuffer, 19, 28); /* software version field */
326 gpsd_report(LOG_DATA, "1011: subtype=%s mask={DEVICEID}\n",
332 static void handle1108(struct gps_device_t *session)
333 /* leap-second correction report */
335 /* ticks = getzlong(6); */
336 /* sequence = getzword(8); */
337 /* utc_week_seconds = getzlong(14); */
338 /* leap_nanoseconds = getzlong(17); */
339 if ((int)(getzword(19) & 3) == 3)
340 session->context->leap_seconds = (int)getzword(16);
343 static gps_mask_t zodiac_analyze(struct gps_device_t *session)
348 (unsigned int)((session->packet.outbuffer[3] << 8) |
349 session->packet.outbuffer[2]);
351 if (session->packet.type != ZODIAC_PACKET) {
352 const struct gps_type_t **dp;
353 gpsd_report(LOG_PROG, "zodiac_analyze packet type %d\n",
354 session->packet.type);
355 // Wrong packet type ?
356 // Maybe find a trigger just in case it's an Earthmate
357 gpsd_report(LOG_RAW + 4, "Is this a trigger: %s ?\n",
358 (char *)session->packet.outbuffer);
360 for (dp = gpsd_drivers; *dp; dp++) {
361 char *trigger = (*dp)->trigger;
364 && strncmp((char *)session->packet.outbuffer, trigger,
365 strlen(trigger)) == 0
366 && isatty(session->gpsdata.gps_fd) != 0) {
367 gpsd_report(LOG_PROG, "found %s.\n", trigger);
369 (void)gpsd_switch_driver(session, (*dp)->type_name);
377 for (i = 0; i < (int)session->packet.outbuflen; i++)
378 (void)snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
379 "%02x", (unsigned int)session->packet.outbuffer[i]);
380 gpsd_report(LOG_RAW, "Raw Zodiac packet type %d length %zd: %s\n",
381 id, session->packet.outbuflen, buf);
383 if (session->packet.outbuflen < 10)
386 (void)snprintf(session->gpsdata.tag, sizeof(session->gpsdata.tag), "%u",
390 * Normal cycle for these devices is 1001 1002.
391 * We count 1001 as end of cycle because 1002 doesn't
392 * carry fix information.
394 session->cycle_end_reliable = true;
398 return handle1000(session) | (CLEAR_IS | REPORT_IS);
400 return handle1002(session);
402 return handle1003(session);
407 return handle1011(session);
416 #ifdef ALLOW_CONTROLSEND
417 static ssize_t zodiac_control_send(struct gps_device_t *session,
418 char *msg, size_t len)
420 unsigned short *shortwords = (unsigned short *)msg;
422 /* and if len isn't even, it's your own fault */
423 return zodiac_spew(session, shortwords[0], shortwords + 1,
426 #endif /* ALLOW_CONTROLSEND */
428 #ifdef ALLOW_RECONFIGURE
429 static bool zodiac_speed_switch(struct gps_device_t *session,
430 speed_t speed, char parity, int stopbits)
432 unsigned short data[15];
434 if (session->driver.zodiac.sn++ > 32767)
435 session->driver.zodiac.sn = 0;
453 memset(data, 0, sizeof(data));
454 /* data is the part of the message starting at word 6 */
455 data[0] = session->driver.zodiac.sn; /* sequence number */
456 data[1] = 1; /* port 1 data valid */
457 data[2] = (unsigned short)parity; /* port 1 character width (8 bits) */
458 data[3] = (unsigned short)(stopbits - 1); /* port 1 stop bits (1 stopbit) */
459 data[4] = 0; /* port 1 parity (none) */
460 data[5] = (unsigned short)(round(log((double)speed / 300) / M_LN2) + 1); /* port 1 speed */
461 data[14] = zodiac_checksum(data, 14);
463 (void)zodiac_spew(session, 1330, data, 15);
464 return true; /* it would be nice to error-check this */
466 #endif /* ALLOW_RECONFIGURE */
469 static double zodiac_ntp_offset(struct gps_device_t *session)
471 /* Removing/changing the magic number below is likely to disturb
472 * the handling of the 1pps signal from the gps device. The regression
473 * tests and simple gps applications do not detect this. A live test
474 * with the 1pps signal active is required. */
477 #endif /* NTPSHM_ENABLE */
479 /* this is everything we export */
481 const struct gps_type_t zodiac_binary =
483 .type_name = "Zodiac binary", /* full name of type */
484 .packet_type = ZODIAC_PACKET, /* associated lexer packet type */
485 .trigger = NULL, /* no trigger */
486 .channels = 12, /* consumer-grade GPS */
487 .probe_detect = NULL, /* no probe */
488 .get_packet = generic_get, /* use the generic packet getter */
489 .parse_packet = zodiac_analyze, /* parse message packets */
490 .rtcm_writer = zodiac_send_rtcm, /* send DGPS correction */
491 .event_hook = NULL, /* no configuration */
492 #ifdef ALLOW_RECONFIGURE
493 .speed_switcher = zodiac_speed_switch,/* we can change baud rate */
494 .mode_switcher = NULL, /* no mode switcher */
495 .rate_switcher = NULL, /* no sample-rate switcher */
496 .min_cycle = 1, /* not relevant, no rate switch */
497 #endif /* ALLOW_RECONFIGURE */
498 #ifdef ALLOW_CONTROLSEND
499 .control_send = zodiac_control_send, /* for gpsctl and friends */
500 #endif /* ALLOW_CONTROLSEND */
502 .ntp_offset = zodiac_ntp_offset, /* compute NTO fudge factor */
503 #endif /* NTPSHM_ENABLE */
507 #endif /* ZODIAC_ENABLE */