1 /****************************************************************************
2
3 NAME
4 gpsd_json.c - move data between in-core and JSON structures
5
6 DESCRIPTION
7 These are functions (used only by the daemon) to dump the contents
8 of various core data structures in JSON.
9
10 PERMISSIONS
11 Written by Eric S. Raymond, 2009
12 This file is Copyright (c) 2010-2019 by the GPSD project
13 SPDX-License-Identifier: BSD-2-clause
14
15 ***************************************************************************/
16
17 #include "gpsd_config.h" /* must be before all includes */
18
19 #include <assert.h>
20 #include <ctype.h>
21 #include <math.h>
22 #include <stdio.h>
23 #include <string.h> /* for strcat(), strlcpy() */
24
25 #include "gpsd.h"
26 #include "bits.h"
27 #include "strfuncs.h"
28
29 #ifdef SOCKET_EXPORT_ENABLE
30 #include "gps_json.h"
31 #include "timespec.h"
32 #include "revision.h"
33
34 /* *INDENT-OFF* */
35 #define JSON_BOOL(x) ((x)?"true":"false")
36
37 /*
38 * Manifest names for the gnss_type enum - must be kept synced with it.
39 * Also, masks so we can tell what packet types correspond to each class.
40 */
41 /* the map of device class names */
42 struct classmap_t {
43 char *name;
44 int typemask;
45 int packetmask;
46 };
47 #define CLASSMAP_NITEMS 5
48
49 struct classmap_t classmap[CLASSMAP_NITEMS] = {
50 /* name typemask packetmask */
51 {"ANY", 0, 0},
52 {"GPS", SEEN_GPS, GPS_TYPEMASK},
53 {"RTCM2", SEEN_RTCM2, PACKET_TYPEMASK(RTCM2_PACKET)},
54 {"RTCM3", SEEN_RTCM3, PACKET_TYPEMASK(RTCM3_PACKET)},
55 {"AIS", SEEN_AIS, PACKET_TYPEMASK(AIVDM_PACKET)},
56 };
57 /* *INDENT-ON* */
58
json_stringify(char * to,size_t len,const char * from)59 char *json_stringify(char *to,
60 size_t len,
61 const char *from)
62 /* escape double quotes and control characters inside a JSON string */
63 {
64 const char *sp;
65 char *tp;
66
67 tp = to;
68 /*
69 * The limit is len-6 here because we need to be leave room for
70 * each character to generate an up to 6-character Java-style
71 * escape
72 */
73 for (sp = from; *sp != '\0' && ((tp - to) < ((int)len - 6)); sp++) {
74 if (!isascii((unsigned char) *sp) || iscntrl((unsigned char) *sp)) {
75 *tp++ = '\\';
76 switch (*sp) {
77 case '\b':
78 *tp++ = 'b';
79 break;
80 case '\f':
81 *tp++ = 'f';
82 break;
83 case '\n':
84 *tp++ = 'n';
85 break;
86 case '\r':
87 *tp++ = 'r';
88 break;
89 case '\t':
90 *tp++ = 't';
91 break;
92 default:
93 /* ugh, we'd prefer a C-style escape here, but this is JSON */
94 /* http://www.ietf.org/rfc/rfc4627.txt
95 * section 2.5, escape is \uXXXX */
96 /* don't forget the NUL in the output count! */
97 (void)snprintf(tp, 6, "u%04x", 0x00ff & (unsigned int)*sp);
98 tp += strlen(tp);
99 }
100 } else {
101 if (*sp == '"' || *sp == '\\')
102 *tp++ = '\\';
103 *tp++ = *sp;
104 }
105 }
106 *tp = '\0';
107
108 return to;
109 }
110
json_version_dump(char * reply,size_t replylen)111 void json_version_dump( char *reply, size_t replylen)
112 {
113 (void)snprintf(reply, replylen,
114 "{\"class\":\"VERSION\",\"release\":\"%s\",\"rev\":\"%s\","
115 "\"proto_major\":%d,\"proto_minor\":%d}\r\n",
116 VERSION, REVISION,
117 GPSD_PROTO_MAJOR_VERSION, GPSD_PROTO_MINOR_VERSION);
118 }
119
json_tpv_dump(const struct gps_device_t * session,const struct gps_policy_t * policy,char * reply,size_t replylen)120 void json_tpv_dump(const struct gps_device_t *session,
121 const struct gps_policy_t *policy,
122 char *reply, size_t replylen)
123 {
124 const struct gps_data_t *gpsdata = &session->gpsdata;
125
126 assert(replylen > sizeof(char *));
127 (void)strlcpy(reply, "{\"class\":\"TPV\",", replylen);
128 if (gpsdata->dev.path[0] != '\0')
129 /* Note: Assumes /dev paths are always plain ASCII */
130 str_appendf(reply, replylen, "\"device\":\"%s\",", gpsdata->dev.path);
131 if (STATUS_DGPS_FIX <= gpsdata->status) {
132 /* to save rebuilding all the regressions, skip NO_FIX and FIX */
133 str_appendf(reply, replylen, "\"status\":%d,", gpsdata->status);
134 }
135 str_appendf(reply, replylen, "\"mode\":%d,", gpsdata->fix.mode);
136 if (0 < gpsdata->fix.time.tv_sec) {
137 char tbuf[JSON_DATE_MAX+1];
138 str_appendf(reply, replylen,
139 "\"time\":\"%s\",",
140 timespec_to_iso8601(gpsdata->fix.time,
141 tbuf, sizeof(tbuf)));
142 }
143 if (LEAP_SECOND_VALID == (session->context->valid & LEAP_SECOND_VALID)) {
144 str_appendf(reply, replylen, "\"leapseconds\":%d,",
145 session->context->leap_seconds);
146 }
147 if (isfinite(gpsdata->fix.ept) != 0)
148 str_appendf(reply, replylen, "\"ept\":%.3f,", gpsdata->fix.ept);
149 /*
150 * Suppressing TPV fields that would be invalid because the fix
151 * quality doesn't support them is nice for cutting down on the
152 * volume of meaningless output, but the real reason to do it is
153 * that we've observed that geodetic fix computation is unstable
154 * in a way that tends to change low-order digits in invalid
155 * fixes. Dumping these tends to cause cross-architecture failures
156 * in the regression tests. This effect has been seen on SiRF-II
157 * chips, which are quite common.
158 */
159 if (gpsdata->fix.mode >= MODE_2D) {
160 double altitude = NAN;
161
162 if (isfinite(gpsdata->fix.latitude) != 0)
163 str_appendf(reply, replylen,
164 "\"lat\":%.9f,", gpsdata->fix.latitude);
165 if (isfinite(gpsdata->fix.longitude) != 0)
166 str_appendf(reply, replylen,
167 "\"lon\":%.9f,", gpsdata->fix.longitude);
168 if (0 != isfinite(gpsdata->fix.altHAE)) {
169 altitude = gpsdata->fix.altHAE;
170 str_appendf(reply, replylen,
171 "\"altHAE\":%.3f,", gpsdata->fix.altHAE);
172 }
173 if (0 != isfinite(gpsdata->fix.altMSL)) {
174 altitude = gpsdata->fix.altMSL;
175 str_appendf(reply, replylen,
176 "\"altMSL\":%.3f,", gpsdata->fix.altMSL);
177 }
178 if (0 != isfinite(altitude)) {
179 // DEPRECATED, undefined
180 str_appendf(reply, replylen,
181 "\"alt\":%.3f,", altitude);
182 }
183
184 if (isfinite(gpsdata->fix.epx) != 0)
185 str_appendf(reply, replylen, "\"epx\":%.3f,", gpsdata->fix.epx);
186 if (isfinite(gpsdata->fix.epy) != 0)
187 str_appendf(reply, replylen, "\"epy\":%.3f,", gpsdata->fix.epy);
188 if (isfinite(gpsdata->fix.epv) != 0)
189 str_appendf(reply, replylen, "\"epv\":%.3f,", gpsdata->fix.epv);
190 if (isfinite(gpsdata->fix.track) != 0)
191 str_appendf(reply, replylen, "\"track\":%.4f,", gpsdata->fix.track);
192 if (0 != isfinite(gpsdata->fix.magnetic_track))
193 str_appendf(reply, replylen, "\"magtrack\":%.4f,",
194 gpsdata->fix.magnetic_track);
195 if (0 != isfinite(gpsdata->fix.magnetic_var))
196 str_appendf(reply, replylen, "\"magvar\":%.1f,",
197 gpsdata->fix.magnetic_var);
198 if (isfinite(gpsdata->fix.speed) != 0)
199 str_appendf(reply, replylen, "\"speed\":%.3f,", gpsdata->fix.speed);
200 if ((gpsdata->fix.mode >= MODE_3D) && isfinite(gpsdata->fix.climb) != 0)
201 str_appendf(reply, replylen, "\"climb\":%.3f,", gpsdata->fix.climb);
202 if (isfinite(gpsdata->fix.epd) != 0)
203 str_appendf(reply, replylen, "\"epd\":%.4f,", gpsdata->fix.epd);
204 if (isfinite(gpsdata->fix.eps) != 0)
205 str_appendf(reply, replylen, "\"eps\":%.2f,", gpsdata->fix.eps);
206 if (gpsdata->fix.mode >= MODE_3D) {
207 if (isfinite(gpsdata->fix.epc) != 0)
208 str_appendf(reply, replylen, "\"epc\":%.2f,", gpsdata->fix.epc);
209 /* ECEF is in meters, so %.3f is millimeter resolution */
210 if (0 != isfinite(gpsdata->fix.ecef.x))
211 str_appendf(reply, replylen, "\"ecefx\":%.2f,",
212 gpsdata->fix.ecef.x);
213 if (0 != isfinite(gpsdata->fix.ecef.y))
214 str_appendf(reply, replylen, "\"ecefy\":%.2f,",
215 gpsdata->fix.ecef.y);
216 if (0 != isfinite(gpsdata->fix.ecef.z))
217 str_appendf(reply, replylen, "\"ecefz\":%.2f,",
218 gpsdata->fix.ecef.z);
219 if (0 != isfinite(gpsdata->fix.ecef.vx))
220 str_appendf(reply, replylen, "\"ecefvx\":%.2f,",
221 gpsdata->fix.ecef.vx);
222 if (0 != isfinite(gpsdata->fix.ecef.vy))
223 str_appendf(reply, replylen, "\"ecefvy\":%.2f,",
224 gpsdata->fix.ecef.vy);
225 if (0 != isfinite(gpsdata->fix.ecef.vz))
226 str_appendf(reply, replylen, "\"ecefvz\":%.2f,",
227 gpsdata->fix.ecef.vz);
228 if (0 != isfinite(gpsdata->fix.ecef.pAcc))
229 str_appendf(reply, replylen, "\"ecefpAcc\":%.2f,",
230 gpsdata->fix.ecef.pAcc);
231 if (0 != isfinite(gpsdata->fix.ecef.vAcc))
232 str_appendf(reply, replylen, "\"ecefvAcc\":%.2f,",
233 gpsdata->fix.ecef.vAcc);
234 /* NED is in meters, so %.3f is millimeter resolution */
235 if (0 != isfinite(gpsdata->fix.NED.relPosN))
236 str_appendf(reply, replylen, "\"relN\":%.3f,",
237 gpsdata->fix.NED.relPosN);
238 if (0 != isfinite(gpsdata->fix.NED.relPosE))
239 str_appendf(reply, replylen, "\"relE\":%.3f,",
240 gpsdata->fix.NED.relPosE);
241 if (0 != isfinite(gpsdata->fix.NED.relPosD))
242 str_appendf(reply, replylen, "\"relD\":%.3f,",
243 gpsdata->fix.NED.relPosD);
244 if (0 != isfinite(gpsdata->fix.NED.velN))
245 str_appendf(reply, replylen, "\"velN\":%.3f,",
246 gpsdata->fix.NED.velN);
247 if (0 != isfinite(gpsdata->fix.NED.velE))
248 str_appendf(reply, replylen, "\"velE\":%.3f,",
249 gpsdata->fix.NED.velE);
250 if (0 != isfinite(gpsdata->fix.NED.velD))
251 str_appendf(reply, replylen, "\"velD\":%.3f,",
252 gpsdata->fix.NED.velD);
253 if (0 != isfinite(gpsdata->fix.geoid_sep))
254 str_appendf(reply, replylen, "\"geoidSep\":%.3f,",
255 gpsdata->fix.geoid_sep);
256 }
257 if (policy->timing) {
258 char rtime_str[TIMESPEC_LEN];
259 char ts_buf[TIMESPEC_LEN];
260 struct timespec rtime_tmp;
261 (void)clock_gettime(CLOCK_REALTIME, &rtime_tmp);
262 str_appendf(reply, replylen, "\"rtime\":%s,",
263 timespec_str(&rtime_tmp, rtime_str,
264 sizeof(rtime_str)));
265 if (session->pps_thread.ppsout_count) {
266 char ts_str[TIMESPEC_LEN];
267 struct timedelta_t timedelta;
268 /* ugh - de-consting this might get us in trouble someday */
269 pps_thread_ppsout(&((struct gps_device_t *)session)->pps_thread,
270 &timedelta);
271 str_appendf(reply, replylen, "\"pps\":%s,",
272 timespec_str(&timedelta.clock, ts_str,
273 sizeof(ts_str)));
274 /* TODO: add PPS precision to JSON output */
275 }
276 str_appendf(reply, replylen,
277 "\"sor\":%s,\"chars\":%lu,\"sats\":%2d,"
278 "\"week\":%u,\"tow\":%lld.%03ld,\"rollovers\":%d",
279 timespec_str(&session->sor, ts_buf, sizeof(ts_buf)),
280 session->chars,
281 gpsdata->satellites_used,
282 session->context->gps_week,
283 (long long)session->context->gps_tow.tv_sec,
284 session->context->gps_tow.tv_nsec / 1000000L,
285 session->context->rollovers);
286 }
287 /* at the end because it is new and microjson chokes on new items */
288 if (0 != isfinite(gpsdata->fix.eph))
289 str_appendf(reply, replylen, "\"eph\":%.3f,", gpsdata->fix.eph);
290 if (0 != isfinite(gpsdata->fix.sep))
291 str_appendf(reply, replylen, "\"sep\":%.3f,", gpsdata->fix.sep);
292 if ('\0' != gpsdata->fix.datum[0])
293 str_appendf(reply, replylen, "\"datum\":\"%.40s\",",
294 gpsdata->fix.datum);
295 if (0 != isfinite(gpsdata->fix.depth))
296 str_appendf(reply, replylen,
297 "\"depth\":%.3f,", gpsdata->fix.depth);
298 if (0 != isfinite(gpsdata->fix.dgps_age) &&
299 0 <= gpsdata->fix.dgps_station) {
300 /* both, or none */
301 str_appendf(reply, replylen,
302 "\"dgpsAge\":%.1f,", gpsdata->fix.dgps_age);
303 str_appendf(reply, replylen,
304 "\"dgpsSta\":%d,", gpsdata->fix.dgps_station);
305 }
306 }
307 str_rstrip_char(reply, ',');
308 (void)strlcat(reply, "}\r\n", replylen);
309 }
310
json_noise_dump(const struct gps_data_t * gpsdata,char * reply,size_t replylen)311 void json_noise_dump(const struct gps_data_t *gpsdata,
312 char *reply, size_t replylen)
313 {
314 assert(replylen > sizeof(char *));
315 (void)strlcpy(reply, "{\"class\":\"GST\",", replylen);
316 if (gpsdata->dev.path[0] != '\0')
317 str_appendf(reply, replylen, "\"device\":\"%s\",", gpsdata->dev.path);
318 if (0 < gpsdata->gst.utctime.tv_sec) {
319 char tbuf[JSON_DATE_MAX+1];
320 str_appendf(reply, replylen,
321 "\"time\":\"%s\",",
322 timespec_to_iso8601(gpsdata->gst.utctime,
323 tbuf, sizeof(tbuf)));
324 }
325 #define ADD_GST_FIELD(tag, field) do { \
326 if (isfinite(gpsdata->gst.field) != 0) \
327 str_appendf(reply, replylen, "\"" tag "\":%.3f,", gpsdata->gst.field); \
328 } while(0)
329
330 ADD_GST_FIELD("rms", rms_deviation);
331 ADD_GST_FIELD("major", smajor_deviation);
332 ADD_GST_FIELD("minor", sminor_deviation);
333 ADD_GST_FIELD("orient", smajor_orientation);
334 ADD_GST_FIELD("lat", lat_err_deviation);
335 ADD_GST_FIELD("lon", lon_err_deviation);
336 ADD_GST_FIELD("alt", alt_err_deviation);
337
338 #undef ADD_GST_FIELD
339
340 str_rstrip_char(reply, ',');
341 (void)strlcat(reply, "}\r\n", replylen);
342 }
343
json_sky_dump(const struct gps_data_t * datap,char * reply,size_t replylen)344 void json_sky_dump(const struct gps_data_t *datap,
345 char *reply, size_t replylen)
346 {
347 int i, reported = 0;
348
349 assert(replylen > sizeof(char *));
350 (void)strlcpy(reply, "{\"class\":\"SKY\",", replylen);
351 if (datap->dev.path[0] != '\0')
352 str_appendf(reply, replylen, "\"device\":\"%s\",", datap->dev.path);
353 if (0 < datap->skyview_time.tv_sec) {
354 char tbuf[JSON_DATE_MAX+1];
355 str_appendf(reply, replylen,
356 "\"time\":\"%s\",",
357 timespec_to_iso8601(datap->skyview_time,
358 tbuf, sizeof(tbuf)));
359 }
360 if (isfinite(datap->dop.xdop) != 0)
361 str_appendf(reply, replylen, "\"xdop\":%.2f,", datap->dop.xdop);
362 if (isfinite(datap->dop.ydop) != 0)
363 str_appendf(reply, replylen, "\"ydop\":%.2f,", datap->dop.ydop);
364 if (isfinite(datap->dop.vdop) != 0)
365 str_appendf(reply, replylen, "\"vdop\":%.2f,", datap->dop.vdop);
366 if (isfinite(datap->dop.tdop) != 0)
367 str_appendf(reply, replylen, "\"tdop\":%.2f,", datap->dop.tdop);
368 if (isfinite(datap->dop.hdop) != 0)
369 str_appendf(reply, replylen, "\"hdop\":%.2f,", datap->dop.hdop);
370 if (isfinite(datap->dop.gdop) != 0)
371 str_appendf(reply, replylen, "\"gdop\":%.2f,", datap->dop.gdop);
372 if (isfinite(datap->dop.pdop) != 0)
373 str_appendf(reply, replylen, "\"pdop\":%.2f,", datap->dop.pdop);
374 /* insurance against flaky drivers */
375 for (i = 0; i < datap->satellites_visible; i++)
376 if (datap->skyview[i].PRN)
377 reported++;
378 if (reported) {
379 (void)strlcat(reply, "\"satellites\":[", replylen);
380 for (i = 0; i < reported; i++) {
381 if (datap->skyview[i].PRN) {
382 str_appendf(reply, replylen, "{\"PRN\":%d,",
383 datap->skyview[i].PRN);
384 if (0 != isfinite(datap->skyview[i].elevation) &&
385 90 >= fabs(datap->skyview[i].elevation)) {
386 str_appendf(reply, replylen, "\"el\":%.1f,",
387 datap->skyview[i].elevation);
388 }
389 if (0 != isfinite(datap->skyview[i].azimuth) &&
390 0 <= fabs(datap->skyview[i].azimuth) &&
391 359 >= fabs(datap->skyview[i].azimuth)) {
392 str_appendf(reply, replylen, "\"az\":%.1f,",
393 datap->skyview[i].azimuth);
394 }
395 if (0 != isfinite(datap->skyview[i].ss)) {
396 str_appendf(reply, replylen, "\"ss\":%.1f,",
397 datap->skyview[i].ss);
398 }
399 str_appendf(reply, replylen,
400 "\"used\":%s",
401 datap->skyview[i].used ? "true" : "false");
402 if (0 != datap->skyview[i].svid) {
403 str_appendf(reply, replylen,
404 ",\"gnssid\":%d,\"svid\":%d",
405 datap->skyview[i].gnssid,
406 datap->skyview[i].svid);
407 }
408 if (0 != datap->skyview[i].sigid) {
409 str_appendf(reply, replylen,
410 ",\"sigid\":%d", datap->skyview[i].sigid);
411 }
412 if (GNSSID_GLO == datap->skyview[i].gnssid &&
413 0 <= datap->skyview[i].freqid &&
414 16 >= datap->skyview[i].freqid) {
415 str_appendf(reply, replylen,
416 ",\"freqid\":%d", datap->skyview[i].freqid);
417 }
418 if (SAT_HEALTH_UNK != datap->skyview[i].health) {
419 str_appendf(reply, replylen,
420 ",\"health\":%d", datap->skyview[i].health);
421 }
422 (void)strlcat(reply, "},", replylen);
423 }
424 }
425 str_rstrip_char(reply, ',');
426 (void)strlcat(reply, "]", replylen);
427 }
428 str_rstrip_char(reply, ',');
429 (void)strlcat(reply, "}\r\n", replylen);
430 }
431
json_device_dump(const struct gps_device_t * device,char * reply,size_t replylen)432 void json_device_dump(const struct gps_device_t *device,
433 char *reply, size_t replylen)
434 {
435 struct classmap_t *cmp;
436 char buf1[JSON_VAL_MAX * 2 + 1];
437
438 (void)strlcpy(reply, "{\"class\":\"DEVICE\",\"path\":\"", replylen);
439 (void)strlcat(reply, device->gpsdata.dev.path, replylen);
440 (void)strlcat(reply, "\",", replylen);
441 if (device->device_type != NULL) {
442 (void)strlcat(reply, "\"driver\":\"", replylen);
443 (void)strlcat(reply, device->device_type->type_name, replylen);
444 (void)strlcat(reply, "\",", replylen);
445 }
446 if (device->subtype[0] != '\0') {
447 (void)strlcat(reply, "\"subtype\":\"", replylen);
448 (void)strlcat(reply,
449 json_stringify(buf1, sizeof(buf1), device->subtype),
450 replylen);
451 (void)strlcat(reply, "\",", replylen);
452 }
453 if (device->subtype1[0] != '\0') {
454 (void)strlcat(reply, "\"subtype1\":\"", replylen);
455 (void)strlcat(reply,
456 json_stringify(buf1, sizeof(buf1), device->subtype1),
457 replylen);
458 (void)strlcat(reply, "\",", replylen);
459 }
460 /*
461 * There's an assumption here: Anything that we type service_sensor is
462 * a serial device with the usual control parameters.
463 */
464 if (0 < device->gpsdata.online.tv_sec) {
465 /* odd, using online, not activated, time */
466 str_appendf(reply, replylen, "\"activated\":\"%s\",",
467 timespec_to_iso8601(device->gpsdata.online,
468 buf1, sizeof(buf1)));
469 if (device->observed != 0) {
470 int mask = 0;
471 for (cmp = classmap; cmp < classmap + NITEMS(classmap); cmp++)
472 if ((device->observed & cmp->packetmask) != 0)
473 mask |= cmp->typemask;
474 if (mask != 0)
475 str_appendf(reply, replylen, "\"flags\":%d,", mask);
476 }
477 if (device->servicetype == service_sensor) {
478 /* speed can be 0 if the device is not currently active */
479 speed_t speed = gpsd_get_speed(device);
480 if (speed != 0)
481 str_appendf(reply, replylen,
482 "\"native\":%d,\"bps\":%d,\"parity\":\"%c\","
483 "\"stopbits\":%u,\"cycle\":%lld.%02ld,",
484 device->gpsdata.dev.driver_mode,
485 (int)speed,
486 device->gpsdata.dev.parity,
487 device->gpsdata.dev.stopbits,
488 (long long)device->gpsdata.dev.cycle.tv_sec,
489 device->gpsdata.dev.cycle.tv_nsec / 10000000);
490 #ifdef RECONFIGURE_ENABLE
491 if (device->device_type != NULL
492 && device->device_type->rate_switcher != NULL)
493 str_appendf(reply, replylen,
494 "\"mincycle\":%lld.%02ld,",
495 (long long)device->device_type->min_cycle.tv_sec,
496 device->device_type->min_cycle.tv_nsec /
497 10000000);
498 #endif /* RECONFIGURE_ENABLE */
499 }
500 }
501 str_rstrip_char(reply, ',');
502 (void)strlcat(reply, "}\r\n", replylen);
503 }
504
json_watch_dump(const struct gps_policy_t * ccp,char * reply,size_t replylen)505 void json_watch_dump(const struct gps_policy_t *ccp,
506 char *reply, size_t replylen)
507 {
508 (void)snprintf(reply, replylen,
509 "{\"class\":\"WATCH\",\"enable\":%s,\"json\":%s,"
510 "\"nmea\":%s,\"raw\":%d,\"scaled\":%s,\"timing\":%s,"
511 "\"split24\":%s,\"pps\":%s,",
512 ccp->watcher ? "true" : "false",
513 ccp->json ? "true" : "false",
514 ccp->nmea ? "true" : "false",
515 ccp->raw,
516 ccp->scaled ? "true" : "false",
517 ccp->timing ? "true" : "false",
518 ccp->split24 ? "true" : "false",
519 ccp->pps ? "true" : "false");
520 if (ccp->devpath[0] != '\0')
521 str_appendf(reply, replylen, "\"device\":\"%s\",", ccp->devpath);
522 str_rstrip_char(reply, ',');
523 (void)strlcat(reply, "}\r\n", replylen);
524 }
525
json_subframe_dump(const struct gps_data_t * datap,char buf[],size_t buflen)526 void json_subframe_dump(const struct gps_data_t *datap,
527 char buf[], size_t buflen)
528 {
529 const struct subframe_t *subframe = &datap->subframe;
530 const bool scaled = datap->policy.scaled;
531
532 (void)snprintf(buf, buflen, "{\"class\":\"SUBFRAME\",\"device\":\"%s\","
533 "\"tSV\":%u,\"TOW17\":%u,\"frame\":%u,\"scaled\":%s",
534 datap->dev.path,
535 (unsigned int)subframe->tSVID,
536 (unsigned int)subframe->TOW17,
537 (unsigned int)subframe->subframe_num,
538 JSON_BOOL(scaled));
539
540 if ( 1 == subframe->subframe_num ) {
541 if (scaled) {
542 str_appendf(buf, buflen,
543 ",\"EPHEM1\":{\"WN\":%u,\"IODC\":%u,\"L2\":%u,"
544 "\"ura\":%u,\"hlth\":%u,\"L2P\":%u,\"Tgd\":%g,"
545 "\"toc\":%lu,\"af2\":%.4g,\"af1\":%.6e,\"af0\":%.7e}",
546 (unsigned int)subframe->sub1.WN,
547 (unsigned int)subframe->sub1.IODC,
548 (unsigned int)subframe->sub1.l2,
549 subframe->sub1.ura,
550 subframe->sub1.hlth,
551 (unsigned int)subframe->sub1.l2p,
552 subframe->sub1.d_Tgd,
553 (unsigned long)subframe->sub1.l_toc,
554 subframe->sub1.d_af2,
555 subframe->sub1.d_af1,
556 subframe->sub1.d_af0);
557 } else {
558 str_appendf(buf, buflen,
559 ",\"EPHEM1\":{\"WN\":%u,\"IODC\":%u,\"L2\":%u,"
560 "\"ura\":%u,\"hlth\":%u,\"L2P\":%u,\"Tgd\":%d,"
561 "\"toc\":%u,\"af2\":%ld,\"af1\":%d,\"af0\":%d}",
562 (unsigned int)subframe->sub1.WN,
563 (unsigned int)subframe->sub1.IODC,
564 (unsigned int)subframe->sub1.l2,
565 subframe->sub1.ura,
566 subframe->sub1.hlth,
567 (unsigned int)subframe->sub1.l2p,
568 (int)subframe->sub1.Tgd,
569 (unsigned int)subframe->sub1.toc,
570 (long)subframe->sub1.af2,
571 (int)subframe->sub1.af1,
572 (int)subframe->sub1.af0);
573 }
574 } else if ( 2 == subframe->subframe_num ) {
575 if (scaled) {
576 str_appendf(buf, buflen,
577 ",\"EPHEM2\":{\"IODE\":%u,\"Crs\":%.6e,\"deltan\":%.6e,"
578 "\"M0\":%.11e,\"Cuc\":%.6e,\"e\":%f,\"Cus\":%.6e,"
579 "\"sqrtA\":%.11g,\"toe\":%lu,\"FIT\":%u,\"AODO\":%u}",
580 (unsigned int)subframe->sub2.IODE,
581 subframe->sub2.d_Crs,
582 subframe->sub2.d_deltan,
583 subframe->sub2.d_M0,
584 subframe->sub2.d_Cuc,
585 subframe->sub2.d_eccentricity,
586 subframe->sub2.d_Cus,
587 subframe->sub2.d_sqrtA,
588 (unsigned long)subframe->sub2.l_toe,
589 (unsigned int)subframe->sub2.fit,
590 (unsigned int)subframe->sub2.u_AODO);
591 } else {
592 str_appendf(buf, buflen,
593 ",\"EPHEM2\":{\"IODE\":%u,\"Crs\":%d,\"deltan\":%d,"
594 "\"M0\":%ld,\"Cuc\":%d,\"e\":%ld,\"Cus\":%d,"
595 "\"sqrtA\":%lu,\"toe\":%lu,\"FIT\":%u,\"AODO\":%u}",
596 (unsigned int)subframe->sub2.IODE,
597 (int)subframe->sub2.Crs,
598 (int)subframe->sub2.deltan,
599 (long)subframe->sub2.M0,
600 (int)subframe->sub2.Cuc,
601 (long)subframe->sub2.e,
602 (int)subframe->sub2.Cus,
603 (unsigned long)subframe->sub2.sqrtA,
604 (unsigned long)subframe->sub2.toe,
605 (unsigned int)subframe->sub2.fit,
606 (unsigned int)subframe->sub2.AODO);
607 }
608 } else if ( 3 == subframe->subframe_num ) {
609 if (scaled) {
610 str_appendf(buf, buflen,
611 ",\"EPHEM3\":{\"IODE\":%3u,\"IDOT\":%.6g,\"Cic\":%.6e,"
612 "\"Omega0\":%.11e,\"Cis\":%.7g,\"i0\":%.11e,\"Crc\":%.7g,"
613 "\"omega\":%.11e,\"Omegad\":%.6e}",
614 (unsigned int)subframe->sub3.IODE,
615 subframe->sub3.d_IDOT,
616 subframe->sub3.d_Cic,
617 subframe->sub3.d_Omega0,
618 subframe->sub3.d_Cis,
619 subframe->sub3.d_i0,
620 subframe->sub3.d_Crc,
621 subframe->sub3.d_omega,
622 subframe->sub3.d_Omegad );
623 } else {
624 str_appendf(buf, buflen,
625 ",\"EPHEM3\":{\"IODE\":%u,\"IDOT\":%u,\"Cic\":%u,"
626 "\"Omega0\":%ld,\"Cis\":%d,\"i0\":%ld,\"Crc\":%d,"
627 "\"omega\":%ld,\"Omegad\":%ld}",
628 (unsigned int)subframe->sub3.IODE,
629 (unsigned int)subframe->sub3.IDOT,
630 (unsigned int)subframe->sub3.Cic,
631 (long int)subframe->sub3.Omega0,
632 (int)subframe->sub3.Cis,
633 (long int)subframe->sub3.i0,
634 (int)subframe->sub3.Crc,
635 (long int)subframe->sub3.omega,
636 (long int)subframe->sub3.Omegad );
637 }
638 } else if ( subframe->is_almanac ) {
639 if (scaled) {
640 str_appendf(buf, buflen,
641 ",\"ALMANAC\":{\"ID\":%d,\"Health\":%u,"
642 "\"e\":%g,\"toa\":%lu,"
643 "\"deltai\":%.10e,\"Omegad\":%.5e,\"sqrtA\":%.10g,"
644 "\"Omega0\":%.10e,\"omega\":%.10e,\"M0\":%.11e,"
645 "\"af0\":%.5e,\"af1\":%.5e}",
646 (int)subframe->sub5.almanac.sv,
647 (unsigned int)subframe->sub5.almanac.svh,
648 subframe->sub5.almanac.d_eccentricity,
649 (unsigned long)subframe->sub5.almanac.l_toa,
650 subframe->sub5.almanac.d_deltai,
651 subframe->sub5.almanac.d_Omegad,
652 subframe->sub5.almanac.d_sqrtA,
653 subframe->sub5.almanac.d_Omega0,
654 subframe->sub5.almanac.d_omega,
655 subframe->sub5.almanac.d_M0,
656 subframe->sub5.almanac.d_af0,
657 subframe->sub5.almanac.d_af1);
658 } else {
659 str_appendf(buf, buflen,
660 ",\"ALMANAC\":{\"ID\":%d,\"Health\":%u,"
661 "\"e\":%u,\"toa\":%u,"
662 "\"deltai\":%d,\"Omegad\":%d,\"sqrtA\":%lu,"
663 "\"Omega0\":%ld,\"omega\":%ld,\"M0\":%ld,"
664 "\"af0\":%d,\"af1\":%d}",
665 (int)subframe->sub5.almanac.sv,
666 (unsigned int)subframe->sub5.almanac.svh,
667 (unsigned int)subframe->sub5.almanac.e,
668 (unsigned int)subframe->sub5.almanac.toa,
669 (int)subframe->sub5.almanac.deltai,
670 (int)subframe->sub5.almanac.Omegad,
671 (unsigned long)subframe->sub5.almanac.sqrtA,
672 (long)subframe->sub5.almanac.Omega0,
673 (long)subframe->sub5.almanac.omega,
674 (long)subframe->sub5.almanac.M0,
675 (int)subframe->sub5.almanac.af0,
676 (int)subframe->sub5.almanac.af1);
677 }
678 } else if ( 4 == subframe->subframe_num ) {
679 str_appendf(buf, buflen,
680 ",\"pageid\":%u",
681 (unsigned int)subframe->pageid);
682 switch (subframe->pageid ) {
683 case 13:
684 case 52:
685 {
686 int i;
687 /* decoding of ERD to SV is non trivial and not done yet */
688 str_appendf(buf, buflen,
689 ",\"ERD\":{\"ai\":%u,", subframe->sub4_13.ai);
690
691 /* 1-index loop to construct json, rather than giant snprintf */
692 for(i = 1 ; i <= 30; i++){
693 str_appendf(buf, buflen,
694 "\"ERD%d\":%d,", i, subframe->sub4_13.ERD[i]);
695 }
696 str_rstrip_char(buf, ',');
697 str_appendf(buf, buflen, "}");
698 break;
699 }
700 case 55:
701 /* JSON is UTF-8. double quote, backslash and
702 * control charactores (U+0000 through U+001F).must be
703 * escaped. */
704 /* system message can be 24 bytes, JSON can escape all
705 * chars so up to 24*6 long. */
706
707 {
708 char buf1[25 * 6];
709 (void)json_stringify(buf1, sizeof(buf1), subframe->sub4_17.str);
710 str_appendf(buf, buflen,
711 ",\"system_message\":\"%.144s\"", buf1);
712 }
713 break;
714 case 56:
715 if (scaled) {
716 str_appendf(buf, buflen,
717 ",\"IONO\":{\"a0\":%.5g,\"a1\":%.5g,\"a2\":%.5g,"
718 "\"a3\":%.5g,\"b0\":%.5g,\"b1\":%.5g,\"b2\":%.5g,"
719 "\"b3\":%.5g,\"A1\":%.11e,\"A0\":%.11e,\"tot\":%lld,"
720 "\"WNt\":%u,\"ls\":%d,\"WNlsf\":%u,\"DN\":%u,"
721 "\"lsf\":%d}",
722 subframe->sub4_18.d_alpha0,
723 subframe->sub4_18.d_alpha1,
724 subframe->sub4_18.d_alpha2,
725 subframe->sub4_18.d_alpha3,
726 subframe->sub4_18.d_beta0,
727 subframe->sub4_18.d_beta1,
728 subframe->sub4_18.d_beta2,
729 subframe->sub4_18.d_beta3,
730 subframe->sub4_18.d_A1,
731 subframe->sub4_18.d_A0,
732 (long long)subframe->sub4_18.t_tot,
733 (unsigned int)subframe->sub4_18.WNt,
734 (int)subframe->sub4_18.leap,
735 (unsigned int)subframe->sub4_18.WNlsf,
736 (unsigned int)subframe->sub4_18.DN,
737 (int)subframe->sub4_18.lsf);
738 } else {
739 str_appendf(buf, buflen,
740 ",\"IONO\":{\"a0\":%d,\"a1\":%d,\"a2\":%d,\"a3\":%d,"
741 "\"b0\":%d,\"b1\":%d,\"b2\":%d,\"b3\":%d,"
742 "\"A1\":%ld,\"A0\":%ld,\"tot\":%u,\"WNt\":%u,"
743 "\"ls\":%d,\"WNlsf\":%u,\"DN\":%u,\"lsf\":%d}",
744 (int)subframe->sub4_18.alpha0,
745 (int)subframe->sub4_18.alpha1,
746 (int)subframe->sub4_18.alpha2,
747 (int)subframe->sub4_18.alpha3,
748 (int)subframe->sub4_18.beta0,
749 (int)subframe->sub4_18.beta1,
750 (int)subframe->sub4_18.beta2,
751 (int)subframe->sub4_18.beta3,
752 (long)subframe->sub4_18.A1,
753 (long)subframe->sub4_18.A0,
754 (unsigned int)subframe->sub4_18.tot,
755 (unsigned int)subframe->sub4_18.WNt,
756 (int)subframe->sub4_18.leap,
757 (unsigned int)subframe->sub4_18.WNlsf,
758 (unsigned int)subframe->sub4_18.DN,
759 (int)subframe->sub4_18.lsf);
760 }
761 break;
762 case 25:
763 case 63:
764 {
765 int i;
766 str_appendf(buf, buflen,
767 ",\"HEALTH\":{\"data_id\":%d,",
768 (int)subframe->data_id);
769
770 /* 1-index loop to construct json, rather than giant snprintf */
771 for(i = 1 ; i <= 32; i++){
772 str_appendf(buf, buflen,
773 "\"SV%d\":%d,",
774 i, (int)subframe->sub4_25.svf[i]);
775 }
776 for(i = 0 ; i < 8; i++){ /* 0-index */
777 str_appendf(buf, buflen,
778 "\"SVH%d\":%d,",
779 i+25, (int)subframe->sub4_25.svhx[i]);
780 }
781 str_rstrip_char(buf, ',');
782 str_appendf(buf, buflen, "}");
783
784 break;
785 }
786 }
787 } else if ( 5 == subframe->subframe_num ) {
788 str_appendf(buf, buflen,
789 ",\"pageid\":%u",
790 (unsigned int)subframe->pageid);
791 if ( 51 == subframe->pageid ) {
792 int i;
793 /* subframe5, page 25 */
794 str_appendf(buf, buflen,
795 ",\"HEALTH2\":{\"toa\":%lu,\"WNa\":%u,",
796 (unsigned long)subframe->sub5_25.l_toa,
797 (unsigned int)subframe->sub5_25.WNa);
798 /* 1-index loop to construct json */
799 for(i = 1 ; i <= 24; i++){
800 str_appendf(buf, buflen,
801 "\"SV%d\":%d,",
802 i, (int)subframe->sub5_25.sv[i]);
803 }
804 str_rstrip_char(buf, ',');
805 str_appendf(buf, buflen, "}");
806
807 }
808 }
809 (void)strlcat(buf, "}\r\n", buflen);
810 }
811
812 /* RAW dump - should be good enough to make a RINEX 3 file */
json_raw_dump(const struct gps_data_t * gpsdata,char * reply,size_t replylen)813 void json_raw_dump(const struct gps_data_t *gpsdata,
814 char *reply, size_t replylen)
815 {
816 int i;
817
818 assert(replylen > sizeof(char *));
819 if (0 == gpsdata->raw.mtime.tv_sec) {
820 /* no data to dump */
821 return;
822 }
823 (void)strlcpy(reply, "{\"class\":\"RAW\",", replylen);
824 if (gpsdata->dev.path[0] != '\0')
825 str_appendf(reply, replylen, "\"device\":\"%s\",", gpsdata->dev.path);
826
827 str_appendf(reply, replylen, "\"time\":%lld,\"nsec\":%ld,\"rawdata\":[",
828 (long long)gpsdata->raw.mtime.tv_sec,
829 gpsdata->raw.mtime.tv_nsec);
830
831 for (i = 0; i < MAXCHANNELS; i++) {
832 bool comma = false;
833 if (0 == gpsdata->raw.meas[i].svid ||
834 255 == gpsdata->raw.meas[i].svid) {
835 /* skip empty and GLONASS 255 */
836 continue;
837 }
838 str_appendf(reply, replylen,
839 "{\"gnssid\":%u,\"svid\":%u,\"snr\":%u,"
840 "\"obs\":\"%s\",\"lli\":%1u,\"locktime\":%u",
841 gpsdata->raw.meas[i].gnssid, gpsdata->raw.meas[i].svid,
842 gpsdata->raw.meas[i].snr,
843 gpsdata->raw.meas[i].obs_code, gpsdata->raw.meas[i].lli,
844 gpsdata->raw.meas[i].locktime);
845 if (0 < gpsdata->raw.meas[i].sigid) {
846 str_appendf(reply, replylen, ",\"sigid\":%u",
847 gpsdata->raw.meas[i].sigid);
848 }
849 if (GNSSID_GLO == gpsdata->raw.meas[i].gnssid) {
850 str_appendf(reply, replylen, ",\"freqid\":%u",
851 gpsdata->raw.meas[i].freqid);
852 }
853 comma = true;
854
855 if (0 != isfinite(gpsdata->raw.meas[i].pseudorange) &&
856 1.0 < gpsdata->raw.meas[i].pseudorange) {
857 if (comma)
858 (void)strlcat(reply, ",", replylen);
859 str_appendf(reply, replylen, "\"pseudorange\":%f",
860 gpsdata->raw.meas[i].pseudorange);
861 comma = true;
862
863 if (0 != isfinite(gpsdata->raw.meas[i].carrierphase)) {
864 str_appendf(reply, replylen, ",\"carrierphase\":%f",
865 gpsdata->raw.meas[i].carrierphase);
866 comma = true;
867 }
868 }
869 if (0 != isfinite(gpsdata->raw.meas[i].doppler)) {
870 if (comma)
871 (void)strlcat(reply, ",", replylen);
872 str_appendf(reply, replylen, "\"doppler\":%f",
873 gpsdata->raw.meas[i].doppler);
874 comma = true;
875 }
876
877 /* L2 C/A pseudo range, RINEX C2C */
878 if (0 != isfinite(gpsdata->raw.meas[i].c2c) &&
879 1.0 < gpsdata->raw.meas[i].c2c) {
880 if (comma)
881 (void)strlcat(reply, ",", replylen);
882 str_appendf(reply, replylen, "\"c2c\":%f",
883 gpsdata->raw.meas[i].c2c);
884 comma = true;
885
886 /* L2 C/A carrier phase, RINEX L2C */
887 if (0 != isfinite(gpsdata->raw.meas[i].l2c)) {
888 if (comma)
889 (void)strlcat(reply, ",", replylen);
890 str_appendf(reply, replylen, "\"l2c\":%f",
891 gpsdata->raw.meas[i].l2c);
892 comma = true;
893 }
894 }
895 (void)strlcat(reply, "},", replylen);
896 }
897 str_rstrip_char(reply, ',');
898 (void)strlcat(reply, "]", replylen);
899
900 str_rstrip_char(reply, ',');
901 (void)strlcat(reply, "}\r\n", replylen);
902 }
903
904 #if defined(RTCM104V2_ENABLE)
json_rtcm2_dump(const struct rtcm2_t * rtcm,const char * device,char buf[],size_t buflen)905 void json_rtcm2_dump(const struct rtcm2_t *rtcm,
906 const char *device,
907 char buf[], size_t buflen)
908 /* dump the contents of a parsed RTCM104 message as JSON */
909 {
910 char buf1[JSON_VAL_MAX * 2 + 1];
911 unsigned int n;
912
913 (void)snprintf(buf, buflen, "{\"class\":\"RTCM2\",");
914 if (device != NULL && device[0] != '\0')
915 str_appendf(buf, buflen, "\"device\":\"%s\",", device);
916 str_appendf(buf, buflen,
917 "\"type\":%u,\"station_id\":%u,\"zcount\":%0.1f,"
918 "\"seqnum\":%u,\"length\":%u,\"station_health\":%u,",
919 rtcm->type, rtcm->refstaid, rtcm->zcount, rtcm->seqnum,
920 rtcm->length, rtcm->stathlth);
921
922 switch (rtcm->type) {
923 case 1:
924 case 9:
925 (void)strlcat(buf, "\"satellites\":[", buflen);
926 for (n = 0; n < rtcm->gps_ranges.nentries; n++) {
927 const struct gps_rangesat_t *rsp = &rtcm->gps_ranges.sat[n];
928 str_appendf(buf, buflen,
929 "{\"ident\":%u,\"udre\":%u,\"iod\":%u,"
930 "\"prc\":%0.3f,\"rrc\":%0.3f},",
931 rsp->ident,
932 rsp->udre, rsp->iod,
933 rsp->prc, rsp->rrc);
934 }
935 str_rstrip_char(buf, ',');
936 (void)strlcat(buf, "]", buflen);
937 break;
938
939 case 3:
940 if (rtcm->ecef.valid)
941 str_appendf(buf, buflen,
942 "\"x\":%.2f,\"y\":%.2f,\"z\":%.2f,",
943 rtcm->ecef.x, rtcm->ecef.y, rtcm->ecef.z);
944 break;
945
946 case 4:
947 if (rtcm->reference.valid) {
948 /*
949 * Beware! Needs to stay synchronized with a JSON
950 * enumeration map in the parser. This interpretation of
951 * NAVSYSTEM_GALILEO is assumed from RTCM3, it's not
952 * actually documented in RTCM 2.1.
953 */
954 static char *navsysnames[] = { "GPS", "GLONASS", "GALILEO" };
955 str_appendf(buf, buflen,
956 "\"system\":\"%s\",\"sense\":%1d,"
957 "\"datum\":\"%s\",\"dx\":%.1f,\"dy\":%.1f,"
958 "\"dz\":%.1f,",
959 rtcm->reference.system >= NITEMS(navsysnames)
960 ? "UNKNOWN"
961 : navsysnames[rtcm->reference.system],
962 rtcm->reference.sense,
963 rtcm->reference.datum,
964 rtcm->reference.dx,
965 rtcm->reference.dy, rtcm->reference.dz);
966 }
967 break;
968
969 case 5:
970 (void)strlcat(buf, "\"satellites\":[", buflen);
971 for (n = 0; n < rtcm->conhealth.nentries; n++) {
972 const struct consat_t *csp = &rtcm->conhealth.sat[n];
973 str_appendf(buf, buflen,
974 "{\"ident\":%u,\"iodl\":%s,\"health\":%1u,"
975 "\"snr\":%d,\"health_en\":%s,\"new_data\":%s,"
976 "\"los_warning\":%s,\"tou\":%u},",
977 csp->ident,
978 JSON_BOOL(csp->iodl),
979 (unsigned)csp->health,
980 csp->snr,
981 JSON_BOOL(csp->health_en),
982 JSON_BOOL(csp->new_data),
983 JSON_BOOL(csp->los_warning), csp->tou);
984 }
985 str_rstrip_char(buf, ',');
986 (void)strlcat(buf, "]", buflen);
987 break;
988
989 case 6: /* NOP msg */
990 break;
991
992 case 7:
993 (void)strlcat(buf, "\"satellites\":[", buflen);
994 for (n = 0; n < rtcm->almanac.nentries; n++) {
995 const struct station_t *ssp = &rtcm->almanac.station[n];
996 str_appendf(buf, buflen,
997 "{\"lat\":%.4f,\"lon\":%.4f,\"range\":%u,"
998 "\"frequency\":%.1f,\"health\":%u,"
999 "\"station_id\":%u,\"bitrate\":%u},",
1000 ssp->latitude,
1001 ssp->longitude,
1002 ssp->range,
1003 ssp->frequency,
1004 ssp->health, ssp->station_id, ssp->bitrate);
1005 }
1006 str_rstrip_char(buf, ',');
1007 (void)strlcat(buf, "]", buflen);
1008 break;
1009
1010 case 13:
1011 str_appendf(buf, buflen,
1012 "\"status\":%s,\"rangeflag\":%s,"
1013 "\"lat\":%.2f,\"lon\":%.2f,\"range\":%u,",
1014 JSON_BOOL(rtcm->xmitter.status),
1015 JSON_BOOL(rtcm->xmitter.rangeflag),
1016 rtcm->xmitter.lat,
1017 rtcm->xmitter.lon,
1018 rtcm->xmitter.range);
1019 break;
1020
1021 case 14:
1022 str_appendf(buf, buflen,
1023 "\"week\":%u,\"hour\":%u,\"leapsecs\":%u,",
1024 rtcm->gpstime.week,
1025 rtcm->gpstime.hour,
1026 rtcm->gpstime.leapsecs);
1027 break;
1028
1029 case 16:
1030 str_appendf(buf, buflen,
1031 "\"message\":\"%s\"", json_stringify(buf1,
1032 sizeof(buf1),
1033 rtcm->message));
1034 break;
1035
1036 case 31:
1037 (void)strlcat(buf, "\"satellites\":[", buflen);
1038 for (n = 0; n < rtcm->glonass_ranges.nentries; n++) {
1039 const struct glonass_rangesat_t *rsp = &rtcm->glonass_ranges.sat[n];
1040 str_appendf(buf, buflen,
1041 "{\"ident\":%u,\"udre\":%u,\"change\":%s,"
1042 "\"tod\":%u,\"prc\":%0.3f,\"rrc\":%0.3f},",
1043 rsp->ident,
1044 rsp->udre,
1045 JSON_BOOL(rsp->change),
1046 rsp->tod,
1047 rsp->prc, rsp->rrc);
1048 }
1049 str_rstrip_char(buf, ',');
1050 (void)strlcat(buf, "]", buflen);
1051 break;
1052
1053 default:
1054 (void)strlcat(buf, "\"data\":[", buflen);
1055 for (n = 0; n < rtcm->length; n++)
1056 str_appendf(buf, buflen, "\"0x%08x\",", rtcm->words[n]);
1057 str_rstrip_char(buf, ',');
1058 (void)strlcat(buf, "]", buflen);
1059 break;
1060 }
1061
1062 str_rstrip_char(buf, ',');
1063 (void)strlcat(buf, "}\r\n", buflen);
1064 }
1065 #endif /* defined(RTCM104V2_ENABLE) */
1066
1067 #if defined(RTCM104V3_ENABLE)
json_rtcm3_dump(const struct rtcm3_t * rtcm,const char * device,char buf[],size_t buflen)1068 void json_rtcm3_dump(const struct rtcm3_t *rtcm,
1069 const char *device,
1070 char buf[], size_t buflen)
1071 /* dump the contents of a parsed RTCM104v3 message as JSON */
1072 {
1073 char buf1[JSON_VAL_MAX * 2 + 1];
1074 unsigned short i;
1075 unsigned int n;
1076
1077 (void)snprintf(buf, buflen, "{\"class\":\"RTCM3\",");
1078 if (device != NULL && device[0] != '\0')
1079 str_appendf(buf, buflen, "\"device\":\"%s\",", device);
1080 str_appendf(buf, buflen, "\"type\":%u,", rtcm->type);
1081 str_appendf(buf, buflen, "\"length\":%u,", rtcm->length);
1082
1083 #define CODE(x) (unsigned int)(x)
1084 #define INT(x) (unsigned int)(x)
1085 switch (rtcm->type) {
1086 case 1001:
1087 str_appendf(buf, buflen,
1088 "\"station_id\":%u,\"tow\":%d,\"sync\":\"%s\","
1089 "\"smoothing\":\"%s\",\"interval\":\"%u\",",
1090 rtcm->rtcmtypes.rtcm3_1001.header.station_id,
1091 (int)rtcm->rtcmtypes.rtcm3_1001.header.tow,
1092 JSON_BOOL(rtcm->rtcmtypes.rtcm3_1001.header.sync),
1093 JSON_BOOL(rtcm->rtcmtypes.rtcm3_1001.header.smoothing),
1094 rtcm->rtcmtypes.rtcm3_1001.header.interval);
1095 (void)strlcat(buf, "\"satellites\":[", buflen);
1096 for (i = 0; i < rtcm->rtcmtypes.rtcm3_1001.header.satcount; i++) {
1097 #define R1001 rtcm->rtcmtypes.rtcm3_1001.rtk_data[i]
1098 str_appendf(buf, buflen,
1099 "{\"ident\":%u,\"ind\":%u,\"prange\":%8.2f,"
1100 "\"delta\":%6.4f,\"lockt\":%u},",
1101 R1001.ident,
1102 CODE(R1001.L1.indicator),
1103 R1001.L1.pseudorange,
1104 R1001.L1.rangediff,
1105 INT(R1001.L1.locktime));
1106 #undef R1001
1107 }
1108 str_rstrip_char(buf, ',');
1109 (void)strlcat(buf, "]", buflen);
1110 break;
1111
1112 case 1002:
1113 str_appendf(buf, buflen,
1114 "\"station_id\":%u,\"tow\":%d,\"sync\":\"%s\","
1115 "\"smoothing\":\"%s\",\"interval\":\"%u\",",
1116 rtcm->rtcmtypes.rtcm3_1002.header.station_id,
1117 (int)rtcm->rtcmtypes.rtcm3_1002.header.tow,
1118 JSON_BOOL(rtcm->rtcmtypes.rtcm3_1002.header.sync),
1119 JSON_BOOL(rtcm->rtcmtypes.rtcm3_1002.header.smoothing),
1120 rtcm->rtcmtypes.rtcm3_1002.header.interval);
1121 (void)strlcat(buf, "\"satellites\":[", buflen);
1122 for (i = 0; i < rtcm->rtcmtypes.rtcm3_1002.header.satcount; i++) {
1123 #define R1002 rtcm->rtcmtypes.rtcm3_1002.rtk_data[i]
1124 str_appendf(buf, buflen,
1125 "{\"ident\":%u,\"ind\":%u,\"prange\":%8.2f,"
1126 "\"delta\":%6.4f,\"lockt\":%u,\"amb\":%u,"
1127 "\"CNR\":%.2f},",
1128 R1002.ident,
1129 CODE(R1002.L1.indicator),
1130 R1002.L1.pseudorange,
1131 R1002.L1.rangediff,
1132 INT(R1002.L1.locktime),
1133 INT(R1002.L1.ambiguity),
1134 R1002.L1.CNR);
1135 #undef R1002
1136 }
1137 str_rstrip_char(buf, ',');
1138 (void)strlcat(buf, "]", buflen);
1139 break;
1140
1141 case 1003:
1142 str_appendf(buf, buflen,
1143 "\"station_id\":%u,\"tow\":%d,\"sync\":\"%s\","
1144 "\"smoothing\":\"%s\",\"interval\":\"%u\",",
1145 rtcm->rtcmtypes.rtcm3_1003.header.station_id,
1146 (int)rtcm->rtcmtypes.rtcm3_1003.header.tow,
1147 JSON_BOOL(rtcm->rtcmtypes.rtcm3_1003.header.sync),
1148 JSON_BOOL(rtcm->rtcmtypes.rtcm3_1003.header.smoothing),
1149 rtcm->rtcmtypes.rtcm3_1003.header.interval);
1150 (void)strlcat(buf, "\"satellites\":[", buflen);
1151 for (i = 0; i < rtcm->rtcmtypes.rtcm3_1003.header.satcount; i++) {
1152 #define R1003 rtcm->rtcmtypes.rtcm3_1003.rtk_data[i]
1153 str_appendf(buf, buflen,
1154 "{\"ident\":%u,"
1155 "\"L1\":{\"ind\":%u,\"prange\":%8.2f,"
1156 "\"delta\":%6.4f,\"lockt\":%u},"
1157 "\"L2\":{\"ind\":%u,\"prange\":%8.2f,"
1158 "\"delta\":%6.4f,\"lockt\":%u},"
1159 "},",
1160 R1003.ident,
1161 CODE(R1003.L1.indicator),
1162 R1003.L1.pseudorange,
1163 R1003.L1.rangediff,
1164 INT(R1003.L1.locktime),
1165 CODE(R1003.L2.indicator),
1166 R1003.L2.pseudorange,
1167 R1003.L2.rangediff,
1168 INT(R1003.L2.locktime));
1169 #undef R1003
1170 }
1171 str_rstrip_char(buf, ',');
1172 (void)strlcat(buf, "]", buflen);
1173 break;
1174
1175 case 1004:
1176 str_appendf(buf, buflen,
1177 "\"station_id\":%u,\"tow\":%d,\"sync\":\"%s\","
1178 "\"smoothing\":\"%s\",\"interval\":\"%u\",",
1179 rtcm->rtcmtypes.rtcm3_1004.header.station_id,
1180 (int)rtcm->rtcmtypes.rtcm3_1004.header.tow,
1181 JSON_BOOL(rtcm->rtcmtypes.rtcm3_1004.header.sync),
1182 JSON_BOOL(rtcm->rtcmtypes.rtcm3_1004.header.smoothing),
1183 rtcm->rtcmtypes.rtcm3_1004.header.interval);
1184 (void)strlcat(buf, "\"satellites\":[", buflen);
1185 for (i = 0; i < rtcm->rtcmtypes.rtcm3_1004.header.satcount; i++) {
1186 #define R1004 rtcm->rtcmtypes.rtcm3_1004.rtk_data[i]
1187 str_appendf(buf, buflen,
1188 "{\"ident\":%u,"
1189 "\"L1\":{\"ind\":%u,\"prange\":%8.2f,"
1190 "\"delta\":%6.4f,\"lockt\":%u,"
1191 "\"amb\":%u,\"CNR\":%.2f},"
1192 "\"L2\":{\"ind\":%u,\"prange\":%8.2f,"
1193 "\"delta\":%6.4f,\"lockt\":%u,"
1194 "\"CNR\":%.2f}"
1195 "},",
1196 R1004.ident,
1197 CODE(R1004.L1.indicator),
1198 R1004.L1.pseudorange,
1199 R1004.L1.rangediff,
1200 INT(R1004.L1.locktime),
1201 INT(R1004.L1.ambiguity),
1202 R1004.L1.CNR,
1203 CODE(R1004.L2.indicator),
1204 R1004.L2.pseudorange,
1205 R1004.L2.rangediff,
1206 INT(R1004.L2.locktime),
1207 R1004.L2.CNR);
1208 #undef R1004
1209 }
1210 str_rstrip_char(buf, ',');
1211 (void)strlcat(buf, "]", buflen);
1212 break;
1213
1214 case 1005:
1215 str_appendf(buf, buflen,
1216 "\"station_id\":%u,\"system\":[",
1217 rtcm->rtcmtypes.rtcm3_1005.station_id);
1218 if ((rtcm->rtcmtypes.rtcm3_1005.system & 0x04)!=0)
1219 (void)strlcat(buf, "\"GPS\",", buflen);
1220 if ((rtcm->rtcmtypes.rtcm3_1005.system & 0x02)!=0)
1221 (void)strlcat(buf, "\"GLONASS\",", buflen);
1222 if ((rtcm->rtcmtypes.rtcm3_1005.system & 0x01)!=0)
1223 (void)strlcat(buf, "\"GALILEO\",", buflen);
1224 str_rstrip_char(buf, ',');
1225 str_appendf(buf, buflen,
1226 "],\"refstation\":%s,\"sro\":%s,"
1227 "\"x\":%.4f,\"y\":%.4f,\"z\":%.4f,",
1228 JSON_BOOL(rtcm->rtcmtypes.rtcm3_1005.reference_station),
1229 JSON_BOOL(rtcm->rtcmtypes.rtcm3_1005.single_receiver),
1230 rtcm->rtcmtypes.rtcm3_1005.ecef_x,
1231 rtcm->rtcmtypes.rtcm3_1005.ecef_y,
1232 rtcm->rtcmtypes.rtcm3_1005.ecef_z);
1233 break;
1234
1235 case 1006:
1236 str_appendf(buf, buflen,
1237 "\"station_id\":%u,\"system\":[",
1238 rtcm->rtcmtypes.rtcm3_1006.station_id);
1239 if ((rtcm->rtcmtypes.rtcm3_1006.system & 0x04)!=0)
1240 (void)strlcat(buf, "\"GPS\",", buflen);
1241 if ((rtcm->rtcmtypes.rtcm3_1006.system & 0x02)!=0)
1242 (void)strlcat(buf, "\"GLONASS\",", buflen);
1243 if ((rtcm->rtcmtypes.rtcm3_1006.system & 0x01)!=0)
1244 (void)strlcat(buf, "\"GALILEO\",", buflen);
1245 str_rstrip_char(buf, ',');
1246 str_appendf(buf, buflen,
1247 "],\"refstation\":%s,\"sro\":%s,"
1248 "\"x\":%.4f,\"y\":%.4f,\"z\":%.4f,",
1249 JSON_BOOL(rtcm->rtcmtypes.rtcm3_1006.reference_station),
1250 JSON_BOOL(rtcm->rtcmtypes.rtcm3_1006.single_receiver),
1251 rtcm->rtcmtypes.rtcm3_1006.ecef_x,
1252 rtcm->rtcmtypes.rtcm3_1006.ecef_y,
1253 rtcm->rtcmtypes.rtcm3_1006.ecef_z);
1254 str_appendf(buf, buflen,
1255 "\"h\":%.4f,",
1256 rtcm->rtcmtypes.rtcm3_1006.height);
1257 break;
1258
1259 case 1007:
1260 str_appendf(buf, buflen,
1261 "\"station_id\":%u,\"desc\":\"%s\",\"setup_id\":%u",
1262 rtcm->rtcmtypes.rtcm3_1007.station_id,
1263 rtcm->rtcmtypes.rtcm3_1007.descriptor,
1264 rtcm->rtcmtypes.rtcm3_1007.setup_id);
1265 break;
1266
1267 case 1008:
1268 str_appendf(buf, buflen,
1269 "\"station_id\":%u,\"desc\":\"%s\","
1270 "\"setup_id\":%u,\"serial\":\"%s\"",
1271 rtcm->rtcmtypes.rtcm3_1008.station_id,
1272 rtcm->rtcmtypes.rtcm3_1008.descriptor,
1273 INT(rtcm->rtcmtypes.rtcm3_1008.setup_id),
1274 rtcm->rtcmtypes.rtcm3_1008.serial);
1275 break;
1276
1277 case 1009:
1278 str_appendf(buf, buflen,
1279 "\"station_id\":%u,\"tow\":%lld,\"sync\":\"%s\","
1280 "\"smoothing\":\"%s\",\"interval\":\"%u\","
1281 "\"satcount\":\"%u\",",
1282 rtcm->rtcmtypes.rtcm3_1009.header.station_id,
1283 (long long)rtcm->rtcmtypes.rtcm3_1009.header.tow,
1284 JSON_BOOL(rtcm->rtcmtypes.rtcm3_1009.header.sync),
1285 JSON_BOOL(rtcm->rtcmtypes.rtcm3_1009.header.smoothing),
1286 rtcm->rtcmtypes.rtcm3_1009.header.interval,
1287 rtcm->rtcmtypes.rtcm3_1009.header.satcount);
1288 (void)strlcat(buf, "\"satellites\":[", buflen);
1289 for (i = 0; i < rtcm->rtcmtypes.rtcm3_1009.header.satcount; i++) {
1290 #define R1009 rtcm->rtcmtypes.rtcm3_1009.rtk_data[i]
1291 str_appendf(buf, buflen,
1292 "{\"ident\":%u,\"ind\":%u,\"channel\":%u,"
1293 "\"prange\":%8.2f,\"delta\":%6.4f,\"lockt\":%u},",
1294 R1009.ident,
1295 CODE(R1009.L1.indicator),
1296 R1009.L1.channel,
1297 R1009.L1.pseudorange,
1298 R1009.L1.rangediff,
1299 INT(R1009.L1.locktime));
1300 #undef R1009
1301 }
1302 str_rstrip_char(buf, ',');
1303 (void)strlcat(buf, "]", buflen);
1304 break;
1305
1306 case 1010:
1307 str_appendf(buf, buflen,
1308 "\"station_id\":%u,\"tow\":%d,\"sync\":\"%s\","
1309 "\"smoothing\":\"%s\",\"interval\":\"%u\",",
1310 rtcm->rtcmtypes.rtcm3_1010.header.station_id,
1311 (int)rtcm->rtcmtypes.rtcm3_1010.header.tow,
1312 JSON_BOOL(rtcm->rtcmtypes.rtcm3_1010.header.sync),
1313 JSON_BOOL(rtcm->rtcmtypes.rtcm3_1010.header.smoothing),
1314 rtcm->rtcmtypes.rtcm3_1010.header.interval);
1315 (void)strlcat(buf, "\"satellites\":[", buflen);
1316 for (i = 0; i < rtcm->rtcmtypes.rtcm3_1010.header.satcount; i++) {
1317 #define R1010 rtcm->rtcmtypes.rtcm3_1010.rtk_data[i]
1318 str_appendf(buf, buflen,
1319 "{\"ident\":%u,\"ind\":%u,\"channel\":%u,"
1320 "\"prange\":%8.2f,\"delta\":%6.4f,\"lockt\":%u,"
1321 "\"amb\":%u,\"CNR\":%.2f},",
1322 R1010.ident,
1323 CODE(R1010.L1.indicator),
1324 R1010.L1.channel,
1325 R1010.L1.pseudorange,
1326 R1010.L1.rangediff,
1327 INT(R1010.L1.locktime),
1328 INT(R1010.L1.ambiguity),
1329 R1010.L1.CNR);
1330 #undef R1010
1331 }
1332 str_rstrip_char(buf, ',');
1333 (void)strlcat(buf, "]", buflen);
1334 break;
1335
1336 case 1011:
1337 str_appendf(buf, buflen,
1338 "\"station_id\":%u,\"tow\":%d,\"sync\":\"%s\","
1339 "\"smoothing\":\"%s\",\"interval\":\"%u\",",
1340 rtcm->rtcmtypes.rtcm3_1011.header.station_id,
1341 (int)rtcm->rtcmtypes.rtcm3_1011.header.tow,
1342 JSON_BOOL(rtcm->rtcmtypes.rtcm3_1011.header.sync),
1343 JSON_BOOL(rtcm->rtcmtypes.rtcm3_1011.header.smoothing),
1344 rtcm->rtcmtypes.rtcm3_1011.header.interval);
1345 (void)strlcat(buf, "\"satellites\":[", buflen);
1346 for (i = 0; i < rtcm->rtcmtypes.rtcm3_1011.header.satcount; i++) {
1347 #define R1011 rtcm->rtcmtypes.rtcm3_1011.rtk_data[i]
1348 str_appendf(buf, buflen,
1349 "{\"ident\":%u,\"channel\":%u,"
1350 "\"L1\":{\"ind\":%u,"
1351 "\"prange\":%8.2f,\"delta\":%6.4f,\"lockt\":%u},"
1352 "\"L2:{\"ind\":%u,\"prange\":%8.2f,"
1353 "\"delta\":%6.4f,\"lockt\":%u}"
1354 "}",
1355 R1011.ident,R1011.L1.channel,
1356 CODE(R1011.L1.indicator),
1357 R1011.L1.pseudorange,
1358 R1011.L1.rangediff,
1359 INT(R1011.L1.locktime),
1360 CODE(R1011.L2.indicator),
1361 R1011.L2.pseudorange,
1362 R1011.L2.rangediff,
1363 INT(R1011.L2.locktime));
1364 #undef R1011
1365 }
1366 str_rstrip_char(buf, ',');
1367 (void)strlcat(buf, "]", buflen);
1368 break;
1369
1370 case 1012:
1371 str_appendf(buf, buflen,
1372 "\"station_id\":%u,\"tow\":%d,\"sync\":\"%s\","
1373 "\"smoothing\":\"%s\",\"interval\":\"%u\",",
1374 rtcm->rtcmtypes.rtcm3_1012.header.station_id,
1375 (int)rtcm->rtcmtypes.rtcm3_1012.header.tow,
1376 JSON_BOOL(rtcm->rtcmtypes.rtcm3_1012.header.sync),
1377 JSON_BOOL(rtcm->rtcmtypes.rtcm3_1012.header.smoothing),
1378 rtcm->rtcmtypes.rtcm3_1012.header.interval);
1379 (void)strlcat(buf, "\"satellites\":[", buflen);
1380 for (i = 0; i < rtcm->rtcmtypes.rtcm3_1012.header.satcount; i++) {
1381 #define R1012 rtcm->rtcmtypes.rtcm3_1012.rtk_data[i]
1382 str_appendf(buf, buflen,
1383 "{\"ident\":%u,\"channel\":%u,"
1384 "\"L1\":{\"ind\":%u,\"prange\":%8.2f,"
1385 "\"delta\":%6.4f,\"lockt\":%u,\"amb\":%u,"
1386 "\"CNR\":%.2f},"
1387 "\"L2\":{\"ind\":%u,\"prange\":%8.2f,"
1388 "\"delta\":%6.4f,\"lockt\":%u,"
1389 "\"CNR\":%.2f},"
1390 "},",
1391 R1012.ident,
1392 R1012.L1.channel,
1393 CODE(R1012.L1.indicator),
1394 R1012.L1.pseudorange,
1395 R1012.L1.rangediff,
1396 INT(R1012.L1.locktime),
1397 INT(R1012.L1.ambiguity),
1398 R1012.L1.CNR,
1399 CODE(R1012.L2.indicator),
1400 R1012.L2.pseudorange,
1401 R1012.L2.rangediff,
1402 INT(R1012.L2.locktime),
1403 R1012.L2.CNR);
1404 #undef R1012
1405 }
1406 str_rstrip_char(buf, ',');
1407 (void)strlcat(buf, "]", buflen);
1408 break;
1409
1410 case 1013:
1411 str_appendf(buf, buflen,
1412 "\"station_id\":%u,\"mjd\":%u,\"sec\":%u,"
1413 "\"leapsecs\":%u,",
1414 rtcm->rtcmtypes.rtcm3_1013.station_id,
1415 rtcm->rtcmtypes.rtcm3_1013.mjd,
1416 rtcm->rtcmtypes.rtcm3_1013.sod,
1417 INT(rtcm->rtcmtypes.rtcm3_1013.leapsecs));
1418 for (i = 0; i < (unsigned short)rtcm->rtcmtypes.rtcm3_1013.ncount; i++)
1419 str_appendf(buf, buflen,
1420 "{\"id\":%u,\"sync\":\"%s\",\"interval\":%u}",
1421 rtcm->rtcmtypes.rtcm3_1013.announcements[i].id,
1422 JSON_BOOL(rtcm->rtcmtypes.rtcm3_1013.
1423 announcements[i].sync),
1424 rtcm->rtcmtypes.rtcm3_1013.
1425 announcements[i].interval);
1426 break;
1427
1428 case 1014:
1429 str_appendf(buf, buflen,
1430 "\"netid\":%u,\"subnetid\":%u,\"statcount\":%u"
1431 "\"master\":%u,\"aux\":%u,\"lat\":%f,\"lon\":%f,"
1432 "\"alt\":%f,",
1433 rtcm->rtcmtypes.rtcm3_1014.network_id,
1434 rtcm->rtcmtypes.rtcm3_1014.subnetwork_id,
1435 rtcm->rtcmtypes.rtcm3_1014.stationcount,
1436 rtcm->rtcmtypes.rtcm3_1014.master_id,
1437 rtcm->rtcmtypes.rtcm3_1014.aux_id,
1438 rtcm->rtcmtypes.rtcm3_1014.d_lat,
1439 rtcm->rtcmtypes.rtcm3_1014.d_lon,
1440 rtcm->rtcmtypes.rtcm3_1014.d_alt);
1441 break;
1442
1443 case 1015:
1444 break;
1445
1446 case 1016:
1447 break;
1448
1449 case 1017:
1450 break;
1451
1452 case 1018:
1453 break;
1454
1455 case 1019:
1456 break;
1457
1458 case 1020:
1459 break;
1460
1461 case 1029:
1462 str_appendf(buf, buflen,
1463 "\"station_id\":%u,\"mjd\":%u,\"sec\":%u,"
1464 "\"len\":%zd,\"units\":%zd,\"msg\":\"%s\",",
1465 rtcm->rtcmtypes.rtcm3_1029.station_id,
1466 rtcm->rtcmtypes.rtcm3_1029.mjd,
1467 rtcm->rtcmtypes.rtcm3_1029.sod,
1468 rtcm->rtcmtypes.rtcm3_1029.len,
1469 rtcm->rtcmtypes.rtcm3_1029.unicode_units,
1470 json_stringify(buf1, sizeof(buf1),
1471 (char *)rtcm->rtcmtypes.rtcm3_1029.text));
1472 break;
1473
1474 case 1033:
1475 str_appendf(buf, buflen,
1476 "\"station_id\":%u,\"desc\":\"%s\","
1477 "\"setup_id\":%u,\"serial\":\"%s\","
1478 "\"receiver\":\"%s\",\"firmware\":\"%s\"",
1479 rtcm->rtcmtypes.rtcm3_1033.station_id,
1480 rtcm->rtcmtypes.rtcm3_1033.descriptor,
1481 INT(rtcm->rtcmtypes.rtcm3_1033.setup_id),
1482 rtcm->rtcmtypes.rtcm3_1033.serial,
1483 rtcm->rtcmtypes.rtcm3_1033.receiver,
1484 rtcm->rtcmtypes.rtcm3_1033.firmware);
1485 break;
1486
1487 default:
1488 (void)strlcat(buf, "\"data\":[", buflen);
1489 for (n = 0; n < rtcm->length; n++)
1490 str_appendf(buf, buflen,
1491 "\"0x%02x\",",(unsigned int)rtcm->rtcmtypes.data[n]);
1492 str_rstrip_char(buf, ',');
1493 (void)strlcat(buf, "]", buflen);
1494 break;
1495 }
1496
1497 str_rstrip_char(buf, ',');
1498 (void)strlcat(buf, "}\r\n", buflen);
1499 #undef CODE
1500 #undef INT
1501 }
1502 #endif /* defined(RTCM104V3_ENABLE) */
1503
1504 #if defined(AIVDM_ENABLE)
json_aivdm_dump(const struct ais_t * ais,const char * device,bool scaled,char * buf,size_t buflen)1505 void json_aivdm_dump(const struct ais_t *ais,
1506 const char *device, bool scaled,
1507 char *buf, size_t buflen)
1508 {
1509 char buf1[JSON_VAL_MAX * 2 + 1];
1510 char buf2[JSON_VAL_MAX * 2 + 1];
1511 char buf3[JSON_VAL_MAX * 2 + 1];
1512 char scratchbuf[MAX_PACKET_LENGTH*2+1];
1513 int i;
1514
1515 static char *nav_legends[] = {
1516 "Under way using engine",
1517 "At anchor",
1518 "Not under command",
1519 "Restricted manoeuverability",
1520 "Constrained by her draught",
1521 "Moored",
1522 "Aground",
1523 "Engaged in fishing",
1524 "Under way sailing",
1525 "Reserved for HSC",
1526 "Reserved for WIG",
1527 "Reserved",
1528 "Reserved",
1529 "Reserved",
1530 "Reserved",
1531 "Not defined",
1532 };
1533
1534 static char *epfd_legends[] = {
1535 "Undefined",
1536 "GPS",
1537 "GLONASS",
1538 "Combined GPS/GLONASS",
1539 "Loran-C",
1540 "Chayka",
1541 "Integrated navigation system",
1542 "Surveyed",
1543 "Galileo",
1544 };
1545
1546 #define EPFD_DISPLAY(n) (((n) < (unsigned int)NITEMS(epfd_legends)) ? epfd_legends[n] : "INVALID EPFD")
1547
1548 static char *ship_type_legends[100] = {
1549 "Not available",
1550 "Reserved for future use",
1551 "Reserved for future use",
1552 "Reserved for future use",
1553 "Reserved for future use",
1554 "Reserved for future use",
1555 "Reserved for future use",
1556 "Reserved for future use",
1557 "Reserved for future use",
1558 "Reserved for future use",
1559 "Reserved for future use",
1560 "Reserved for future use",
1561 "Reserved for future use",
1562 "Reserved for future use",
1563 "Reserved for future use",
1564 "Reserved for future use",
1565 "Reserved for future use",
1566 "Reserved for future use",
1567 "Reserved for future use",
1568 "Reserved for future use",
1569 "Wing in ground (WIG) - all ships of this type",
1570 "Wing in ground (WIG) - Hazardous category A",
1571 "Wing in ground (WIG) - Hazardous category B",
1572 "Wing in ground (WIG) - Hazardous category C",
1573 "Wing in ground (WIG) - Hazardous category D",
1574 "Wing in ground (WIG) - Reserved for future use",
1575 "Wing in ground (WIG) - Reserved for future use",
1576 "Wing in ground (WIG) - Reserved for future use",
1577 "Wing in ground (WIG) - Reserved for future use",
1578 "Wing in ground (WIG) - Reserved for future use",
1579 "Fishing",
1580 "Towing",
1581 "Towing: length exceeds 200m or breadth exceeds 25m",
1582 "Dredging or underwater ops",
1583 "Diving ops",
1584 "Military ops",
1585 "Sailing",
1586 "Pleasure Craft",
1587 "Reserved",
1588 "Reserved",
1589 "High speed craft (HSC) - all ships of this type",
1590 "High speed craft (HSC) - Hazardous category A",
1591 "High speed craft (HSC) - Hazardous category B",
1592 "High speed craft (HSC) - Hazardous category C",
1593 "High speed craft (HSC) - Hazardous category D",
1594 "High speed craft (HSC) - Reserved for future use",
1595 "High speed craft (HSC) - Reserved for future use",
1596 "High speed craft (HSC) - Reserved for future use",
1597 "High speed craft (HSC) - Reserved for future use",
1598 "High speed craft (HSC) - No additional information",
1599 "Pilot Vessel",
1600 "Search and Rescue vessel",
1601 "Tug",
1602 "Port Tender",
1603 "Anti-pollution equipment",
1604 "Law Enforcement",
1605 "Spare - Local Vessel",
1606 "Spare - Local Vessel",
1607 "Medical Transport",
1608 "Ship according to RR Resolution No. 18",
1609 "Passenger - all ships of this type",
1610 "Passenger - Hazardous category A",
1611 "Passenger - Hazardous category B",
1612 "Passenger - Hazardous category C",
1613 "Passenger - Hazardous category D",
1614 "Passenger - Reserved for future use",
1615 "Passenger - Reserved for future use",
1616 "Passenger - Reserved for future use",
1617 "Passenger - Reserved for future use",
1618 "Passenger - No additional information",
1619 "Cargo - all ships of this type",
1620 "Cargo - Hazardous category A",
1621 "Cargo - Hazardous category B",
1622 "Cargo - Hazardous category C",
1623 "Cargo - Hazardous category D",
1624 "Cargo - Reserved for future use",
1625 "Cargo - Reserved for future use",
1626 "Cargo - Reserved for future use",
1627 "Cargo - Reserved for future use",
1628 "Cargo - No additional information",
1629 "Tanker - all ships of this type",
1630 "Tanker - Hazardous category A",
1631 "Tanker - Hazardous category B",
1632 "Tanker - Hazardous category C",
1633 "Tanker - Hazardous category D",
1634 "Tanker - Reserved for future use",
1635 "Tanker - Reserved for future use",
1636 "Tanker - Reserved for future use",
1637 "Tanker - Reserved for future use",
1638 "Tanker - No additional information",
1639 "Other Type - all ships of this type",
1640 "Other Type - Hazardous category A",
1641 "Other Type - Hazardous category B",
1642 "Other Type - Hazardous category C",
1643 "Other Type - Hazardous category D",
1644 "Other Type - Reserved for future use",
1645 "Other Type - Reserved for future use",
1646 "Other Type - Reserved for future use",
1647 "Other Type - Reserved for future use",
1648 "Other Type - no additional information",
1649 };
1650
1651 #define SHIPTYPE_DISPLAY(n) (((n) < (unsigned int)NITEMS(ship_type_legends)) ? ship_type_legends[n] : "INVALID SHIP TYPE")
1652
1653 static const char *station_type_legends[] = {
1654 "All types of mobiles",
1655 "Reserved for future use",
1656 "All types of Class B mobile stations",
1657 "SAR airborne mobile station",
1658 "Aid to Navigation station",
1659 "Class B shipborne mobile station",
1660 "Regional use and inland waterways",
1661 "Regional use and inland waterways",
1662 "Regional use and inland waterways",
1663 "Regional use and inland waterways",
1664 "Reserved for future use",
1665 "Reserved for future use",
1666 "Reserved for future use",
1667 "Reserved for future use",
1668 "Reserved for future use",
1669 "Reserved for future use",
1670 };
1671
1672 #define STATIONTYPE_DISPLAY(n) (((n) < (unsigned int)NITEMS(station_type_legends)) ? station_type_legends[n] : "INVALID STATION TYPE")
1673
1674 static const char *navaid_type_legends[] = {
1675 "Unspecified",
1676 "Reference point",
1677 "RACON",
1678 "Fixed offshore structure",
1679 "Spare, Reserved for future use.",
1680 "Light, without sectors",
1681 "Light, with sectors",
1682 "Leading Light Front",
1683 "Leading Light Rear",
1684 "Beacon, Cardinal N",
1685 "Beacon, Cardinal E",
1686 "Beacon, Cardinal S",
1687 "Beacon, Cardinal W",
1688 "Beacon, Port hand",
1689 "Beacon, Starboard hand",
1690 "Beacon, Preferred Channel port hand",
1691 "Beacon, Preferred Channel starboard hand",
1692 "Beacon, Isolated danger",
1693 "Beacon, Safe water",
1694 "Beacon, Special mark",
1695 "Cardinal Mark N",
1696 "Cardinal Mark E",
1697 "Cardinal Mark S",
1698 "Cardinal Mark W",
1699 "Port hand Mark",
1700 "Starboard hand Mark",
1701 "Preferred Channel Port hand",
1702 "Preferred Channel Starboard hand",
1703 "Isolated danger",
1704 "Safe Water",
1705 "Special Mark",
1706 "Light Vessel / LANBY / Rigs",
1707 };
1708
1709 #define NAVAIDTYPE_DISPLAY(n) (((n) < (unsigned int)NITEMS(navaid_type_legends)) ? navaid_type_legends[n] : "INVALID NAVAID TYPE")
1710
1711 // cppcheck-suppress variableScope
1712 static const char *signal_legends[] = {
1713 "N/A",
1714 "Serious emergency – stop or divert according to instructions.",
1715 "Vessels shall not proceed.",
1716 "Vessels may proceed. One way traffic.",
1717 "Vessels may proceed. Two way traffic.",
1718 "Vessels shall proceed on specific orders only.",
1719 "Vessels in main channel shall not proceed.",
1720 "Vessels in main channel shall proceed on specific orders only.",
1721 "Vessels in main channel shall proceed on specific orders only.",
1722 "I = \"in-bound\" only acceptable.",
1723 "O = \"out-bound\" only acceptable.",
1724 "F = both \"in- and out-bound\" acceptable.",
1725 "XI = Code will shift to \"I\" in due time.",
1726 "XO = Code will shift to \"O\" in due time.",
1727 "X = Vessels shall proceed only on direction.",
1728 };
1729
1730 #define SIGNAL_DISPLAY(n) (((n) < (unsigned int)NITEMS(signal_legends)) ? signal_legends[n] : "INVALID SIGNAL TYPE")
1731
1732 static const char *route_type[32] = {
1733 "Undefined (default)",
1734 "Mandatory",
1735 "Recommended",
1736 "Alternative",
1737 "Recommended route through ice",
1738 "Ship route plan",
1739 "Reserved for future use.",
1740 "Reserved for future use.",
1741 "Reserved for future use.",
1742 "Reserved for future use.",
1743 "Reserved for future use.",
1744 "Reserved for future use.",
1745 "Reserved for future use.",
1746 "Reserved for future use.",
1747 "Reserved for future use.",
1748 "Reserved for future use.",
1749 "Reserved for future use.",
1750 "Reserved for future use.",
1751 "Reserved for future use.",
1752 "Reserved for future use.",
1753 "Reserved for future use.",
1754 "Reserved for future use.",
1755 "Reserved for future use.",
1756 "Reserved for future use.",
1757 "Reserved for future use.",
1758 "Reserved for future use.",
1759 "Reserved for future use.",
1760 "Reserved for future use.",
1761 "Reserved for future use.",
1762 "Reserved for future use.",
1763 "Reserved for future use.",
1764 "Cancel route identified by message linkage",
1765 };
1766
1767 // cppcheck-suppress variableScope
1768 static const char *idtypes[] = {
1769 "mmsi",
1770 "imo",
1771 "callsign",
1772 "other",
1773 };
1774
1775 // cppcheck-suppress variableScope
1776 static const char *racon_status[] = {
1777 "No RACON installed",
1778 "RACON not monitored",
1779 "RACON operational",
1780 "RACON ERROR"
1781 };
1782
1783 // cppcheck-suppress variableScope
1784 static const char *light_status[] = {
1785 "No light or no monitoring",
1786 "Light ON",
1787 "Light OFF",
1788 "Light ERROR"
1789 };
1790
1791 // cppcheck-suppress variableScope
1792 static const char *rta_status[] = {
1793 "Operational",
1794 "Limited operation",
1795 "Out of order",
1796 "N/A",
1797 };
1798
1799 // cppcheck-suppress variableScope
1800 const char *position_types[8] = {
1801 "Not available",
1802 "Port-side to",
1803 "Starboard-side to",
1804 "Mediterranean (end-on) mooring",
1805 "Mooring buoy",
1806 "Anchorage",
1807 "Reserved for future use",
1808 "Reserved for future use",
1809 };
1810
1811 (void)snprintf(buf, buflen, "{\"class\":\"AIS\",");
1812 if (device != NULL && device[0] != '\0')
1813 str_appendf(buf, buflen, "\"device\":\"%s\",", device);
1814 str_appendf(buf, buflen,
1815 "\"type\":%u,\"repeat\":%u,\"mmsi\":%u,\"scaled\":%s,",
1816 ais->type, ais->repeat, ais->mmsi, JSON_BOOL(scaled));
1817 switch (ais->type) {
1818 case 1: /* Position Report */
1819 case 2:
1820 case 3:
1821 if (scaled) {
1822 char turnlegend[20];
1823 char speedlegend[20];
1824
1825 /*
1826 * Express turn as nan if not available,
1827 * "fastleft"/"fastright" for fast turns.
1828 */
1829 if (ais->type1.turn == -128)
1830 (void)strlcpy(turnlegend, "\"nan\"", sizeof(turnlegend));
1831 else if (ais->type1.turn == -127)
1832 (void)strlcpy(turnlegend, "\"fastleft\"", sizeof(turnlegend));
1833 else if (ais->type1.turn == 127)
1834 (void)strlcpy(turnlegend, "\"fastright\"",
1835 sizeof(turnlegend));
1836 else {
1837 double rot1 = ais->type1.turn / 4.733;
1838 (void)snprintf(turnlegend, sizeof(turnlegend),
1839 "%.0f", rot1 * rot1);
1840 }
1841
1842 /*
1843 * Express speed as nan if not available,
1844 * "fast" for fast movers.
1845 */
1846 if (ais->type1.speed == AIS_SPEED_NOT_AVAILABLE)
1847 (void)strlcpy(speedlegend, "\"nan\"", sizeof(speedlegend));
1848 else if (ais->type1.speed == AIS_SPEED_FAST_MOVER)
1849 (void)strlcpy(speedlegend, "\"fast\"", sizeof(speedlegend));
1850 else
1851 (void)snprintf(speedlegend, sizeof(speedlegend),
1852 "%.1f", ais->type1.speed / 10.0);
1853
1854 str_appendf(buf, buflen,
1855 "\"status\":%u,\"status_text\":\"%s\","
1856 "\"turn\":%s,\"speed\":%s,"
1857 "\"accuracy\":%s,\"lon\":%.6f,\"lat\":%.6f,"
1858 "\"course\":%.1f,\"heading\":%u,\"second\":%u,"
1859 "\"maneuver\":%u,\"raim\":%s,\"radio\":%u}\r\n",
1860 ais->type1.status,
1861 nav_legends[ais->type1.status],
1862 turnlegend,
1863 speedlegend,
1864 JSON_BOOL(ais->type1.accuracy),
1865 ais->type1.lon / AIS_LATLON_DIV,
1866 ais->type1.lat / AIS_LATLON_DIV,
1867 ais->type1.course / 10.0,
1868 ais->type1.heading,
1869 ais->type1.second,
1870 ais->type1.maneuver,
1871 JSON_BOOL(ais->type1.raim), ais->type1.radio);
1872 } else {
1873 str_appendf(buf, buflen,
1874 "\"status\":%u,\"status_text\":\"%s\","
1875 "\"turn\":%d,\"speed\":%u,"
1876 "\"accuracy\":%s,\"lon\":%d,\"lat\":%d,"
1877 "\"course\":%u,\"heading\":%u,\"second\":%u,"
1878 "\"maneuver\":%u,\"raim\":%s,\"radio\":%u}\r\n",
1879 ais->type1.status,
1880 nav_legends[ais->type1.status],
1881 ais->type1.turn,
1882 ais->type1.speed,
1883 JSON_BOOL(ais->type1.accuracy),
1884 ais->type1.lon,
1885 ais->type1.lat,
1886 ais->type1.course,
1887 ais->type1.heading,
1888 ais->type1.second,
1889 ais->type1.maneuver,
1890 JSON_BOOL(ais->type1.raim), ais->type1.radio);
1891 }
1892 break;
1893 case 4: /* Base Station Report */
1894 case 11: /* UTC/Date Response */
1895 /* some fields have beem merged to an ISO8601 date */
1896 if (scaled) {
1897 // The use of %u instead of %04u for the year is to allow
1898 // out-of-band year values.
1899 str_appendf(buf, buflen,
1900 "\"timestamp\":\"%04u-%02u-%02uT%02u:%02u:%02uZ\","
1901 "\"accuracy\":%s,\"lon\":%.6f,\"lat\":%.6f,"
1902 "\"epfd\":%u,\"epfd_text\":\"%s\","
1903 "\"raim\":%s,\"radio\":%u}\r\n",
1904 ais->type4.year,
1905 ais->type4.month,
1906 ais->type4.day,
1907 ais->type4.hour,
1908 ais->type4.minute,
1909 ais->type4.second,
1910 JSON_BOOL(ais->type4.accuracy),
1911 ais->type4.lon / AIS_LATLON_DIV,
1912 ais->type4.lat / AIS_LATLON_DIV,
1913 ais->type4.epfd,
1914 EPFD_DISPLAY(ais->type4.epfd),
1915 JSON_BOOL(ais->type4.raim), ais->type4.radio);
1916 } else {
1917 str_appendf(buf, buflen,
1918 "\"timestamp\":\"%04u-%02u-%02uT%02u:%02u:%02uZ\","
1919 "\"accuracy\":%s,\"lon\":%d,\"lat\":%d,"
1920 "\"epfd\":%u,\"epfd_text\":\"%s\","
1921 "\"raim\":%s,\"radio\":%u}\r\n",
1922 ais->type4.year,
1923 ais->type4.month,
1924 ais->type4.day,
1925 ais->type4.hour,
1926 ais->type4.minute,
1927 ais->type4.second,
1928 JSON_BOOL(ais->type4.accuracy),
1929 ais->type4.lon,
1930 ais->type4.lat,
1931 ais->type4.epfd,
1932 EPFD_DISPLAY(ais->type4.epfd),
1933 JSON_BOOL(ais->type4.raim), ais->type4.radio);
1934 }
1935 break;
1936 case 5: /* Ship static and voyage related data */
1937 /* some fields have beem merged to an ISO8601 partial date */
1938 if (scaled) {
1939 /* *INDENT-OFF* */
1940 str_appendf(buf, buflen,
1941 "\"imo\":%u,\"ais_version\":%u,\"callsign\":\"%s\","
1942 "\"shipname\":\"%s\","
1943 "\"shiptype\":%u,\"shiptype_text\":\"%s\","
1944 "\"to_bow\":%u,\"to_stern\":%u,\"to_port\":%u,"
1945 "\"to_starboard\":%u,"
1946 "\"epfd\":%u,\"epfd_text\":\"%s\","
1947 "\"eta\":\"%02u-%02uT%02u:%02uZ\","
1948 "\"draught\":%.1f,\"destination\":\"%s\","
1949 "\"dte\":%u}\r\n",
1950 ais->type5.imo,
1951 ais->type5.ais_version,
1952 json_stringify(buf1, sizeof(buf1),
1953 ais->type5.callsign),
1954 json_stringify(buf2, sizeof(buf2),
1955 ais->type5.shipname),
1956 ais->type5.shiptype,
1957 SHIPTYPE_DISPLAY(ais->type5.shiptype),
1958 ais->type5.to_bow, ais->type5.to_stern,
1959 ais->type5.to_port, ais->type5.to_starboard,
1960 ais->type5.epfd,
1961 EPFD_DISPLAY(ais->type5.epfd),
1962 ais->type5.month,
1963 ais->type5.day,
1964 ais->type5.hour, ais->type5.minute,
1965 ais->type5.draught / 10.0,
1966 json_stringify(buf3, sizeof(buf3),
1967 ais->type5.destination),
1968 ais->type5.dte);
1969 /* *INDENT-ON* */
1970 } else {
1971 str_appendf(buf, buflen,
1972 "\"imo\":%u,\"ais_version\":%u,\"callsign\":\"%s\","
1973 "\"shipname\":\"%s\","
1974 "\"shiptype\":%u,\"shiptype_text\":\"%s\","
1975 "\"to_bow\":%u,\"to_stern\":%u,\"to_port\":%u,"
1976 "\"to_starboard\":%u,"
1977 "\"epfd\":%u,\"epfd_text\":\"%s\","
1978 "\"eta\":\"%02u-%02uT%02u:%02uZ\","
1979 "\"draught\":%u,\"destination\":\"%s\","
1980 "\"dte\":%u}\r\n",
1981 ais->type5.imo,
1982 ais->type5.ais_version,
1983 json_stringify(buf1, sizeof(buf1),
1984 ais->type5.callsign),
1985 json_stringify(buf2, sizeof(buf2),
1986 ais->type5.shipname),
1987 ais->type5.shiptype,
1988 SHIPTYPE_DISPLAY(ais->type5.shiptype),
1989 ais->type5.to_bow,
1990 ais->type5.to_stern,
1991 ais->type5.to_port,
1992 ais->type5.to_starboard,
1993 ais->type5.epfd,
1994 EPFD_DISPLAY(ais->type5.epfd),
1995 ais->type5.month,
1996 ais->type5.day,
1997 ais->type5.hour,
1998 ais->type5.minute,
1999 ais->type5.draught,
2000 json_stringify(buf3, sizeof(buf3),
2001 ais->type5.destination),
2002 ais->type5.dte);
2003 }
2004 break;
2005 case 6: /* Binary Message */
2006 str_appendf(buf, buflen,
2007 "\"seqno\":%u,\"dest_mmsi\":%u,"
2008 "\"retransmit\":%s,\"dac\":%u,\"fid\":%u,",
2009 ais->type6.seqno,
2010 ais->type6.dest_mmsi,
2011 JSON_BOOL(ais->type6.retransmit),
2012 ais->type6.dac,
2013 ais->type6.fid);
2014 if (!ais->type6.structured) {
2015 str_appendf(buf, buflen,
2016 "\"data\":\"%zd:%s\"}\r\n",
2017 ais->type6.bitcount,
2018 json_stringify(buf1, sizeof(buf1),
2019 gpsd_hexdump(
2020 scratchbuf, sizeof(scratchbuf),
2021 (char *)ais->type6.bitdata,
2022 BITS_TO_BYTES(ais->type6.bitcount))));
2023 break;
2024 }
2025 if (ais->type6.dac == 200) {
2026 switch (ais->type6.fid) {
2027 case 21:
2028 str_appendf(buf, buflen,
2029 "\"country\":\"%s\",\"locode\":\"%s\","
2030 "\"section\":\"%s\",\"terminal\":\"%s\","
2031 "\"hectometre\":\"%s\",\"eta\":\"%u-%uT%u:%u\","
2032 "\"tugs\":%u,\"airdraught\":%u}\r\n",
2033 ais->type6.dac200fid21.country,
2034 ais->type6.dac200fid21.locode,
2035 ais->type6.dac200fid21.section,
2036 ais->type6.dac200fid21.terminal,
2037 ais->type6.dac200fid21.hectometre,
2038 ais->type6.dac200fid21.month,
2039 ais->type6.dac200fid21.day,
2040 ais->type6.dac200fid21.hour,
2041 ais->type6.dac200fid21.minute,
2042 ais->type6.dac200fid21.tugs,
2043 ais->type6.dac200fid21.airdraught);
2044 break;
2045 case 22:
2046 str_appendf(buf, buflen,
2047 "\"country\":\"%s\",\"locode\":\"%s\","
2048 "\"section\":\"%s\","
2049 "\"terminal\":\"%s\",\"hectometre\":\"%s\","
2050 "\"eta\":\"%u-%uT%u:%u\","
2051 "\"status\":%u,\"status_text\":\"%s\"}\r\n",
2052 ais->type6.dac200fid22.country,
2053 ais->type6.dac200fid22.locode,
2054 ais->type6.dac200fid22.section,
2055 ais->type6.dac200fid22.terminal,
2056 ais->type6.dac200fid22.hectometre,
2057 ais->type6.dac200fid22.month,
2058 ais->type6.dac200fid22.day,
2059 ais->type6.dac200fid22.hour,
2060 ais->type6.dac200fid22.minute,
2061 ais->type6.dac200fid22.status,
2062 rta_status[ais->type6.dac200fid22.status]);
2063 break;
2064 case 55:
2065 str_appendf(buf, buflen,
2066 "\"crew\":%u,\"passengers\":%u,\"personnel\":%u}\r\n",
2067
2068 ais->type6.dac200fid55.crew,
2069 ais->type6.dac200fid55.passengers,
2070 ais->type6.dac200fid55.personnel);
2071 break;
2072 }
2073 }
2074 else if (ais->type6.dac == 235 || ais->type6.dac == 250) {
2075 switch (ais->type6.fid) {
2076 case 10: /* GLA - AtoN monitoring data */
2077 str_appendf(buf, buflen,
2078 "\"off_pos\":%s,\"alarm\":%s,"
2079 "\"stat_ext\":%u,",
2080 JSON_BOOL(ais->type6.dac235fid10.off_pos),
2081 JSON_BOOL(ais->type6.dac235fid10.alarm),
2082 ais->type6.dac235fid10.stat_ext);
2083 if (scaled && ais->type6.dac235fid10.ana_int != 0)
2084 str_appendf(buf, buflen,
2085 "\"ana_int\":%.2f,",
2086 ais->type6.dac235fid10.ana_int*0.05);
2087 else
2088 str_appendf(buf, buflen,
2089 "\"ana_int\":%u,",
2090 ais->type6.dac235fid10.ana_int);
2091 if (scaled && ais->type6.dac235fid10.ana_ext1 != 0)
2092 str_appendf(buf, buflen,
2093 "\"ana_ext1\":%.2f,",
2094 ais->type6.dac235fid10.ana_ext1*0.05);
2095 else
2096 str_appendf(buf, buflen,
2097 "\"ana_ext1\":%u,",
2098 ais->type6.dac235fid10.ana_ext1);
2099 if (scaled && ais->type6.dac235fid10.ana_ext2 != 0)
2100 str_appendf(buf, buflen,
2101 "\"ana_ext2\":%.2f,",
2102 ais->type6.dac235fid10.ana_ext2*0.05);
2103 else
2104 str_appendf(buf, buflen,
2105 "\"ana_ext2\":%u,",
2106 ais->type6.dac235fid10.ana_ext2);
2107 str_appendf(buf, buflen,
2108 "\"racon\":%u,"
2109 "\"racon_text\":\"%s\","
2110 "\"light\":%u,"
2111 "\"light_text\":\"%s\"",
2112 ais->type6.dac235fid10.racon,
2113 racon_status[ais->type6.dac235fid10.racon],
2114 ais->type6.dac235fid10.light,
2115 light_status[ais->type6.dac235fid10.light]);
2116 str_rstrip_char(buf, ',');
2117 (void)strlcat(buf, "}\r\n", buflen);
2118 break;
2119 }
2120 }
2121 else if (ais->type6.dac == 1) {
2122 char buf4[JSON_VAL_MAX * 2 + 1];
2123 switch (ais->type6.fid) {
2124 case 12: /* IMO236 -Dangerous cargo indication */
2125 /* some fields have beem merged to an ISO8601 partial date */
2126 str_appendf(buf, buflen,
2127 "\"lastport\":\"%s\","
2128 "\"departure\":\"%02u-%02uT%02u:%02uZ\","
2129 "\"nextport\":\"%s\","
2130 "\"eta\":\"%02u-%02uT%02u:%02uZ\","
2131 "\"dangerous\":\"%s\",\"imdcat\":\"%s\","
2132 "\"unid\":%u,\"amount\":%u,\"unit\":%u}\r\n",
2133 json_stringify(buf1, sizeof(buf1),
2134 ais->type6.dac1fid12.lastport),
2135 ais->type6.dac1fid12.lmonth,
2136 ais->type6.dac1fid12.lday,
2137 ais->type6.dac1fid12.lhour,
2138 ais->type6.dac1fid12.lminute,
2139 json_stringify(buf2, sizeof(buf2),
2140 ais->type6.dac1fid12.nextport),
2141 ais->type6.dac1fid12.nmonth,
2142 ais->type6.dac1fid12.nday,
2143 ais->type6.dac1fid12.nhour,
2144 ais->type6.dac1fid12.nminute,
2145 json_stringify(buf3, sizeof(buf3),
2146 ais->type6.dac1fid12.dangerous),
2147 json_stringify(buf4, sizeof(buf4),
2148 ais->type6.dac1fid12.imdcat),
2149 ais->type6.dac1fid12.unid,
2150 ais->type6.dac1fid12.amount,
2151 ais->type6.dac1fid12.unit);
2152 break;
2153 case 15: /* IMO236 - Extended Ship Static and Voyage Related Data */
2154 str_appendf(buf, buflen,
2155 "\"airdraught\":%u}\r\n",
2156 ais->type6.dac1fid15.airdraught);
2157 break;
2158 case 16: /* IMO236 - Number of persons on board */
2159 str_appendf(buf, buflen,
2160 "\"persons\":%u}\r\n",
2161 ais->type6.dac1fid16.persons);
2162 break;
2163 case 18: /* IMO289 - Clearance time to enter port */
2164 str_appendf(buf, buflen,
2165 "\"linkage\":%u,"
2166 "\"arrival\":\"%02u-%02uT%02u:%02uZ\","
2167 "\"portname\":\"%s\",\"destination\":\"%s\",",
2168 ais->type6.dac1fid18.linkage,
2169 ais->type6.dac1fid18.month,
2170 ais->type6.dac1fid18.day,
2171 ais->type6.dac1fid18.hour,
2172 ais->type6.dac1fid18.minute,
2173 json_stringify(buf1, sizeof(buf1),
2174 ais->type6.dac1fid18.portname),
2175 json_stringify(buf2, sizeof(buf2),
2176 ais->type6.dac1fid18.destination));
2177 if (scaled)
2178 str_appendf(buf, buflen,
2179 "\"lon\":%.4f,\"lat\":%.4f}\r\n",
2180 ais->type6.dac1fid18.lon/AIS_LATLON3_DIV,
2181 ais->type6.dac1fid18.lat/AIS_LATLON3_DIV);
2182 else
2183 str_appendf(buf, buflen,
2184 "\"lon\":%d,\"lat\":%d}\r\n",
2185 ais->type6.dac1fid18.lon,
2186 ais->type6.dac1fid18.lat);
2187 break;
2188 case 20: /* IMO289 - Berthing Data */
2189 str_appendf(buf, buflen,
2190 "\"linkage\":%u,\"berth_length\":%u,"
2191 "\"position\":%u,\"position_text\":\"%s\","
2192 "\"arrival\":\"%u-%uT%u:%u\","
2193 "\"availability\":%u,"
2194 "\"agent\":%u,\"fuel\":%u,\"chandler\":%u,"
2195 "\"stevedore\":%u,\"electrical\":%u,"
2196 "\"water\":%u,\"customs\":%u,\"cartage\":%u,"
2197 "\"crane\":%u,\"lift\":%u,\"medical\":%u,"
2198 "\"navrepair\":%u,\"provisions\":%u,"
2199 "\"shiprepair\":%u,\"surveyor\":%u,"
2200 "\"steam\":%u,\"tugs\":%u,\"solidwaste\":%u,"
2201 "\"liquidwaste\":%u,\"hazardouswaste\":%u,"
2202 "\"ballast\":%u,\"additional\":%u,"
2203 "\"regional1\":%u,\"regional2\":%u,"
2204 "\"future1\":%u,\"future2\":%u,"
2205 "\"berth_name\":\"%s\",",
2206 ais->type6.dac1fid20.linkage,
2207 ais->type6.dac1fid20.berth_length,
2208 ais->type6.dac1fid20.position,
2209 position_types[ais->type6.dac1fid20.position],
2210 ais->type6.dac1fid20.month,
2211 ais->type6.dac1fid20.day,
2212 ais->type6.dac1fid20.hour,
2213 ais->type6.dac1fid20.minute,
2214 ais->type6.dac1fid20.availability,
2215 ais->type6.dac1fid20.agent,
2216 ais->type6.dac1fid20.fuel,
2217 ais->type6.dac1fid20.chandler,
2218 ais->type6.dac1fid20.stevedore,
2219 ais->type6.dac1fid20.electrical,
2220 ais->type6.dac1fid20.water,
2221 ais->type6.dac1fid20.customs,
2222 ais->type6.dac1fid20.cartage,
2223 ais->type6.dac1fid20.crane,
2224 ais->type6.dac1fid20.lift,
2225 ais->type6.dac1fid20.medical,
2226 ais->type6.dac1fid20.navrepair,
2227 ais->type6.dac1fid20.provisions,
2228 ais->type6.dac1fid20.shiprepair,
2229 ais->type6.dac1fid20.surveyor,
2230 ais->type6.dac1fid20.steam,
2231 ais->type6.dac1fid20.tugs,
2232 ais->type6.dac1fid20.solidwaste,
2233 ais->type6.dac1fid20.liquidwaste,
2234 ais->type6.dac1fid20.hazardouswaste,
2235 ais->type6.dac1fid20.ballast,
2236 ais->type6.dac1fid20.additional,
2237 ais->type6.dac1fid20.regional1,
2238 ais->type6.dac1fid20.regional2,
2239 ais->type6.dac1fid20.future1,
2240 ais->type6.dac1fid20.future2,
2241 json_stringify(buf1, sizeof(buf1),
2242 ais->type6.dac1fid20.berth_name));
2243 if (scaled)
2244 str_appendf(buf, buflen,
2245 "\"berth_lon\":%.4f,"
2246 "\"berth_lat\":%.4f,"
2247 "\"berth_depth\":%.1f}\r\n",
2248 ais->type6.dac1fid20.berth_lon / AIS_LATLON3_DIV,
2249 ais->type6.dac1fid20.berth_lat / AIS_LATLON3_DIV,
2250 ais->type6.dac1fid20.berth_depth * 0.1);
2251 else
2252 str_appendf(buf, buflen,
2253 "\"berth_lon\":%d,"
2254 "\"berth_lat\":%d,"
2255 "\"berth_depth\":%u}\r\n",
2256 ais->type6.dac1fid20.berth_lon,
2257 ais->type6.dac1fid20.berth_lat,
2258 ais->type6.dac1fid20.berth_depth);
2259 break;
2260 case 23: /* IMO289 - Area notice - addressed */
2261 break;
2262 case 25: /* IMO289 - Dangerous cargo indication */
2263 str_appendf(buf, buflen,
2264 "\"unit\":%u,\"amount\":%u,\"cargos\":[",
2265 ais->type6.dac1fid25.unit,
2266 ais->type6.dac1fid25.amount);
2267 for (i = 0; i < (int)ais->type6.dac1fid25.ncargos; i++)
2268 str_appendf(buf, buflen,
2269 "{\"code\":%u,\"subtype\":%u},",
2270
2271 ais->type6.dac1fid25.cargos[i].code,
2272 ais->type6.dac1fid25.cargos[i].subtype);
2273 str_rstrip_char(buf, ',');
2274 (void)strlcat(buf, "]}\r\n", buflen);
2275 break;
2276 case 28: /* IMO289 - Route info - addressed */
2277 str_appendf(buf, buflen,
2278 "\"linkage\":%u,\"sender\":%u,"
2279 "\"rtype\":%u,"
2280 "\"rtype_text\":\"%s\","
2281 "\"start\":\"%02u-%02uT%02u:%02uZ\","
2282 "\"duration\":%u,\"waypoints\":[",
2283 ais->type6.dac1fid28.linkage,
2284 ais->type6.dac1fid28.sender,
2285 ais->type6.dac1fid28.rtype,
2286 route_type[ais->type6.dac1fid28.rtype],
2287 ais->type6.dac1fid28.month,
2288 ais->type6.dac1fid28.day,
2289 ais->type6.dac1fid28.hour,
2290 ais->type6.dac1fid28.minute,
2291 ais->type6.dac1fid28.duration);
2292 for (i = 0; i < ais->type6.dac1fid28.waycount; i++) {
2293 if (scaled)
2294 str_appendf(buf, buflen,
2295 "{\"lon\":%.6f,\"lat\":%.6f},",
2296 ais->type6.dac1fid28.waypoints[i].lon / AIS_LATLON4_DIV,
2297 ais->type6.dac1fid28.waypoints[i].lat / AIS_LATLON4_DIV);
2298 else
2299 str_appendf(buf, buflen,
2300 "{\"lon\":%d,\"lat\":%d},",
2301 ais->type6.dac1fid28.waypoints[i].lon,
2302 ais->type6.dac1fid28.waypoints[i].lat);
2303 }
2304 str_rstrip_char(buf, ',');
2305 (void)strlcat(buf, "]}\r\n", buflen);
2306 break;
2307 case 30: /* IMO289 - Text description - addressed */
2308 str_appendf(buf, buflen,
2309 "\"linkage\":%u,\"text\":\"%s\"}\r\n",
2310 ais->type6.dac1fid30.linkage,
2311 json_stringify(buf1, sizeof(buf1),
2312 ais->type6.dac1fid30.text));
2313 break;
2314 case 14: /* IMO236 - Tidal Window */
2315 case 32: /* IMO289 - Tidal Window */
2316 str_appendf(buf, buflen,
2317 "\"month\":%u,\"day\":%u,\"tidals\":[",
2318 ais->type6.dac1fid32.month,
2319 ais->type6.dac1fid32.day);
2320 for (i = 0; i < ais->type6.dac1fid32.ntidals; i++) {
2321 const struct tidal_t *tp = &ais->type6.dac1fid32.tidals[i];
2322 if (scaled)
2323 str_appendf(buf, buflen,
2324 "{\"lon\":%.4f,\"lat\":%.4f,",
2325 tp->lon / AIS_LATLON3_DIV,
2326 tp->lat / AIS_LATLON3_DIV);
2327 else
2328 str_appendf(buf, buflen,
2329 "{\"lon\":%d,\"lat\":%d,",
2330 tp->lon,
2331 tp->lat);
2332 str_appendf(buf, buflen,
2333 "\"from_hour\":%u,\"from_min\":%u,"
2334 "\"to_hour\":%u,\"to_min\":%u,\"cdir\":%u,",
2335 tp->from_hour,
2336 tp->from_min,
2337 tp->to_hour,
2338 tp->to_min,
2339 tp->cdir);
2340 if (scaled)
2341 str_appendf(buf, buflen,
2342 "\"cspeed\":%.1f},",
2343 tp->cspeed / 10.0);
2344 else
2345 str_appendf(buf, buflen,
2346 "\"cspeed\":%u},",
2347 tp->cspeed);
2348 }
2349 str_rstrip_char(buf, ',');
2350 (void)strlcat(buf, "]}\r\n", buflen);
2351 break;
2352 }
2353 }
2354 break;
2355 case 7: /* Binary Acknowledge */
2356 case 13: /* Safety Related Acknowledge */
2357 str_appendf(buf, buflen,
2358 "\"mmsi1\":%u,\"mmsi2\":%u,\"mmsi3\":%u,"
2359 "\"mmsi4\":%u}\r\n",
2360 ais->type7.mmsi1,
2361 ais->type7.mmsi2, ais->type7.mmsi3, ais->type7.mmsi4);
2362 break;
2363 case 8: /* Binary Broadcast Message */
2364 str_appendf(buf, buflen,
2365 "\"dac\":%u,\"fid\":%u,",ais->type8.dac, ais->type8.fid);
2366 if (!ais->type8.structured) {
2367 str_appendf(buf, buflen,
2368 "\"data\":\"%zd:%s\"}\r\n",
2369 ais->type8.bitcount,
2370 json_stringify(buf1, sizeof(buf1),
2371 gpsd_hexdump(
2372 scratchbuf, sizeof(scratchbuf),
2373 (char *)ais->type8.bitdata,
2374 BITS_TO_BYTES(ais->type8.bitcount))));
2375 break;
2376 }
2377 if (ais->type8.dac == 1) {
2378 const char *trends[] = {
2379 "steady",
2380 "increasing",
2381 "decreasing",
2382 "N/A",
2383 };
2384 // WMO 306, Code table 4.201
2385 const char *preciptypes[] = {
2386 "reserved",
2387 "rain",
2388 "thunderstorm",
2389 "freezing rain",
2390 "mixed/ice",
2391 "snow",
2392 "reserved",
2393 "N/A",
2394 };
2395 const char *ice[] = {
2396 "no",
2397 "yes",
2398 "reserved",
2399 "N/A",
2400 };
2401 switch (ais->type8.fid) {
2402 case 11: /* IMO236 - Meteorological/Hydrological data */
2403 /* some fields have been merged to an ISO8601 partial date */
2404 /* layout is almost identical to FID=31 from IMO289 */
2405 if (scaled)
2406 str_appendf(buf, buflen,
2407 "\"lat\":%.4f,\"lon\":%.4f,",
2408 ais->type8.dac1fid11.lat / AIS_LATLON3_DIV,
2409 ais->type8.dac1fid11.lon / AIS_LATLON3_DIV);
2410 else
2411 str_appendf(buf, buflen,
2412 "\"lat\":%d,\"lon\":%d,",
2413 ais->type8.dac1fid11.lat,
2414 ais->type8.dac1fid11.lon);
2415 str_appendf(buf, buflen,
2416 "\"timestamp\":\"%02uT%02u:%02uZ\","
2417 "\"wspeed\":%u,\"wgust\":%u,\"wdir\":%u,"
2418 "\"wgustdir\":%u,\"humidity\":%u,",
2419 ais->type8.dac1fid11.day,
2420 ais->type8.dac1fid11.hour,
2421 ais->type8.dac1fid11.minute,
2422 ais->type8.dac1fid11.wspeed,
2423 ais->type8.dac1fid11.wgust,
2424 ais->type8.dac1fid11.wdir,
2425 ais->type8.dac1fid11.wgustdir,
2426 ais->type8.dac1fid11.humidity);
2427 if (scaled)
2428 str_appendf(buf, buflen,
2429 "\"airtemp\":%.1f,\"dewpoint\":%.1f,"
2430 "\"pressure\":%u,\"pressuretend\":\"%s\",",
2431 ((signed int)ais->type8.dac1fid11.airtemp - DAC1FID11_AIRTEMP_OFFSET) / DAC1FID11_AIRTEMP_DIV,
2432 ((signed int)ais->type8.dac1fid11.dewpoint - DAC1FID11_DEWPOINT_OFFSET) / DAC1FID11_DEWPOINT_DIV,
2433 ais->type8.dac1fid11.pressure - DAC1FID11_PRESSURE_OFFSET,
2434 trends[ais->type8.dac1fid11.pressuretend]);
2435 else
2436 str_appendf(buf, buflen,
2437 "\"airtemp\":%u,\"dewpoint\":%u,"
2438 "\"pressure\":%u,\"pressuretend\":%u,",
2439 ais->type8.dac1fid11.airtemp,
2440 ais->type8.dac1fid11.dewpoint,
2441 ais->type8.dac1fid11.pressure,
2442 ais->type8.dac1fid11.pressuretend);
2443
2444 if (scaled)
2445 str_appendf(buf, buflen,
2446 "\"visibility\":%.1f,",
2447 ais->type8.dac1fid11.visibility / DAC1FID11_VISIBILITY_DIV);
2448 else
2449 str_appendf(buf, buflen,
2450 "\"visibility\":%u,",
2451 ais->type8.dac1fid11.visibility);
2452 if (!scaled)
2453 str_appendf(buf, buflen,
2454 "\"waterlevel\":%d,",
2455 ais->type8.dac1fid11.waterlevel);
2456 else
2457 str_appendf(buf, buflen,
2458 "\"waterlevel\":%.1f,",
2459 ((signed int)ais->type8.dac1fid11.waterlevel - DAC1FID11_WATERLEVEL_OFFSET) / DAC1FID11_WATERLEVEL_DIV);
2460
2461 if (scaled) {
2462 str_appendf(buf, buflen,
2463 "\"leveltrend\":\"%s\","
2464 "\"cspeed\":%.1f,\"cdir\":%u,"
2465 "\"cspeed2\":%.1f,\"cdir2\":%u,"
2466 "\"cdepth2\":%u,"
2467 "\"cspeed3\":%.1f,\"cdir3\":%u,"
2468 "\"cdepth3\":%u,"
2469 "\"waveheight\":%.1f,\"waveperiod\":%u,"
2470 "\"wavedir\":%u,"
2471 "\"swellheight\":%.1f,\"swellperiod\":%u,"
2472 "\"swelldir\":%u,"
2473 "\"seastate\":%u,\"watertemp\":%.1f,"
2474 "\"preciptype\":%u,"
2475 "\"preciptype_text\":\"%s\","
2476 "\"salinity\":%.1f,\"ice\":%u,"
2477 "\"ice_text\":\"%s\"",
2478 trends[ais->type8.dac1fid11.leveltrend],
2479 ais->type8.dac1fid11.cspeed / DAC1FID11_CSPEED_DIV,
2480 ais->type8.dac1fid11.cdir,
2481 ais->type8.dac1fid11.cspeed2 / DAC1FID11_CSPEED_DIV,
2482 ais->type8.dac1fid11.cdir2,
2483 ais->type8.dac1fid11.cdepth2,
2484 ais->type8.dac1fid11.cspeed3 / DAC1FID11_CSPEED_DIV,
2485 ais->type8.dac1fid11.cdir3,
2486 ais->type8.dac1fid11.cdepth3,
2487 ais->type8.dac1fid11.waveheight / DAC1FID11_WAVEHEIGHT_DIV,
2488 ais->type8.dac1fid11.waveperiod,
2489 ais->type8.dac1fid11.wavedir,
2490 ais->type8.dac1fid11.swellheight / DAC1FID11_WAVEHEIGHT_DIV,
2491 ais->type8.dac1fid11.swellperiod,
2492 ais->type8.dac1fid11.swelldir,
2493 ais->type8.dac1fid11.seastate,
2494 ((signed int)ais->type8.dac1fid11.watertemp - DAC1FID11_WATERTEMP_OFFSET) / DAC1FID11_WATERTEMP_DIV,
2495 ais->type8.dac1fid11.preciptype,
2496 preciptypes[ais->type8.dac1fid11.preciptype],
2497 ais->type8.dac1fid11.salinity / DAC1FID11_SALINITY_DIV,
2498 ais->type8.dac1fid11.ice,
2499 ice[ais->type8.dac1fid11.ice]);
2500 } else
2501 str_appendf(buf, buflen,
2502 "\"leveltrend\":%u,"
2503 "\"cspeed\":%u,\"cdir\":%u,"
2504 "\"cspeed2\":%u,\"cdir2\":%u,"
2505 "\"cdepth2\":%u,"
2506 "\"cspeed3\":%u,\"cdir3\":%u,"
2507 "\"cdepth3\":%u,"
2508 "\"waveheight\":%u,\"waveperiod\":%u,"
2509 "\"wavedir\":%u,"
2510 "\"swellheight\":%u,\"swellperiod\":%u,"
2511 "\"swelldir\":%u,"
2512 "\"seastate\":%u,\"watertemp\":%u,"
2513 "\"preciptype\":%u,"
2514 "\"preciptype_text\":\"%s\","
2515 "\"salinity\":%u,\"ice\":%u,"
2516 "\"ice_text\":\"%s\"",
2517 ais->type8.dac1fid11.leveltrend,
2518 ais->type8.dac1fid11.cspeed,
2519 ais->type8.dac1fid11.cdir,
2520 ais->type8.dac1fid11.cspeed2,
2521 ais->type8.dac1fid11.cdir2,
2522 ais->type8.dac1fid11.cdepth2,
2523 ais->type8.dac1fid11.cspeed3,
2524 ais->type8.dac1fid11.cdir3,
2525 ais->type8.dac1fid11.cdepth3,
2526 ais->type8.dac1fid11.waveheight,
2527 ais->type8.dac1fid11.waveperiod,
2528 ais->type8.dac1fid11.wavedir,
2529 ais->type8.dac1fid11.swellheight,
2530 ais->type8.dac1fid11.swellperiod,
2531 ais->type8.dac1fid11.swelldir,
2532 ais->type8.dac1fid11.seastate,
2533 ais->type8.dac1fid11.watertemp,
2534 ais->type8.dac1fid11.preciptype,
2535 preciptypes[ais->type8.dac1fid11.preciptype],
2536 ais->type8.dac1fid11.salinity,
2537 ais->type8.dac1fid11.ice,
2538 ice[ais->type8.dac1fid11.ice]);
2539 (void)strlcat(buf, "}\r\n", buflen);
2540 break;
2541 case 13: /* IMO236 - Fairway closed */
2542 str_appendf(buf, buflen,
2543 "\"reason\":\"%s\",\"closefrom\":\"%s\","
2544 "\"closeto\":\"%s\",\"radius\":%u,"
2545 "\"extunit\":%u,"
2546 "\"from\":\"%02u-%02uT%02u:%02u\","
2547 "\"to\":\"%02u-%02uT%02u:%02u\"}\r\n",
2548 json_stringify(buf1, sizeof(buf1),
2549 ais->type8.dac1fid13.reason),
2550 json_stringify(buf2, sizeof(buf2),
2551 ais->type8.dac1fid13.closefrom),
2552 json_stringify(buf3, sizeof(buf3),
2553 ais->type8.dac1fid13.closeto),
2554 ais->type8.dac1fid13.radius,
2555 ais->type8.dac1fid13.extunit,
2556 ais->type8.dac1fid13.fmonth,
2557 ais->type8.dac1fid13.fday,
2558 ais->type8.dac1fid13.fhour,
2559 ais->type8.dac1fid13.fminute,
2560 ais->type8.dac1fid13.tmonth,
2561 ais->type8.dac1fid13.tday,
2562 ais->type8.dac1fid13.thour,
2563 ais->type8.dac1fid13.tminute);
2564 break;
2565 case 15: /* IMO236 - Extended ship and voyage */
2566 str_appendf(buf, buflen,
2567 "\"airdraught\":%u}\r\n",
2568 ais->type8.dac1fid15.airdraught);
2569 break;
2570 case 16: /* IMO289 - Number of persons on board */
2571 str_appendf(buf, buflen,
2572 "\"persons\":%u}\r\n",
2573 ais->type6.dac1fid16.persons);
2574 break;
2575 case 17: /* IMO289 - VTS-generated/synthetic targets */
2576 (void)strlcat(buf, "\"targets\":[", buflen);
2577 for (i = 0; i < ais->type8.dac1fid17.ntargets; i++) {
2578 str_appendf(buf, buflen,
2579 "{\"idtype\":%u,\"idtype_text\":\"%s\",",
2580 ais->type8.dac1fid17.targets[i].idtype,
2581 idtypes[ais->type8.dac1fid17.targets[i].idtype]);
2582 switch (ais->type8.dac1fid17.targets[i].idtype) {
2583 case DAC1FID17_IDTYPE_MMSI:
2584 str_appendf(buf, buflen,
2585 "\"%s\":\"%u\",",
2586 idtypes[ais->type8.dac1fid17.targets[i].idtype],
2587 ais->type8.dac1fid17.targets[i].id.mmsi);
2588 break;
2589 case DAC1FID17_IDTYPE_IMO:
2590 str_appendf(buf, buflen,
2591 "\"%s\":\"%u\",",
2592 idtypes[ais->type8.dac1fid17.targets[i].idtype],
2593 ais->type8.dac1fid17.targets[i].id.imo);
2594 break;
2595 case DAC1FID17_IDTYPE_CALLSIGN:
2596 str_appendf(buf, buflen,
2597 "\"%s\":\"%s\",",
2598 idtypes[ais->type8.dac1fid17.targets[i].idtype],
2599 json_stringify(buf1, sizeof(buf1),
2600 ais->type8.dac1fid17.targets[i].id.callsign));
2601 break;
2602 default:
2603 str_appendf(buf, buflen,
2604 "\"%s\":\"%s\",",
2605 idtypes[ais->type8.dac1fid17.targets[i].idtype],
2606 json_stringify(buf1, sizeof(buf1),
2607 ais->type8.dac1fid17.targets[i].id.other));
2608 }
2609 if (scaled)
2610 str_appendf(buf, buflen,
2611 "\"lat\":%.4f,\"lon\":%.4f,",
2612 ais->type8.dac1fid17.targets[i].lat / AIS_LATLON3_DIV,
2613 ais->type8.dac1fid17.targets[i].lon / AIS_LATLON3_DIV);
2614 else
2615 str_appendf(buf, buflen,
2616 "\"lat\":%d,\"lon\":%d,",
2617 ais->type8.dac1fid17.targets[i].lat,
2618 ais->type8.dac1fid17.targets[i].lon);
2619 str_appendf(buf, buflen,
2620 "\"course\":%u,\"second\":%u,\"speed\":%u},",
2621 ais->type8.dac1fid17.targets[i].course,
2622 ais->type8.dac1fid17.targets[i].second,
2623 ais->type8.dac1fid17.targets[i].speed);
2624 }
2625 str_rstrip_char(buf, ',');
2626 (void)strlcat(buf, "]}\r\n", buflen);
2627 break;
2628 case 19: /* IMO289 - Marine Traffic Signal */
2629 str_appendf(buf, buflen,
2630 "\"linkage\":%u,\"station\":\"%s\","
2631 "\"lon\":%.4f,\"lat\":%.4f,\"status\":%u,"
2632 "\"signal\":%u,\"signal_text\":\"%s\","
2633 "\"hour\":%u,\"minute\":%u,"
2634 "\"nextsignal\":%u"
2635 "\"nextsignal_text\":\"%s\""
2636 "}\r\n",
2637 ais->type8.dac1fid19.linkage,
2638 json_stringify(buf1, sizeof(buf1),
2639 ais->type8.dac1fid19.station),
2640 ais->type8.dac1fid19.lon / AIS_LATLON3_DIV,
2641 ais->type8.dac1fid19.lat / AIS_LATLON3_DIV,
2642 ais->type8.dac1fid19.status,
2643 ais->type8.dac1fid19.signal,
2644 SIGNAL_DISPLAY(ais->type8.dac1fid19.signal),
2645 ais->type8.dac1fid19.hour,
2646 ais->type8.dac1fid19.minute,
2647 ais->type8.dac1fid19.nextsignal,
2648 SIGNAL_DISPLAY(ais->type8.dac1fid19.nextsignal));
2649 break;
2650 case 21: /* IMO289 - Weather obs. report from ship */
2651 break;
2652 case 22: /* IMO289 - Area notice - broadcast */
2653 break;
2654 case 24: /* IMO289 - Extended ship static & voyage-related data */
2655 break;
2656 case 25: /* IMO289 - Dangerous Cargo Indication */
2657 break;
2658 case 27: /* IMO289 - Route information - broadcast */
2659 str_appendf(buf, buflen,
2660 "\"linkage\":%u,\"sender\":%u,"
2661 "\"rtype\":%u,"
2662 "\"rtype_text\":\"%s\","
2663 "\"start\":\"%02u-%02uT%02u:%02uZ\","
2664 "\"duration\":%u,\"waypoints\":[",
2665 ais->type8.dac1fid27.linkage,
2666 ais->type8.dac1fid27.sender,
2667 ais->type8.dac1fid27.rtype,
2668 route_type[ais->type8.dac1fid27.rtype],
2669 ais->type8.dac1fid27.month,
2670 ais->type8.dac1fid27.day,
2671 ais->type8.dac1fid27.hour,
2672 ais->type8.dac1fid27.minute,
2673 ais->type8.dac1fid27.duration);
2674 for (i = 0; i < ais->type8.dac1fid27.waycount; i++) {
2675 if (scaled)
2676 str_appendf(buf, buflen,
2677 "{\"lon\":%.6f,\"lat\":%.6f},",
2678 ais->type8.dac1fid27.waypoints[i].lon / AIS_LATLON4_DIV,
2679 ais->type8.dac1fid27.waypoints[i].lat / AIS_LATLON4_DIV);
2680 else
2681 str_appendf(buf, buflen,
2682 "{\"lon\":%d,\"lat\":%d},",
2683 ais->type8.dac1fid27.waypoints[i].lon,
2684 ais->type8.dac1fid27.waypoints[i].lat);
2685 }
2686 str_rstrip_char(buf, ',');
2687 (void)strlcat(buf, "]}\r\n", buflen);
2688 break;
2689 case 29: /* IMO289 - Text Description - broadcast */
2690 str_appendf(buf, buflen,
2691 "\"linkage\":%u,\"text\":\"%s\"}\r\n",
2692 ais->type8.dac1fid29.linkage,
2693 json_stringify(buf1, sizeof(buf1),
2694 ais->type8.dac1fid29.text));
2695 break;
2696 case 31: /* IMO289 - Meteorological/Hydrological data */
2697 /* some fields have been merged to an ISO8601 partial date */
2698 /* layout is almost identical to FID=11 from IMO236 */
2699 if (scaled)
2700 str_appendf(buf, buflen,
2701 "\"lat\":%.4f,\"lon\":%.4f,",
2702 ais->type8.dac1fid31.lat / AIS_LATLON3_DIV,
2703 ais->type8.dac1fid31.lon / AIS_LATLON3_DIV);
2704 else
2705 str_appendf(buf, buflen,
2706 "\"lat\":%d,\"lon\":%d,",
2707 ais->type8.dac1fid31.lat,
2708 ais->type8.dac1fid31.lon);
2709 str_appendf(buf, buflen,
2710 "\"accuracy\":%s,",
2711 JSON_BOOL(ais->type8.dac1fid31.accuracy));
2712 str_appendf(buf, buflen,
2713 "\"timestamp\":\"%02uT%02u:%02uZ\","
2714 "\"wspeed\":%u,\"wgust\":%u,\"wdir\":%u,"
2715 "\"wgustdir\":%u,\"humidity\":%u,",
2716 ais->type8.dac1fid31.day,
2717 ais->type8.dac1fid31.hour,
2718 ais->type8.dac1fid31.minute,
2719 ais->type8.dac1fid31.wspeed,
2720 ais->type8.dac1fid31.wgust,
2721 ais->type8.dac1fid31.wdir,
2722 ais->type8.dac1fid31.wgustdir,
2723 ais->type8.dac1fid31.humidity);
2724 if (scaled)
2725 str_appendf(buf, buflen,
2726 "\"airtemp\":%.1f,\"dewpoint\":%.1f,"
2727 "\"pressure\":%u,\"pressuretend\":\"%s\","
2728 "\"visgreater\":%s,",
2729 ais->type8.dac1fid31.airtemp / DAC1FID31_AIRTEMP_DIV,
2730 ais->type8.dac1fid31.dewpoint / DAC1FID31_DEWPOINT_DIV,
2731 ais->type8.dac1fid31.pressure - DAC1FID31_PRESSURE_OFFSET,
2732 trends[ais->type8.dac1fid31.pressuretend],
2733 JSON_BOOL(ais->type8.dac1fid31.visgreater));
2734 else
2735 str_appendf(buf, buflen,
2736 "\"airtemp\":%d,\"dewpoint\":%d,"
2737 "\"pressure\":%u,\"pressuretend\":%u,"
2738 "\"visgreater\":%s,",
2739 ais->type8.dac1fid31.airtemp,
2740 ais->type8.dac1fid31.dewpoint,
2741 ais->type8.dac1fid31.pressure,
2742 ais->type8.dac1fid31.pressuretend,
2743 JSON_BOOL(ais->type8.dac1fid31.visgreater));
2744
2745 if (scaled)
2746 str_appendf(buf, buflen,
2747 "\"visibility\":%.1f,",
2748 ais->type8.dac1fid31.visibility / DAC1FID31_VISIBILITY_DIV);
2749 else
2750 str_appendf(buf, buflen,
2751 "\"visibility\":%u,",
2752 ais->type8.dac1fid31.visibility);
2753 if (!scaled)
2754 str_appendf(buf, buflen,
2755 "\"waterlevel\":%d,",
2756 ais->type8.dac1fid31.waterlevel);
2757 else
2758 str_appendf(buf, buflen,
2759 "\"waterlevel\":%.1f,",
2760 ((unsigned int)ais->type8.dac1fid31.waterlevel - DAC1FID31_WATERLEVEL_OFFSET) / DAC1FID31_WATERLEVEL_DIV);
2761
2762 if (scaled) {
2763 str_appendf(buf, buflen,
2764 "\"leveltrend\":\"%s\","
2765 "\"cspeed\":%.1f,\"cdir\":%u,"
2766 "\"cspeed2\":%.1f,\"cdir2\":%u,"
2767 "\"cdepth2\":%u,"
2768 "\"cspeed3\":%.1f,\"cdir3\":%u,"
2769 "\"cdepth3\":%u,"
2770 "\"waveheight\":%.1f,\"waveperiod\":%u,"
2771 "\"wavedir\":%u,"
2772 "\"swellheight\":%.1f,\"swellperiod\":%u,"
2773 "\"swelldir\":%u,"
2774 "\"seastate\":%u,\"watertemp\":%.1f,"
2775 "\"preciptype\":\"%s\",\"salinity\":%.1f,"
2776 "\"ice\":\"%s\"",
2777 trends[ais->type8.dac1fid31.leveltrend],
2778 ais->type8.dac1fid31.cspeed / DAC1FID31_CSPEED_DIV,
2779 ais->type8.dac1fid31.cdir,
2780 ais->type8.dac1fid31.cspeed2 / DAC1FID31_CSPEED_DIV,
2781 ais->type8.dac1fid31.cdir2,
2782 ais->type8.dac1fid31.cdepth2,
2783 ais->type8.dac1fid31.cspeed3 / DAC1FID31_CSPEED_DIV,
2784 ais->type8.dac1fid31.cdir3,
2785 ais->type8.dac1fid31.cdepth3,
2786 ais->type8.dac1fid31.waveheight / DAC1FID31_HEIGHT_DIV,
2787 ais->type8.dac1fid31.waveperiod,
2788 ais->type8.dac1fid31.wavedir,
2789 ais->type8.dac1fid31.swellheight / DAC1FID31_HEIGHT_DIV,
2790 ais->type8.dac1fid31.swellperiod,
2791 ais->type8.dac1fid31.swelldir,
2792 ais->type8.dac1fid31.seastate,
2793 ais->type8.dac1fid31.watertemp / DAC1FID31_WATERTEMP_DIV,
2794 preciptypes[ais->type8.dac1fid31.preciptype],
2795 ais->type8.dac1fid31.salinity / DAC1FID31_SALINITY_DIV,
2796 ice[ais->type8.dac1fid31.ice]);
2797 } else
2798 str_appendf(buf, buflen,
2799 "\"leveltrend\":%u,"
2800 "\"cspeed\":%u,\"cdir\":%u,"
2801 "\"cspeed2\":%u,\"cdir2\":%u,"
2802 "\"cdepth2\":%u,"
2803 "\"cspeed3\":%u,\"cdir3\":%u,"
2804 "\"cdepth3\":%u,"
2805 "\"waveheight\":%u,\"waveperiod\":%u,"
2806 "\"wavedir\":%u,"
2807 "\"swellheight\":%u,\"swellperiod\":%u,"
2808 "\"swelldir\":%u,"
2809 "\"seastate\":%u,\"watertemp\":%d,"
2810 "\"preciptype\":%u,\"salinity\":%u,"
2811 "\"ice\":%u",
2812 ais->type8.dac1fid31.leveltrend,
2813 ais->type8.dac1fid31.cspeed,
2814 ais->type8.dac1fid31.cdir,
2815 ais->type8.dac1fid31.cspeed2,
2816 ais->type8.dac1fid31.cdir2,
2817 ais->type8.dac1fid31.cdepth2,
2818 ais->type8.dac1fid31.cspeed3,
2819 ais->type8.dac1fid31.cdir3,
2820 ais->type8.dac1fid31.cdepth3,
2821 ais->type8.dac1fid31.waveheight,
2822 ais->type8.dac1fid31.waveperiod,
2823 ais->type8.dac1fid31.wavedir,
2824 ais->type8.dac1fid31.swellheight,
2825 ais->type8.dac1fid31.swellperiod,
2826 ais->type8.dac1fid31.swelldir,
2827 ais->type8.dac1fid31.seastate,
2828 ais->type8.dac1fid31.watertemp,
2829 ais->type8.dac1fid31.preciptype,
2830 ais->type8.dac1fid31.salinity,
2831 ais->type8.dac1fid31.ice);
2832 (void)strlcat(buf, "}\r\n", buflen);
2833 break;
2834 }
2835 }
2836 else if (ais->type8.dac == 200) {
2837 struct {
2838 const unsigned int code;
2839 const unsigned int ais;
2840 const char *legend;
2841 } *cp, shiptypes[] = {
2842 /*
2843 * The Inland AIS standard is not clear which numbers are
2844 * supposed to be in the type slot. The ranges are disjoint,
2845 * so we'll match on both.
2846 */
2847 {8000, 99, "Vessel, type unknown"},
2848 {8010, 79, "Motor freighter"},
2849 {8020, 89, "Motor tanker"},
2850 {8021, 80, "Motor tanker, liquid cargo, type N"},
2851 {8022, 80, "Motor tanker, liquid cargo, type C"},
2852 {8023, 89,
2853 "Motor tanker, dry cargo as if liquid (e.g. cement)"},
2854 {8030, 79, "Container vessel"},
2855 {8040, 80, "Gas tanker"},
2856 {8050, 79, "Motor freighter, tug"},
2857 {8060, 89, "Motor tanker, tug"},
2858 {8070, 79, "Motor freighter with one or more ships alongside"},
2859 {8080, 89, "Motor freighter with tanker"},
2860 {8090, 79, "Motor freighter pushing one or more freighters"},
2861 {8100, 89, "Motor freighter pushing at least one tank-ship"},
2862 {8110, 79, "Tug, freighter"},
2863 {8120, 89, "Tug, tanker"},
2864 {8130, 31, "Tug freighter, coupled"},
2865 {8140, 31, "Tug, freighter/tanker, coupled"},
2866 {8150, 99, "Freightbarge"},
2867 {8160, 99, "Tankbarge"},
2868 {8161, 90, "Tankbarge, liquid cargo, type N"},
2869 {8162, 90, "Tankbarge, liquid cargo, type C"},
2870 {8163, 99, "Tankbarge, dry cargo as if liquid (e.g. cement)"},
2871 {8170, 99, "Freightbarge with containers"},
2872 {8180, 90, "Tankbarge, gas"},
2873 {8210, 79, "Pushtow, one cargo barge"},
2874 {8220, 79, "Pushtow, two cargo barges"},
2875 {8230, 79, "Pushtow, three cargo barges"},
2876 {8240, 79, "Pushtow, four cargo barges"},
2877 {8250, 79, "Pushtow, five cargo barges"},
2878 {8260, 79, "Pushtow, six cargo barges"},
2879 {8270, 79, "Pushtow, seven cargo barges"},
2880 {8280, 79, "Pushtow, eigth cargo barges"},
2881 {8290, 79, "Pushtow, nine or more barges"},
2882 {8310, 80, "Pushtow, one tank/gas barge"},
2883 {8320, 80,
2884 "Pushtow, two barges at least one tanker or gas barge"},
2885 {8330, 80,
2886 "Pushtow, three barges at least one tanker or gas barge"},
2887 {8340, 80,
2888 "Pushtow, four barges at least one tanker or gas barge"},
2889 {8350, 80,
2890 "Pushtow, five barges at least one tanker or gas barge"},
2891 {8360, 80,
2892 "Pushtow, six barges at least one tanker or gas barge"},
2893 {8370, 80,
2894 "Pushtow, seven barges at least one tanker or gas barg"},
2895 {0, 0, "Illegal ship type value."},
2896 };
2897 const char *hazard_types[] = {
2898 "0 blue cones/lights",
2899 "1 blue cone/light",
2900 "2 blue cones/lights",
2901 "3 blue cones/lights",
2902 "4 B-Flag",
2903 "Unknown",
2904 };
2905 #define HTYPE_DISPLAY(n) (((n) < (unsigned int)NITEMS(hazard_types)) ? hazard_types[n] : "INVALID HAZARD TYPE")
2906 const char *lstatus_types[] = {
2907 "N/A (default)",
2908 "Unloaded",
2909 "Loaded",
2910 };
2911 #define LSTATUS_DISPLAY(n) (((n) < (unsigned int)NITEMS(lstatus_types)) ? lstatus_types[n] : "INVALID LOAD STATUS")
2912 const char *emma_types[] = {
2913 "Not Available",
2914 "Wind",
2915 "Rain",
2916 "Snow and ice",
2917 "Thunderstorm",
2918 "Fog",
2919 "Low temperature",
2920 "High temperature",
2921 "Flood",
2922 "Forest Fire",
2923 };
2924 #define EMMA_TYPE_DISPLAY(n) (((n) < (unsigned int)NITEMS(emma_types)) ? emma_types[n] : "INVALID EMMA TYPE")
2925 const char *emma_classes[] = {
2926 "Slight",
2927 "Medium",
2928 "Strong",
2929 };
2930 #define EMMA_CLASS_DISPLAY(n) (((n) < (unsigned int)NITEMS(emma_classes)) ? emma_classes[n] : "INVALID EMMA TYPE")
2931 const char *emma_winds[] = {
2932 "N/A",
2933 "North",
2934 "North East",
2935 "East",
2936 "South East",
2937 "South",
2938 "South West",
2939 "West",
2940 "North West",
2941 };
2942 #define EMMA_WIND_DISPLAY(n) (((n) < (unsigned int)NITEMS(emma_winds)) ? emma_winds[n] : "INVALID EMMA WIND DIRECTION")
2943 const char *direction_vocabulary[] = {
2944 "Unknown",
2945 "Upstream",
2946 "Downstream",
2947 "To left bank",
2948 "To right bank",
2949 };
2950 #define DIRECTION_DISPLAY(n) (((n) < (unsigned int)NITEMS(direction_vocabulary)) ? direction_vocabulary[n] : "INVALID DIRECTION")
2951 const char *status_vocabulary[] = {
2952 "Unknown",
2953 "No light",
2954 "White",
2955 "Yellow",
2956 "Green",
2957 "Red",
2958 "White flashing",
2959 "Yellow flashing.",
2960 };
2961 #define STATUS_DISPLAY(n) (((n) < (unsigned int)NITEMS(status_vocabulary)) ? status_vocabulary[n] : "INVALID STATUS")
2962
2963 switch (ais->type8.fid) {
2964 case 10: /* Inland ship static and voyage-related data */
2965 for (cp = shiptypes; cp < shiptypes + NITEMS(shiptypes); cp++)
2966 if (cp->code == ais->type8.dac200fid10.shiptype
2967 || cp->ais == ais->type8.dac200fid10.shiptype
2968 || cp->code == 0)
2969 break;
2970 str_appendf(buf, buflen,
2971 "\"vin\":\"%s\",\"length\":%u,\"beam\":%u,"
2972 "\"shiptype\":%u,\"shiptype_text\":\"%s\","
2973 "\"hazard\":%u,\"hazard_text\":\"%s\","
2974 "\"draught\":%u,"
2975 "\"loaded\":%u,\"loaded_text\":\"%s\","
2976 "\"speed_q\":%s,"
2977 "\"course_q\":%s,"
2978 "\"heading_q\":%s}\r\n",
2979 json_stringify(buf1, sizeof(buf1),
2980 ais->type8.dac200fid10.vin),
2981 ais->type8.dac200fid10.length,
2982 ais->type8.dac200fid10.beam,
2983 ais->type8.dac200fid10.shiptype,
2984 cp->legend,
2985 ais->type8.dac200fid10.hazard,
2986 HTYPE_DISPLAY(ais->type8.dac200fid10.hazard),
2987 ais->type8.dac200fid10.draught,
2988 ais->type8.dac200fid10.loaded,
2989 LSTATUS_DISPLAY(ais->type8.dac200fid10.loaded),
2990 JSON_BOOL(ais->type8.dac200fid10.speed_q),
2991 JSON_BOOL(ais->type8.dac200fid10.course_q),
2992 JSON_BOOL(ais->type8.dac200fid10.heading_q));
2993 break;
2994 case 23: /* EMMA warning */
2995 if (!ais->type8.structured)
2996 break;
2997 str_appendf(buf, buflen,
2998 "\"start\":\"%4u-%02u-%02uT%02u:%02u\","
2999 "\"end\":\"%4u-%02u-%02uT%02u:%02u\",",
3000 ais->type8.dac200fid23.start_year + 2000,
3001 ais->type8.dac200fid23.start_month,
3002 ais->type8.dac200fid23.start_hour,
3003 ais->type8.dac200fid23.start_minute,
3004 ais->type8.dac200fid23.start_day,
3005 ais->type8.dac200fid23.end_year + 2000,
3006 ais->type8.dac200fid23.end_month,
3007 ais->type8.dac200fid23.end_day,
3008 ais->type8.dac200fid23.end_hour,
3009 ais->type8.dac200fid23.end_minute);
3010 if (scaled)
3011 str_appendf(buf, buflen,
3012 "\"start_lon\":%.6f,\"start_lat\":%.6f,"
3013 "\"end_lon\":%.6f,\"end_lat\":%.6f,",
3014 ais->type8.dac200fid23.start_lon / AIS_LATLON_DIV,
3015 ais->type8.dac200fid23.start_lat / AIS_LATLON_DIV,
3016 ais->type8.dac200fid23.end_lon / AIS_LATLON_DIV,
3017 ais->type8.dac200fid23.end_lat / AIS_LATLON_DIV);
3018 else
3019 str_appendf(buf, buflen,
3020 "\"start_lon\":%d,\"start_lat\":%d,\"end_lon\":%d,"
3021 "\"end_lat\":%d,",
3022 ais->type8.dac200fid23.start_lon,
3023 ais->type8.dac200fid23.start_lat,
3024 ais->type8.dac200fid23.end_lon,
3025 ais->type8.dac200fid23.end_lat);
3026 str_appendf(buf, buflen,
3027 "\"type\":%u,\"type_text\":\"%s\",\"min\":%d,"
3028 "\"max\":%d,\"class\":%u,\"class_text\":\"%s\","
3029 "\"wind\":%u,\"wind_text\":\"%s\"}\r\n",
3030
3031 ais->type8.dac200fid23.type,
3032 EMMA_TYPE_DISPLAY(ais->type8.dac200fid23.type),
3033 ais->type8.dac200fid23.min,
3034 ais->type8.dac200fid23.max,
3035 ais->type8.dac200fid23.intensity,
3036 EMMA_CLASS_DISPLAY(ais->type8.dac200fid23.intensity),
3037 ais->type8.dac200fid23.wind,
3038 EMMA_WIND_DISPLAY(ais->type8.dac200fid23.wind));
3039 break;
3040 case 24: /* Inland AIS Water Levels */
3041 str_appendf(buf, buflen,
3042 "\"country\":\"%s\",\"gauges\":[",
3043 ais->type8.dac200fid24.country);
3044 for (i = 0; i < ais->type8.dac200fid24.ngauges; i++) {
3045 str_appendf(buf, buflen,
3046 "{\"id\":%u,\"level\":%d},",
3047 ais->type8.dac200fid24.gauges[i].id,
3048 ais->type8.dac200fid24.gauges[i].level);
3049 }
3050 str_rstrip_char(buf, ',');
3051 (void)strlcat(buf, "]}\r\n", buflen);
3052 break;
3053 case 40: /* Inland AIS Signal Strength */
3054 if (scaled)
3055 str_appendf(buf, buflen,
3056 "\"lon\":%.6f,\"lat\":%.6f,",
3057 ais->type8.dac200fid40.lon / AIS_LATLON_DIV,
3058 ais->type8.dac200fid40.lat / AIS_LATLON_DIV);
3059 else
3060 str_appendf(buf, buflen,
3061 "\"lon\":%d,\"lat\":%d,",
3062 ais->type8.dac200fid40.lon,
3063 ais->type8.dac200fid40.lat);
3064 str_appendf(buf, buflen,
3065 "\"form\":%u,\"facing\":%u,\"direction\":%u,"
3066 "\"direction_text\":\"%s\",\"status\":%u,"
3067 "\"status_text\":\"%s\"}\r\n",
3068 ais->type8.dac200fid40.form,
3069 ais->type8.dac200fid40.facing,
3070 ais->type8.dac200fid40.direction,
3071 DIRECTION_DISPLAY(ais->type8.dac200fid40.direction),
3072 ais->type8.dac200fid40.status,
3073 STATUS_DISPLAY(ais->type8.dac200fid40.status));
3074 break;
3075 }
3076 }
3077 break;
3078 case 9: /* Standard SAR Aircraft Position Report */
3079 if (scaled) {
3080 char altlegend[20];
3081 char speedlegend[20];
3082
3083 /*
3084 * Express altitude as nan if not available,
3085 * "high" for above the reporting ceiling.
3086 */
3087 if (ais->type9.alt == AIS_ALT_NOT_AVAILABLE)
3088 (void)strlcpy(altlegend, "\"nan\"", sizeof(altlegend));
3089 else if (ais->type9.alt == AIS_ALT_HIGH)
3090 (void)strlcpy(altlegend, "\"high\"", sizeof(altlegend));
3091 else
3092 (void)snprintf(altlegend, sizeof(altlegend),
3093 "%u", ais->type9.alt);
3094
3095 /*
3096 * Express speed as nan if not available,
3097 * "high" for above the reporting ceiling.
3098 */
3099 if (ais->type9.speed == AIS_SAR_SPEED_NOT_AVAILABLE)
3100 (void)strlcpy(speedlegend, "\"nan\"", sizeof(speedlegend));
3101 else if (ais->type9.speed == AIS_SAR_FAST_MOVER)
3102 (void)strlcpy(speedlegend, "\"fast\"", sizeof(speedlegend));
3103 else
3104 (void)snprintf(speedlegend, sizeof(speedlegend),
3105 "%u", ais->type9.speed);
3106
3107 str_appendf(buf, buflen,
3108 "\"alt\":%s,\"speed\":%s,\"accuracy\":%s,"
3109 "\"lon\":%.6f,\"lat\":%.6f,\"course\":%.1f,"
3110 "\"second\":%u,\"regional\":%u,\"dte\":%u,"
3111 "\"raim\":%s,\"radio\":%u}\r\n",
3112 altlegend,
3113 speedlegend,
3114 JSON_BOOL(ais->type9.accuracy),
3115 ais->type9.lon / AIS_LATLON_DIV,
3116 ais->type9.lat / AIS_LATLON_DIV,
3117 ais->type9.course / 10.0,
3118 ais->type9.second,
3119 ais->type9.regional,
3120 ais->type9.dte,
3121 JSON_BOOL(ais->type9.raim), ais->type9.radio);
3122 } else {
3123 str_appendf(buf, buflen,
3124 "\"alt\":%u,\"speed\":%u,\"accuracy\":%s,"
3125 "\"lon\":%d,\"lat\":%d,\"course\":%u,"
3126 "\"second\":%u,\"regional\":%u,\"dte\":%u,"
3127 "\"raim\":%s,\"radio\":%u}\r\n",
3128 ais->type9.alt,
3129 ais->type9.speed,
3130 JSON_BOOL(ais->type9.accuracy),
3131 ais->type9.lon,
3132 ais->type9.lat,
3133 ais->type9.course,
3134 ais->type9.second,
3135 ais->type9.regional,
3136 ais->type9.dte,
3137 JSON_BOOL(ais->type9.raim), ais->type9.radio);
3138 }
3139 break;
3140 case 10: /* UTC/Date Inquiry */
3141 str_appendf(buf, buflen,
3142 "\"dest_mmsi\":%u}\r\n", ais->type10.dest_mmsi);
3143 break;
3144 case 12: /* Safety Related Message */
3145 str_appendf(buf, buflen,
3146 "\"seqno\":%u,\"dest_mmsi\":%u,\"retransmit\":%s,"
3147 "\"text\":\"%s\"}\r\n",
3148 ais->type12.seqno,
3149 ais->type12.dest_mmsi,
3150 JSON_BOOL(ais->type12.retransmit),
3151 json_stringify(buf1, sizeof(buf1), ais->type12.text));
3152 break;
3153 case 14: /* Safety Related Broadcast Message */
3154 str_appendf(buf, buflen,
3155 "\"text\":\"%s\"}\r\n",
3156 json_stringify(buf1, sizeof(buf1), ais->type14.text));
3157 break;
3158 case 15: /* Interrogation */
3159 str_appendf(buf, buflen,
3160 "\"mmsi1\":%u,\"type1_1\":%u,\"offset1_1\":%u,"
3161 "\"type1_2\":%u,\"offset1_2\":%u,\"mmsi2\":%u,"
3162 "\"type2_1\":%u,\"offset2_1\":%u}\r\n",
3163 ais->type15.mmsi1,
3164 ais->type15.type1_1,
3165 ais->type15.offset1_1,
3166 ais->type15.type1_2,
3167 ais->type15.offset1_2,
3168 ais->type15.mmsi2,
3169 ais->type15.type2_1, ais->type15.offset2_1);
3170 break;
3171 case 16:
3172 str_appendf(buf, buflen,
3173 "\"mmsi1\":%u,\"offset1\":%u,\"increment1\":%u,"
3174 "\"mmsi2\":%u,\"offset2\":%u,\"increment2\":%u}\r\n",
3175 ais->type16.mmsi1,
3176 ais->type16.offset1,
3177 ais->type16.increment1,
3178 ais->type16.mmsi2,
3179 ais->type16.offset2, ais->type16.increment2);
3180 break;
3181 case 17:
3182 if (scaled) {
3183 str_appendf(buf, buflen,
3184 "\"lon\":%.1f,\"lat\":%.1f,\"data\":\"%zd:%s\"}\r\n",
3185 ais->type17.lon / AIS_GNSS_LATLON_DIV,
3186 ais->type17.lat / AIS_GNSS_LATLON_DIV,
3187 ais->type17.bitcount,
3188 gpsd_hexdump(scratchbuf, sizeof(scratchbuf),
3189 (char *)ais->type17.bitdata,
3190 BITS_TO_BYTES(ais->type17.bitcount)));
3191 } else {
3192 str_appendf(buf, buflen,
3193 "\"lon\":%d,\"lat\":%d,\"data\":\"%zd:%s\"}\r\n",
3194 ais->type17.lon,
3195 ais->type17.lat,
3196 ais->type17.bitcount,
3197 gpsd_hexdump(scratchbuf, sizeof(scratchbuf),
3198 (char *)ais->type17.bitdata,
3199 BITS_TO_BYTES(ais->type17.bitcount)));
3200 }
3201 break;
3202 case 18:
3203 if (scaled) {
3204 str_appendf(buf, buflen,
3205 "\"reserved\":%u,\"speed\":%.1f,\"accuracy\":%s,"
3206 "\"lon\":%.6f,\"lat\":%.6f,\"course\":%.1f,"
3207 "\"heading\":%u,\"second\":%u,\"regional\":%u,"
3208 "\"cs\":%s,\"display\":%s,\"dsc\":%s,\"band\":%s,"
3209 "\"msg22\":%s,\"raim\":%s,\"radio\":%u}\r\n",
3210 ais->type18.reserved,
3211 ais->type18.speed / 10.0,
3212 JSON_BOOL(ais->type18.accuracy),
3213 ais->type18.lon / AIS_LATLON_DIV,
3214 ais->type18.lat / AIS_LATLON_DIV,
3215 ais->type18.course / 10.0,
3216 ais->type18.heading,
3217 ais->type18.second,
3218 ais->type18.regional,
3219 JSON_BOOL(ais->type18.cs),
3220 JSON_BOOL(ais->type18.display),
3221 JSON_BOOL(ais->type18.dsc),
3222 JSON_BOOL(ais->type18.band),
3223 JSON_BOOL(ais->type18.msg22),
3224 JSON_BOOL(ais->type18.raim), ais->type18.radio);
3225 } else {
3226 str_appendf(buf, buflen,
3227 "\"reserved\":%u,\"speed\":%u,\"accuracy\":%s,"
3228 "\"lon\":%d,\"lat\":%d,\"course\":%u,"
3229 "\"heading\":%u,\"second\":%u,\"regional\":%u,"
3230 "\"cs\":%s,\"display\":%s,\"dsc\":%s,\"band\":%s,"
3231 "\"msg22\":%s,\"raim\":%s,\"radio\":%u}\r\n",
3232 ais->type18.reserved,
3233 ais->type18.speed,
3234 JSON_BOOL(ais->type18.accuracy),
3235 ais->type18.lon,
3236 ais->type18.lat,
3237 ais->type18.course,
3238 ais->type18.heading,
3239 ais->type18.second,
3240 ais->type18.regional,
3241 JSON_BOOL(ais->type18.cs),
3242 JSON_BOOL(ais->type18.display),
3243 JSON_BOOL(ais->type18.dsc),
3244 JSON_BOOL(ais->type18.band),
3245 JSON_BOOL(ais->type18.msg22),
3246 JSON_BOOL(ais->type18.raim), ais->type18.radio);
3247 }
3248 break;
3249 case 19:
3250 if (scaled) {
3251 str_appendf(buf, buflen,
3252 "\"reserved\":%u,\"speed\":%.1f,\"accuracy\":%s,"
3253 "\"lon\":%.6f,\"lat\":%.6f,\"course\":%.1f,"
3254 "\"heading\":%u,\"second\":%u,\"regional\":%u,"
3255 "\"shipname\":\"%s\","
3256 "\"shiptype\":%u,\"shiptype_text\":\"%s\","
3257 "\"to_bow\":%u,\"to_stern\":%u,\"to_port\":%u,"
3258 "\"to_starboard\":%u,"
3259 "\"epfd\":%u,\"epfd_text\":\"%s\","
3260 "\"raim\":%s,\"dte\":%u,\"assigned\":%s}\r\n",
3261 ais->type19.reserved,
3262 ais->type19.speed / 10.0,
3263 JSON_BOOL(ais->type19.accuracy),
3264 ais->type19.lon / AIS_LATLON_DIV,
3265 ais->type19.lat / AIS_LATLON_DIV,
3266 ais->type19.course / 10.0,
3267 ais->type19.heading,
3268 ais->type19.second,
3269 ais->type19.regional,
3270 json_stringify(buf1, sizeof(buf1),
3271 ais->type19.shipname),
3272 ais->type19.shiptype,
3273 SHIPTYPE_DISPLAY(ais->type19.shiptype),
3274 ais->type19.to_bow,
3275 ais->type19.to_stern,
3276 ais->type19.to_port,
3277 ais->type19.to_starboard,
3278 ais->type19.epfd,
3279 EPFD_DISPLAY(ais->type19.epfd),
3280 JSON_BOOL(ais->type19.raim),
3281 ais->type19.dte,
3282 JSON_BOOL(ais->type19.assigned));
3283 } else {
3284 str_appendf(buf, buflen,
3285 "\"reserved\":%u,\"speed\":%u,\"accuracy\":%s,"
3286 "\"lon\":%d,\"lat\":%d,\"course\":%u,"
3287 "\"heading\":%u,\"second\":%u,\"regional\":%u,"
3288 "\"shipname\":\"%s\","
3289 "\"shiptype\":%u,\"shiptype_text\":\"%s\","
3290 "\"to_bow\":%u,\"to_stern\":%u,\"to_port\":%u,"
3291 "\"to_starboard\":%u,"
3292 "\"epfd\":%u,\"epfd_text\":\"%s\","
3293 "\"raim\":%s,\"dte\":%u,\"assigned\":%s}\r\n",
3294 ais->type19.reserved,
3295 ais->type19.speed,
3296 JSON_BOOL(ais->type19.accuracy),
3297 ais->type19.lon,
3298 ais->type19.lat,
3299 ais->type19.course,
3300 ais->type19.heading,
3301 ais->type19.second,
3302 ais->type19.regional,
3303 json_stringify(buf1, sizeof(buf1),
3304 ais->type19.shipname),
3305 ais->type19.shiptype,
3306 SHIPTYPE_DISPLAY(ais->type19.shiptype),
3307 ais->type19.to_bow,
3308 ais->type19.to_stern,
3309 ais->type19.to_port,
3310 ais->type19.to_starboard,
3311 ais->type19.epfd,
3312 EPFD_DISPLAY(ais->type19.epfd),
3313 JSON_BOOL(ais->type19.raim),
3314 ais->type19.dte,
3315 JSON_BOOL(ais->type19.assigned));
3316 }
3317 break;
3318 case 20: /* Data Link Management Message */
3319 str_appendf(buf, buflen,
3320 "\"offset1\":%u,\"number1\":%u,"
3321 "\"timeout1\":%u,\"increment1\":%u,"
3322 "\"offset2\":%u,\"number2\":%u,"
3323 "\"timeout2\":%u,\"increment2\":%u,"
3324 "\"offset3\":%u,\"number3\":%u,"
3325 "\"timeout3\":%u,\"increment3\":%u,"
3326 "\"offset4\":%u,\"number4\":%u,"
3327 "\"timeout4\":%u,\"increment4\":%u}\r\n",
3328 ais->type20.offset1,
3329 ais->type20.number1,
3330 ais->type20.timeout1,
3331 ais->type20.increment1,
3332 ais->type20.offset2,
3333 ais->type20.number2,
3334 ais->type20.timeout2,
3335 ais->type20.increment2,
3336 ais->type20.offset3,
3337 ais->type20.number3,
3338 ais->type20.timeout3,
3339 ais->type20.increment3,
3340 ais->type20.offset4,
3341 ais->type20.number4,
3342 ais->type20.timeout4, ais->type20.increment4);
3343 break;
3344 case 21: /* Aid to Navigation */
3345 if (scaled) {
3346 str_appendf(buf, buflen,
3347 "\"aid_type\":%u,\"aid_type_text\":\"%s\","
3348 "\"name\":\"%s\",\"lon\":%.6f,"
3349 "\"lat\":%.6f,\"accuracy\":%s,\"to_bow\":%u,"
3350 "\"to_stern\":%u,\"to_port\":%u,\"to_starboard\":%u,"
3351 "\"epfd\":%u,\"epfd_text\":\"%s\","
3352 "\"second\":%u,\"regional\":%u,"
3353 "\"off_position\":%s,\"raim\":%s,"
3354 "\"virtual_aid\":%s}\r\n",
3355 ais->type21.aid_type,
3356 NAVAIDTYPE_DISPLAY(ais->type21.aid_type),
3357 json_stringify(buf1, sizeof(buf1),
3358 ais->type21.name),
3359 ais->type21.lon / AIS_LATLON_DIV,
3360 ais->type21.lat / AIS_LATLON_DIV,
3361 JSON_BOOL(ais->type21.accuracy),
3362 ais->type21.to_bow, ais->type21.to_stern,
3363 ais->type21.to_port, ais->type21.to_starboard,
3364 ais->type21.epfd,
3365 EPFD_DISPLAY(ais->type21.epfd),
3366 ais->type21.second,
3367 ais->type21.regional,
3368 JSON_BOOL(ais->type21.off_position),
3369 JSON_BOOL(ais->type21.raim),
3370 JSON_BOOL(ais->type21.virtual_aid));
3371 } else {
3372 str_appendf(buf, buflen,
3373 "\"aid_type\":%u,\"aid_type_text\":\"%s\","
3374 "\"name\":\"%s\",\"accuracy\":%s,"
3375 "\"lon\":%d,\"lat\":%d,\"to_bow\":%u,"
3376 "\"to_stern\":%u,\"to_port\":%u,\"to_starboard\":%u,"
3377 "\"epfd\":%u,\"epfd_text\":\"%s\","
3378 "\"second\":%u,\"regional\":%u,"
3379 "\"off_position\":%s,\"raim\":%s,"
3380 "\"virtual_aid\":%s}\r\n",
3381 ais->type21.aid_type,
3382 NAVAIDTYPE_DISPLAY(ais->type21.aid_type),
3383 json_stringify(buf1, sizeof(buf1),
3384 ais->type21.name),
3385 JSON_BOOL(ais->type21.accuracy),
3386 ais->type21.lon,
3387 ais->type21.lat,
3388 ais->type21.to_bow,
3389 ais->type21.to_stern,
3390 ais->type21.to_port,
3391 ais->type21.to_starboard,
3392 ais->type21.epfd,
3393 EPFD_DISPLAY(ais->type21.epfd),
3394 ais->type21.second,
3395 ais->type21.regional,
3396 JSON_BOOL(ais->type21.off_position),
3397 JSON_BOOL(ais->type21.raim),
3398 JSON_BOOL(ais->type21.virtual_aid));
3399 }
3400 break;
3401 case 22: /* Channel Management */
3402 str_appendf(buf, buflen,
3403 "\"channel_a\":%u,\"channel_b\":%u,"
3404 "\"txrx\":%u,\"power\":%s,",
3405 ais->type22.channel_a,
3406 ais->type22.channel_b,
3407 ais->type22.txrx, JSON_BOOL(ais->type22.power));
3408 if (ais->type22.addressed) {
3409 str_appendf(buf, buflen,
3410 "\"dest1\":%u,\"dest2\":%u,",
3411 ais->type22.mmsi.dest1, ais->type22.mmsi.dest2);
3412 } else if (scaled) {
3413 str_appendf(buf, buflen,
3414 "\"ne_lon\":\"%f\",\"ne_lat\":\"%f\","
3415 "\"sw_lon\":\"%f\",\"sw_lat\":\"%f\",",
3416 ais->type22.area.ne_lon / AIS_CHANNEL_LATLON_DIV,
3417 ais->type22.area.ne_lat / AIS_CHANNEL_LATLON_DIV,
3418 ais->type22.area.sw_lon / AIS_CHANNEL_LATLON_DIV,
3419 ais->type22.area.sw_lat /
3420 AIS_CHANNEL_LATLON_DIV);
3421 } else {
3422 str_appendf(buf, buflen,
3423 "\"ne_lon\":%d,\"ne_lat\":%d,"
3424 "\"sw_lon\":%d,\"sw_lat\":%d,",
3425 ais->type22.area.ne_lon,
3426 ais->type22.area.ne_lat,
3427 ais->type22.area.sw_lon, ais->type22.area.sw_lat);
3428 }
3429 str_appendf(buf, buflen,
3430 "\"addressed\":%s,\"band_a\":%s,"
3431 "\"band_b\":%s,\"zonesize\":%u}\r\n",
3432 JSON_BOOL(ais->type22.addressed),
3433 JSON_BOOL(ais->type22.band_a),
3434 JSON_BOOL(ais->type22.band_b), ais->type22.zonesize);
3435 break;
3436 case 23: /* Group Assignment Command */
3437 if (scaled) {
3438 str_appendf(buf, buflen,
3439 "\"ne_lon\":\"%f\",\"ne_lat\":\"%f\","
3440 "\"sw_lon\":\"%f\",\"sw_lat\":\"%f\","
3441 "\"stationtype\":%u,\"stationtype_text\":\"%s\","
3442 "\"shiptype\":%u,\"shiptype_text\":\"%s\","
3443 "\"interval\":%u,\"quiet\":%u}\r\n",
3444 ais->type23.ne_lon / AIS_CHANNEL_LATLON_DIV,
3445 ais->type23.ne_lat / AIS_CHANNEL_LATLON_DIV,
3446 ais->type23.sw_lon / AIS_CHANNEL_LATLON_DIV,
3447 ais->type23.sw_lat / AIS_CHANNEL_LATLON_DIV,
3448 ais->type23.stationtype,
3449 STATIONTYPE_DISPLAY(ais->type23.stationtype),
3450 ais->type23.shiptype,
3451 SHIPTYPE_DISPLAY(ais->type23.shiptype),
3452 ais->type23.interval, ais->type23.quiet);
3453 } else {
3454 str_appendf(buf, buflen,
3455 "\"ne_lon\":%d,\"ne_lat\":%d,"
3456 "\"sw_lon\":%d,\"sw_lat\":%d,"
3457 "\"stationtype\":%u,\"stationtype_text\":\"%s\","
3458 "\"shiptype\":%u,\"shiptype_text\":\"%s\","
3459 "\"interval\":%u,\"quiet\":%u}\r\n",
3460 ais->type23.ne_lon,
3461 ais->type23.ne_lat,
3462 ais->type23.sw_lon,
3463 ais->type23.sw_lat,
3464 ais->type23.stationtype,
3465 STATIONTYPE_DISPLAY(ais->type23.stationtype),
3466 ais->type23.shiptype,
3467 SHIPTYPE_DISPLAY(ais->type23.shiptype),
3468 ais->type23.interval, ais->type23.quiet);
3469 }
3470 break;
3471 case 24: /* Class B CS Static Data Report */
3472 if (ais->type24.part != both) {
3473 static char *partnames[] = {"AB", "A", "B"};
3474 str_appendf(buf, buflen,
3475 "\"part\":\"%s\",",
3476 json_stringify(buf1, sizeof(buf1),
3477 partnames[ais->type24.part]));
3478 }
3479 if (ais->type24.part != part_b)
3480 str_appendf(buf, buflen,
3481 "\"shipname\":\"%s\",",
3482 json_stringify(buf1, sizeof(buf1),
3483 ais->type24.shipname));
3484 if (ais->type24.part != part_a) {
3485 str_appendf(buf, buflen,
3486 "\"shiptype\":%u,\"shiptype_text\":\"%s\","
3487 "\"vendorid\":\"%s\",\"model\":%u,\"serial\":%u,"
3488 "\"callsign\":\"%s\",",
3489 ais->type24.shiptype,
3490 SHIPTYPE_DISPLAY(ais->type24.shiptype),
3491 json_stringify(buf1, sizeof(buf1),
3492 ais->type24.vendorid),
3493 ais->type24.model,
3494 ais->type24.serial,
3495 json_stringify(buf2, sizeof(buf2),
3496 ais->type24.callsign));
3497 if (AIS_AUXILIARY_MMSI(ais->mmsi)) {
3498 str_appendf(buf, buflen,
3499 "\"mothership_mmsi\":%u",
3500 ais->type24.mothership_mmsi);
3501 } else {
3502 str_appendf(buf, buflen,
3503 "\"to_bow\":%u,\"to_stern\":%u,"
3504 "\"to_port\":%u,\"to_starboard\":%u",
3505 ais->type24.dim.to_bow,
3506 ais->type24.dim.to_stern,
3507 ais->type24.dim.to_port,
3508 ais->type24.dim.to_starboard);
3509 }
3510 }
3511 str_rstrip_char(buf, ',');
3512 (void)strlcat(buf, "}\r\n", buflen);
3513 break;
3514 case 25: /* Binary Message, Single Slot */
3515 str_appendf(buf, buflen,
3516 "\"addressed\":%s,\"structured\":%s,\"dest_mmsi\":%u,"
3517 "\"app_id\":%u,\"data\":\"%zd:%s\"}\r\n",
3518 JSON_BOOL(ais->type25.addressed),
3519 JSON_BOOL(ais->type25.structured),
3520 ais->type25.dest_mmsi,
3521 ais->type25.app_id,
3522 ais->type25.bitcount,
3523 gpsd_hexdump(scratchbuf, sizeof(scratchbuf),
3524 (char *)ais->type25.bitdata,
3525 BITS_TO_BYTES(ais->type25.bitcount)));
3526 break;
3527 case 26: /* Binary Message, Multiple Slot */
3528 str_appendf(buf, buflen,
3529 "\"addressed\":%s,\"structured\":%s,\"dest_mmsi\":%u,"
3530 "\"app_id\":%u,\"data\":\"%zd:%s\",\"radio\":%u}\r\n",
3531 JSON_BOOL(ais->type26.addressed),
3532 JSON_BOOL(ais->type26.structured),
3533 ais->type26.dest_mmsi,
3534 ais->type26.app_id,
3535 ais->type26.bitcount,
3536 gpsd_hexdump(scratchbuf, sizeof(scratchbuf),
3537 (char *)ais->type26.bitdata,
3538 BITS_TO_BYTES(ais->type26.bitcount)),
3539 ais->type26.radio);
3540 break;
3541 case 27: /* Long Range AIS Broadcast message */
3542 if (scaled)
3543 str_appendf(buf, buflen,
3544 "\"status\":\"%s\","
3545 "\"accuracy\":%s,\"lon\":%.1f,\"lat\":%.1f,"
3546 "\"speed\":%u,\"course\":%u,\"raim\":%s,"
3547 "\"gnss\":%s}\r\n",
3548 nav_legends[ais->type27.status],
3549 JSON_BOOL(ais->type27.accuracy),
3550 ais->type27.lon / AIS_LONGRANGE_LATLON_DIV,
3551 ais->type27.lat / AIS_LONGRANGE_LATLON_DIV,
3552 ais->type27.speed,
3553 ais->type27.course,
3554 JSON_BOOL(ais->type27.raim),
3555 JSON_BOOL(ais->type27.gnss));
3556 else
3557 str_appendf(buf, buflen,
3558 "\"status\":%u,"
3559 "\"accuracy\":%s,\"lon\":%d,\"lat\":%d,"
3560 "\"speed\":%u,\"course\":%u,\"raim\":%s,"
3561 "\"gnss\":%s}\r\n",
3562 ais->type27.status,
3563 JSON_BOOL(ais->type27.accuracy),
3564 ais->type27.lon,
3565 ais->type27.lat,
3566 ais->type27.speed,
3567 ais->type27.course,
3568 JSON_BOOL(ais->type27.raim),
3569 JSON_BOOL(ais->type27.gnss));
3570 break;
3571 default:
3572 str_rstrip_char(buf, ',');
3573 (void)strlcat(buf, "}\r\n", buflen);
3574 break;
3575 }
3576 }
3577 #endif /* defined(AIVDM_ENABLE) */
3578
3579 #ifdef COMPASS_ENABLE
json_att_dump(const struct gps_data_t * gpsdata,char * reply,size_t replylen)3580 void json_att_dump(const struct gps_data_t *gpsdata,
3581 char *reply, size_t replylen)
3582 /* dump the contents of an attitude_t structure as JSON */
3583 {
3584 assert(replylen > sizeof(char *));
3585 (void)strlcpy(reply, "{\"class\":\"ATT\",", replylen);
3586 str_appendf(reply, replylen, "\"device\":\"%s\",", gpsdata->dev.path);
3587 if (isfinite(gpsdata->attitude.heading) != 0) {
3588 /* Trimble outputs %.3f, so we do too. */
3589 str_appendf(reply, replylen,
3590 "\"heading\":%.3f,", gpsdata->attitude.heading);
3591 if (gpsdata->attitude.mag_st != '\0')
3592 str_appendf(reply, replylen,
3593 "\"mag_st\":\"%c\",", gpsdata->attitude.mag_st);
3594
3595 }
3596 if (isfinite(gpsdata->attitude.pitch) != 0) {
3597 str_appendf(reply, replylen,
3598 "\"pitch\":%.2f,", gpsdata->attitude.pitch);
3599 if (gpsdata->attitude.pitch_st != '\0')
3600 str_appendf(reply, replylen,
3601 "\"pitch_st\":\"%c\",",
3602 gpsdata->attitude.pitch_st);
3603
3604 }
3605 if (isfinite(gpsdata->attitude.yaw) != 0) {
3606 str_appendf(reply, replylen,
3607 "\"yaw\":%.2f,", gpsdata->attitude.yaw);
3608 if (gpsdata->attitude.yaw_st != '\0')
3609 str_appendf(reply, replylen,
3610 "\"yaw_st\":\"%c\",", gpsdata->attitude.yaw_st);
3611
3612 }
3613 if (isfinite(gpsdata->attitude.roll) != 0) {
3614 str_appendf(reply, replylen,
3615 "\"roll\":%.2f,", gpsdata->attitude.roll);
3616 if (gpsdata->attitude.roll_st != '\0')
3617 str_appendf(reply, replylen,
3618 "\"roll_st\":\"%c\",", gpsdata->attitude.roll_st);
3619
3620 }
3621
3622 if (isfinite(gpsdata->attitude.dip) != 0)
3623 str_appendf(reply, replylen,
3624 "\"dip\":%.3f,", gpsdata->attitude.dip);
3625
3626 if (isfinite(gpsdata->attitude.mag_len) != 0)
3627 str_appendf(reply, replylen,
3628 "\"mag_len\":%.3f,", gpsdata->attitude.mag_len);
3629 if (isfinite(gpsdata->attitude.mag_x) != 0)
3630 str_appendf(reply, replylen,
3631 "\"mag_x\":%.3f,", gpsdata->attitude.mag_x);
3632 if (isfinite(gpsdata->attitude.mag_y) != 0)
3633 str_appendf(reply, replylen,
3634 "\"mag_y\":%.3f,", gpsdata->attitude.mag_y);
3635 if (isfinite(gpsdata->attitude.mag_z) != 0)
3636 str_appendf(reply, replylen,
3637 "\"mag_z\":%.3f,", gpsdata->attitude.mag_z);
3638
3639 if (isfinite(gpsdata->attitude.acc_len) != 0)
3640 str_appendf(reply, replylen,
3641 "\"acc_len\":%.3f,", gpsdata->attitude.acc_len);
3642 if (isfinite(gpsdata->attitude.acc_x) != 0)
3643 str_appendf(reply, replylen,
3644 "\"acc_x\":%.3f,", gpsdata->attitude.acc_x);
3645 if (isfinite(gpsdata->attitude.acc_y) != 0)
3646 str_appendf(reply, replylen,
3647 "\"acc_y\":%.3f,", gpsdata->attitude.acc_y);
3648 if (isfinite(gpsdata->attitude.acc_z) != 0)
3649 str_appendf(reply, replylen,
3650 "\"acc_z\":%.3f,", gpsdata->attitude.acc_z);
3651
3652 if (isfinite(gpsdata->attitude.gyro_x) != 0)
3653 str_appendf(reply, replylen,
3654 "\"gyro_x\":%.3f,", gpsdata->attitude.gyro_x);
3655 if (isfinite(gpsdata->attitude.gyro_y) != 0)
3656 str_appendf(reply, replylen,
3657 "\"gyro_y\":%.3f,", gpsdata->attitude.gyro_y);
3658
3659 if (isfinite(gpsdata->attitude.temp) != 0)
3660 str_appendf(reply, replylen,
3661 "\"temp\":%.3f,", gpsdata->attitude.temp);
3662 if (isfinite(gpsdata->attitude.depth) != 0)
3663 str_appendf(reply, replylen,
3664 "\"depth\":%.3f,", gpsdata->attitude.depth);
3665
3666 str_rstrip_char(reply, ',');
3667 (void)strlcat(reply, "}\r\n", replylen);
3668 }
3669 #endif /* COMPASS_ENABLE */
3670
3671 #ifdef OSCILLATOR_ENABLE
json_oscillator_dump(const struct gps_data_t * datap,char * reply,size_t replylen)3672 void json_oscillator_dump(const struct gps_data_t *datap,
3673 char *reply, size_t replylen)
3674 /* dump the contents of an oscillator_t structure as JSON */
3675 {
3676 (void)snprintf(reply, replylen,
3677 "{\"class\":\"OSC\",\"device\":\"%s\",\"running\":%s,"
3678 "\"reference\":%s,\"disciplined\":%s,\"delta\":%d}\r\n",
3679 datap->dev.path,
3680 JSON_BOOL(datap->osc.running),
3681 JSON_BOOL(datap->osc.reference),
3682 JSON_BOOL(datap->osc.disciplined),
3683 datap->osc.delta);
3684 }
3685 #endif /* OSCILLATOR_ENABLE */
3686
json_data_report(const gps_mask_t changed,const struct gps_device_t * session,const struct gps_policy_t * policy,char * buf,size_t buflen)3687 void json_data_report(const gps_mask_t changed,
3688 const struct gps_device_t *session,
3689 const struct gps_policy_t *policy,
3690 char *buf, size_t buflen)
3691 /* report a session state in JSON */
3692 {
3693 const struct gps_data_t *datap = &session->gpsdata;
3694 buf[0] = '\0';
3695
3696 if ((changed & REPORT_IS) != 0) {
3697 json_tpv_dump(session, policy, buf+strlen(buf), buflen-strlen(buf));
3698 }
3699
3700 if ((changed & GST_SET) != 0) {
3701 json_noise_dump(datap, buf+strlen(buf), buflen-strlen(buf));
3702 }
3703
3704 if ((changed & SATELLITE_SET) != 0) {
3705 json_sky_dump(datap, buf+strlen(buf), buflen-strlen(buf));
3706 }
3707
3708 if ((changed & SUBFRAME_SET) != 0) {
3709 json_subframe_dump(datap, buf+strlen(buf), buflen-strlen(buf));
3710 }
3711
3712 if ((changed & RAW_IS) != 0) {
3713 json_raw_dump(datap, buf+strlen(buf), buflen-strlen(buf));
3714 }
3715
3716 #ifdef COMPASS_ENABLE
3717 if ((changed & ATTITUDE_SET) != 0) {
3718 json_att_dump(datap, buf+strlen(buf), buflen-strlen(buf));
3719 }
3720 #endif /* COMPASS_ENABLE */
3721
3722 #ifdef RTCM104V2_ENABLE
3723 if ((changed & RTCM2_SET) != 0) {
3724 json_rtcm2_dump(&datap->rtcm2, datap->dev.path,
3725 buf+strlen(buf), buflen-strlen(buf));
3726 }
3727 #endif /* RTCM104V2_ENABLE */
3728
3729 #ifdef RTCM104V3_ENABLE
3730 if ((changed & RTCM3_SET) != 0) {
3731 json_rtcm3_dump(&datap->rtcm3, datap->dev.path,
3732 buf+strlen(buf), buflen-strlen(buf));
3733 }
3734 #endif /* RTCM104V3_ENABLE */
3735
3736 #ifdef AIVDM_ENABLE
3737 if ((changed & AIS_SET) != 0) {
3738 json_aivdm_dump(&datap->ais, datap->dev.path,
3739 policy->scaled,
3740 buf+strlen(buf), buflen-strlen(buf));
3741 }
3742 #endif /* AIVDM_ENABLE */
3743
3744 #ifdef OSCILLATOR_ENABLE
3745 if ((changed & OSCILLATOR_SET) != 0) {
3746 json_oscillator_dump(datap, buf+strlen(buf), buflen-strlen(buf));
3747 }
3748 #endif /* OSCILLATOR_ENABLE */
3749 }
3750
3751 #undef JSON_BOOL
3752 #endif /* SOCKET_EXPORT_ENABLE */
3753
3754 /* gpsd_json.c ends here */
3755