"Initial commit to Gerrit"
[profile/ivi/gpsd.git] / gpsd_json.c
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 by the GPSD project
13   BSD terms apply: see the file COPYING in the distribution root for details.
14
15 ***************************************************************************/
16
17 #include <math.h>
18 #include <assert.h>
19 #include <string.h>
20 #include <stdio.h>
21 #include <ctype.h>
22
23 #include "gpsd.h"
24 #include "gps_json.h"
25 #include "revision.h"
26
27 /* *INDENT-OFF* */
28 /*
29  * Manifest names for the gnss_type enum - must be kept synced with it.
30  * Also, masks so we can tell what packet types correspond to each class.
31  */
32 /* the map of device class names */
33 struct classmap_t {
34     char        *name;
35     int         typemask;
36     int         packetmask;
37 };
38 #define CLASSMAP_NITEMS 5
39
40 struct classmap_t classmap[CLASSMAP_NITEMS] = {
41     /* name     typemask        packetmask */
42     {"ANY",     0,              0},
43     {"GPS",     SEEN_GPS,       GPS_TYPEMASK},
44     {"RTCM2",   SEEN_RTCM2,     PACKET_TYPEMASK(RTCM2_PACKET)},
45     {"RTCM3",   SEEN_RTCM3,     PACKET_TYPEMASK(RTCM3_PACKET)},
46     {"AIS",     SEEN_AIS,       PACKET_TYPEMASK(AIVDM_PACKET)},
47 };
48 /* *INDENT-ON* */
49
50 char *json_stringify( /*@out@*/ char *to,
51                      size_t len,
52                      /*@in@*/ const char *from)
53 /* escape double quotes and control characters inside a JSON string */
54 {
55     /*@-temptrans@*/
56     const char *sp;
57     char *tp;
58
59     tp = to;
60     /*
61      * The limit is len-6 here because we need to be leave room for
62      * each character to generate an up to 6-character Java-style
63      * escape
64      */
65     for (sp = from; *sp != '\0' && ((tp - to) < ((int)len - 5)); sp++) {
66         if (iscntrl(*sp)) {
67             *tp++ = '\\';
68             switch (*sp) {
69             case '\b':
70                 *tp++ = 'b';
71                 break;
72             case '\f':
73                 *tp++ = 'f';
74                 break;
75             case '\n':
76                 *tp++ = 'n';
77                 break;
78             case '\r':
79                 *tp++ = 'r';
80                 break;
81             case '\t':
82                 *tp++ = 't';
83                 break;
84             default:
85                 /* ugh, we'd prefer a C-style escape here, but this is JSON */
86                 (void)snprintf(tp, 5, "%u04x", (unsigned int)*sp);
87                 tp += strlen(tp);
88             }
89         } else {
90             if (*sp == '"' || *sp == '\\')
91                 *tp++ = '\\';
92             *tp++ = *sp;
93         }
94     }
95     *tp = '\0';
96
97     return to;
98     /*@+temptrans@*/
99 }
100
101 void json_version_dump( /*@out@*/ char *reply, size_t replylen)
102 {
103     (void)snprintf(reply, replylen,
104                    "{\"class\":\"VERSION\",\"release\":\"%s\",\"rev\":\"%s\",\"proto_major\":%d,\"proto_minor\":%d}\r\n",
105                    VERSION, REVISION,
106                    GPSD_PROTO_MAJOR_VERSION, GPSD_PROTO_MINOR_VERSION);
107 }
108
109 void json_tpv_dump(const struct gps_data_t *gpsdata,
110                    /*@out@*/ char *reply, size_t replylen)
111 {
112     assert(replylen > 2);
113     (void)strlcpy(reply, "{\"class\":\"TPV\",", replylen);
114     (void)snprintf(reply + strlen(reply),
115                    replylen - strlen(reply),
116                    "\"tag\":\"%s\",",
117                    gpsdata->tag[0] != '\0' ? gpsdata->tag : "-");
118     (void)snprintf(reply + strlen(reply),
119                    replylen - strlen(reply),
120                    "\"device\":\"%s\",", gpsdata->dev.path);
121     if (isnan(gpsdata->fix.time) == 0)
122         (void)snprintf(reply + strlen(reply),
123                        replylen - strlen(reply),
124                        "\"time\":%.3f,", gpsdata->fix.time);
125     if (isnan(gpsdata->fix.ept) == 0)
126         (void)snprintf(reply + strlen(reply),
127                        replylen - strlen(reply),
128                        "\"ept\":%.3f,", gpsdata->fix.ept);
129     /*
130      * Suppressing TPV fields that would be invalid because the fix
131      * quality doesn't support them is nice for cutting down on the
132      * volume of meaningless output, but the real reason to do it is
133      * that we've observed that geodetic fix computation is unstable
134      * in a way that tends to change low-order digits in invalid
135      * fixes. Dumping these tends to cause cross-architecture failures
136      * in the regression tests.  Rgus effect has been seen on SiRF-II
137      * chips, which are quite common.
138      */
139     if (gpsdata->fix.mode >= MODE_2D) {
140         if (isnan(gpsdata->fix.latitude) == 0)
141             (void)snprintf(reply + strlen(reply),
142                            replylen - strlen(reply),
143                            "\"lat\":%.9f,", gpsdata->fix.latitude);
144         if (isnan(gpsdata->fix.longitude) == 0)
145             (void)snprintf(reply + strlen(reply),
146                            replylen - strlen(reply),
147                            "\"lon\":%.9f,", gpsdata->fix.longitude);
148         if (gpsdata->fix.mode >= MODE_3D && isnan(gpsdata->fix.altitude) == 0)
149             (void)snprintf(reply + strlen(reply),
150                            replylen - strlen(reply),
151                            "\"alt\":%.3f,", gpsdata->fix.altitude);
152         if (isnan(gpsdata->fix.epx) == 0)
153             (void)snprintf(reply + strlen(reply),
154                            replylen - strlen(reply),
155                            "\"epx\":%.3f,", gpsdata->fix.epx);
156         if (isnan(gpsdata->fix.epy) == 0)
157             (void)snprintf(reply + strlen(reply),
158                            replylen - strlen(reply),
159                            "\"epy\":%.3f,", gpsdata->fix.epy);
160         if ((gpsdata->fix.mode >= MODE_3D) && isnan(gpsdata->fix.epv) == 0)
161             (void)snprintf(reply + strlen(reply),
162                            replylen - strlen(reply),
163                            "\"epv\":%.3f,", gpsdata->fix.epv);
164         if (isnan(gpsdata->fix.track) == 0)
165             (void)snprintf(reply + strlen(reply),
166                            replylen - strlen(reply),
167                            "\"track\":%.4f,", gpsdata->fix.track);
168         if (isnan(gpsdata->fix.speed) == 0)
169             (void)snprintf(reply + strlen(reply),
170                            replylen - strlen(reply),
171                            "\"speed\":%.3f,", gpsdata->fix.speed);
172         if ((gpsdata->fix.mode >= MODE_3D) && isnan(gpsdata->fix.climb) == 0)
173             (void)snprintf(reply + strlen(reply),
174                            replylen - strlen(reply),
175                            "\"climb\":%.3f,", gpsdata->fix.climb);
176         if (isnan(gpsdata->fix.epd) == 0)
177             (void)snprintf(reply + strlen(reply),
178                            replylen - strlen(reply),
179                            "\"epd\":%.4f,", gpsdata->fix.epd);
180         if (isnan(gpsdata->fix.eps) == 0)
181             (void)snprintf(reply + strlen(reply),
182                            replylen - strlen(reply),
183                            "\"eps\":%.2f,", gpsdata->fix.eps);
184         if ((gpsdata->fix.mode >= MODE_3D) && isnan(gpsdata->fix.epc) == 0)
185             (void)snprintf(reply + strlen(reply),
186                            replylen - strlen(reply),
187                            "\"epc\":%.2f,", gpsdata->fix.epc);
188     }
189     (void)snprintf(reply + strlen(reply),
190                    replylen - strlen(reply),
191                    "\"mode\":%d,", gpsdata->fix.mode);
192     if (reply[strlen(reply) - 1] == ',')
193         reply[strlen(reply) - 1] = '\0';        /* trim trailing comma */
194     (void)strlcat(reply, "}\r\n", sizeof(reply) - strlen(reply));
195 }
196
197 void json_sky_dump(const struct gps_data_t *datap,
198                    /*@out@*/ char *reply, size_t replylen)
199 {
200     int i, j, used, reported = 0;
201     assert(replylen > 2);
202     (void)strlcpy(reply, "{\"class\":\"SKY\",", replylen);
203     (void)snprintf(reply + strlen(reply),
204                    replylen - strlen(reply),
205                    "\"tag\":\"%s\",",
206                    datap->tag[0] != '\0' ? datap->tag : "-");
207     (void)snprintf(reply + strlen(reply),
208                    replylen - strlen(reply),
209                    "\"device\":\"%s\",", datap->dev.path);
210     if (isnan(datap->skyview_time) == 0)
211         (void)snprintf(reply + strlen(reply),
212                        replylen - strlen(reply),
213                        "\"time\":%.3f,", datap->skyview_time);
214     if (isnan(datap->dop.xdop) == 0)
215         (void)snprintf(reply + strlen(reply),
216                        replylen - strlen(reply),
217                        "\"xdop\":%.2f,", datap->dop.xdop);
218     if (isnan(datap->dop.ydop) == 0)
219         (void)snprintf(reply + strlen(reply),
220                        replylen - strlen(reply),
221                        "\"ydop\":%.2f,", datap->dop.ydop);
222     if (isnan(datap->dop.vdop) == 0)
223         (void)snprintf(reply + strlen(reply),
224                        replylen - strlen(reply),
225                        "\"vdop\":%.2f,", datap->dop.vdop);
226     if (isnan(datap->dop.tdop) == 0)
227         (void)snprintf(reply + strlen(reply),
228                        replylen - strlen(reply),
229                        "\"tdop\":%.2f,", datap->dop.tdop);
230     if (isnan(datap->dop.hdop) == 0)
231         (void)snprintf(reply + strlen(reply),
232                        replylen - strlen(reply),
233                        "\"hdop\":%.2f,", datap->dop.hdop);
234     if (isnan(datap->dop.gdop) == 0)
235         (void)snprintf(reply + strlen(reply),
236                        replylen - strlen(reply),
237                        "\"gdop\":%.2f,", datap->dop.gdop);
238     if (isnan(datap->dop.pdop) == 0)
239         (void)snprintf(reply + strlen(reply),
240                        replylen - strlen(reply),
241                        "\"pdop\":%.2f,", datap->dop.pdop);
242     /* insurance against flaky drivers */
243     for (i = 0; i < datap->satellites_visible; i++)
244         if (datap->PRN[i])
245             reported++;
246     if (reported) {
247         (void)strlcat(reply, "\"satellites\":[", replylen);
248         for (i = 0; i < reported; i++) {
249             used = 0;
250             for (j = 0; j < datap->satellites_used; j++)
251                 if (datap->used[j] == datap->PRN[i]) {
252                     used = 1;
253                     break;
254                 }
255             if (datap->PRN[i]) {
256                 (void)snprintf(reply + strlen(reply),
257                                replylen - strlen(reply),
258                                "{\"PRN\":%d,\"el\":%d,\"az\":%d,\"ss\":%.0f,\"used\":%s},",
259                                datap->PRN[i],
260                                datap->elevation[i], datap->azimuth[i],
261                                datap->ss[i], used ? "true" : "false");
262             }
263         }
264     }
265     if (reported) {
266         if (reply[strlen(reply) - 1] == ',')
267             reply[strlen(reply) - 1] = '\0';    /* trim trailing comma */
268         (void)strlcat(reply, "]", replylen - strlen(reply));
269     }
270     if (reply[strlen(reply) - 1] == ',')
271         reply[strlen(reply) - 1] = '\0';        /* trim trailing comma */
272     (void)strlcat(reply, "}\r\n", replylen - strlen(reply));
273     if (datap->satellites_visible != reported)
274         gpsd_report(LOG_WARN, "Satellite count %d != PRN count %d\n",
275                     datap->satellites_visible, reported);
276 }
277
278 void json_device_dump(const struct gps_device_t *device,
279                       /*@out@*/ char *reply, size_t replylen)
280 {
281     char buf1[JSON_VAL_MAX * 2 + 1];
282     struct classmap_t *cmp;
283     (void)strlcpy(reply, "{\"class\":\"DEVICE\",\"path\":\"", replylen);
284     (void)strlcat(reply, device->gpsdata.dev.path, replylen);
285     (void)strlcat(reply, "\",", replylen);
286     if (device->gpsdata.online > 0) {
287         (void)snprintf(reply + strlen(reply), replylen - strlen(reply),
288                        "\"activated\":%2.2f,", device->gpsdata.online);
289         if (device->observed != 0) {
290             int mask = 0;
291             for (cmp = classmap; cmp < classmap + NITEMS(classmap); cmp++)
292                 if ((device->observed & cmp->packetmask) != 0)
293                     mask |= cmp->typemask;
294             if (mask != 0)
295                 (void)snprintf(reply + strlen(reply),
296                                replylen - strlen(reply), "\"flags\":%d,",
297                                mask);
298         }
299         if (device->device_type != NULL) {
300             (void)strlcat(reply, "\"driver\":\"", replylen);
301             (void)strlcat(reply, device->device_type->type_name, replylen);
302             (void)strlcat(reply, "\",", replylen);
303         }
304         /*@-mustfreefresh@*/
305         if (device->subtype[0] != '\0') {
306             (void)strlcat(reply, "\"subtype\":\"", replylen);
307             (void)strlcat(reply,
308                           json_stringify(buf1, sizeof(buf1), device->subtype),
309                           replylen);
310             (void)strlcat(reply, "\",", replylen);
311         }
312         /*@+mustfreefresh@*/
313         if (device->is_serial) {
314             (void)snprintf(reply + strlen(reply), replylen - strlen(reply),
315                            "\"native\":%d,\"bps\":%d,\"parity\":\"%c\",\"stopbits\":%u,\"cycle\":%2.2f",
316                            device->gpsdata.dev.driver_mode,
317                            (int)gpsd_get_speed(&device->ttyset),
318                            device->gpsdata.dev.parity,
319                            device->gpsdata.dev.stopbits,
320                            device->gpsdata.dev.cycle);
321 #ifdef ALLOW_RECONFIGURE
322             if (device->device_type != NULL
323                 && device->device_type->rate_switcher != NULL)
324                 (void)snprintf(reply + strlen(reply),
325                                replylen - strlen(reply),
326                                ",\"mincycle\":%2.2f",
327                                device->device_type->min_cycle);
328 #endif /* ALLOW_RECONFIGURE */
329         }
330     }
331     if (reply[strlen(reply) - 1] == ',')
332         reply[strlen(reply) - 1] = '\0';        /* trim trailing comma */
333     (void)strlcat(reply, "}\r\n", replylen);
334 }
335
336 void json_watch_dump(const struct policy_t *ccp,
337                      /*@out@*/ char *reply, size_t replylen)
338 {
339     /*@-compdef@*/
340     (void)snprintf(reply, replylen,
341                    "{\"class\":\"WATCH\",\"enable\":%s,\"json\":%s,\"nmea\":%s,\"raw\":%d,\"scaled\":%s,\"timing\":%s",
342                    ccp->watcher ? "true" : "false",
343                    ccp->json ? "true" : "false",
344                    ccp->nmea ? "true" : "false",
345                    ccp->raw,
346                    ccp->scaled ? "true" : "false",
347                    ccp->timing ? "true" : "false");
348     if (ccp->devpath[0] != '\0')
349         (void)snprintf(reply + strlen(reply), replylen - strlen(reply),
350                        "\"device\":%s,", ccp->devpath);
351     if (reply[strlen(reply) - 1] == ',')
352         reply[strlen(reply) - 1] = '\0';
353     (void)strlcat(reply, "}\r\n", replylen - strlen(reply));
354     /*@+compdef@*/
355 }
356
357 #if defined(RTCM104V2_ENABLE)
358 void rtcm2_json_dump(const struct rtcm2_t *rtcm, /*@out@*/ char buf[],
359                      size_t buflen)
360 /* dump the contents of a parsed RTCM104 message as JSON */
361 {
362     /*@-mustfreefresh@*/
363     char buf1[JSON_VAL_MAX * 2 + 1];
364     /*
365      * Beware! Needs to stay synchronized with a JSON enumeration map in
366      * the parser. This interpretation of NAVSYSTEM_GALILEO is assumed
367      * from RTCM3, it's not actually documented in RTCM 2.1.
368      */
369     static char *navsysnames[] = { "GPS", "GLONASS", "GALILEO" };
370
371     unsigned int n;
372
373     (void)snprintf(buf, buflen,
374                    "{\"class\":\"RTCM2\",\"type\":%u,\"station_id\":%u,\"zcount\":%0.1f,\"seqnum\":%u,\"length\":%u,\"station_health\":%u,",
375                    rtcm->type, rtcm->refstaid, rtcm->zcount, rtcm->seqnum,
376                    rtcm->length, rtcm->stathlth);
377
378     switch (rtcm->type) {
379     case 1:
380     case 9:
381         (void)strlcat(buf, "\"satellites\":[", buflen);
382         for (n = 0; n < rtcm->ranges.nentries; n++) {
383             const struct rangesat_t *rsp = &rtcm->ranges.sat[n];
384             (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
385                            "{\"ident\":%u,\"udre\":%u,\"issuedata\":%u,\"rangerr\":%0.3f,\"rangerate\":%0.3f},",
386                            rsp->ident,
387                            rsp->udre,
388                            rsp->issuedata, rsp->rangerr, rsp->rangerate);
389         }
390         if (buf[strlen(buf) - 1] == ',')
391             buf[strlen(buf) - 1] = '\0';
392         (void)strlcat(buf, "]", buflen);
393         break;
394
395     case 3:
396         if (rtcm->ecef.valid)
397             (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
398                            "\"x\":%.2f,\"y\":%.2f,\"z\":%.2f,",
399                            rtcm->ecef.x, rtcm->ecef.y, rtcm->ecef.z);
400         break;
401
402     case 4:
403         if (rtcm->reference.valid) {
404             (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
405                            "\"system\":\"%s\",\"sense\":%1d,\"datum\":\"%s\",\"dx\":%.1f,\"dy\":%.1f,\"dz\":%.1f,",
406                            rtcm->reference.system >= NITEMS(navsysnames)
407                            ? "UNKNOWN"
408                            : navsysnames[rtcm->reference.system],
409                            rtcm->reference.sense,
410                            rtcm->reference.datum,
411                            rtcm->reference.dx,
412                            rtcm->reference.dy, rtcm->reference.dz);
413         }
414         break;
415
416     case 5:
417 #define JSON_BOOL(x)    ((x)?"true":"false")
418         (void)strlcat(buf, "\"satellites\":[", buflen);
419         for (n = 0; n < rtcm->conhealth.nentries; n++) {
420             const struct consat_t *csp = &rtcm->conhealth.sat[n];
421             (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
422                            "{\"ident\":%u,\"iodl\":%s,\"health\":%1u,\"snr\":%d,\"health_en\":%s,\"new_data\":%s,\"los_warning\":%s,\"tou\":%u},",
423                            csp->ident,
424                            JSON_BOOL(csp->iodl),
425                            (unsigned)csp->health,
426                            csp->snr,
427                            JSON_BOOL(csp->health_en),
428                            JSON_BOOL(csp->new_data),
429                            JSON_BOOL(csp->los_warning), csp->tou);
430         }
431 #undef JSON_BOOL
432         if (buf[strlen(buf) - 1] == ',')
433             buf[strlen(buf) - 1] = '\0';
434         (void)strlcat(buf, "]", buflen);
435         break;
436
437     case 6:                     /* NOP msg */
438         break;
439
440     case 7:
441         (void)strlcat(buf, "\"satellites\":[", buflen);
442         for (n = 0; n < rtcm->almanac.nentries; n++) {
443             const struct station_t *ssp = &rtcm->almanac.station[n];
444             (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
445                            "{\"lat\":%.4f,\"lon\":%.4f,\"range\":%u,\"frequency\":%.1f,\"health\":%u,\"station_id\":%u,\"bitrate\":%u},",
446                            ssp->latitude,
447                            ssp->longitude,
448                            ssp->range,
449                            ssp->frequency,
450                            ssp->health, ssp->station_id, ssp->bitrate);
451         }
452         if (buf[strlen(buf) - 1] == ',')
453             buf[strlen(buf) - 1] = '\0';
454         (void)strlcat(buf, "]", buflen);
455         break;
456     case 16:
457         (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
458                        "\"message\":\"%s\"", json_stringify(buf1,
459                                                             sizeof(buf1),
460                                                             rtcm->message));
461         break;
462
463     default:
464         (void)strlcat(buf, "\"data\":[", buflen);
465         for (n = 0; n < rtcm->length; n++)
466             (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
467                            "\"0x%08x\",", rtcm->words[n]);
468         if (buf[strlen(buf) - 1] == ',')
469             buf[strlen(buf) - 1] = '\0';
470         (void)strlcat(buf, "]", buflen);
471         break;
472     }
473
474     if (buf[strlen(buf) - 1] == ',')
475         buf[strlen(buf) - 1] = '\0';
476     (void)strlcat(buf, "}\r\n", buflen);
477     /*@+mustfreefresh@*/
478 }
479 #endif /* defined(RTCM104V2_ENABLE) */
480
481 #if defined(AIVDM_ENABLE)
482
483 void aivdm_json_dump(const struct ais_t *ais, bool scaled,
484                      /*@out@*/ char *buf, size_t buflen)
485 {
486     char buf1[JSON_VAL_MAX * 2 + 1];
487     char buf2[JSON_VAL_MAX * 2 + 1];
488     char buf3[JSON_VAL_MAX * 2 + 1];
489
490     static char *nav_legends[] = {
491         "Under way using engine",
492         "At anchor",
493         "Not under command",
494         "Restricted manoeuverability",
495         "Constrained by her draught",
496         "Moored",
497         "Aground",
498         "Engaged in fishing",
499         "Under way sailing",
500         "Reserved for HSC",
501         "Reserved for WIG",
502         "Reserved",
503         "Reserved",
504         "Reserved",
505         "Reserved",
506         "Not defined",
507     };
508     static char *epfd_legends[] = {
509         "Undefined",
510         "GPS",
511         "GLONASS",
512         "Combined GPS/GLONASS",
513         "Loran-C",
514         "Chayka",
515         "Integrated navigation system",
516         "Surveyed",
517         "Galileo",
518     };
519
520     static char *ship_type_legends[100] = {
521         "Not available",
522         "Reserved for future use",
523         "Reserved for future use",
524         "Reserved for future use",
525         "Reserved for future use",
526         "Reserved for future use",
527         "Reserved for future use",
528         "Reserved for future use",
529         "Reserved for future use",
530         "Reserved for future use",
531         "Reserved for future use",
532         "Reserved for future use",
533         "Reserved for future use",
534         "Reserved for future use",
535         "Reserved for future use",
536         "Reserved for future use",
537         "Reserved for future use",
538         "Reserved for future use",
539         "Reserved for future use",
540         "Reserved for future use",
541         "Wing in ground (WIG) - all ships of this type",
542         "Wing in ground (WIG) - Hazardous category A",
543         "Wing in ground (WIG) - Hazardous category B",
544         "Wing in ground (WIG) - Hazardous category C",
545         "Wing in ground (WIG) - Hazardous category D",
546         "Wing in ground (WIG) - Reserved for future use",
547         "Wing in ground (WIG) - Reserved for future use",
548         "Wing in ground (WIG) - Reserved for future use",
549         "Wing in ground (WIG) - Reserved for future use",
550         "Wing in ground (WIG) - Reserved for future use",
551         "Fishing",
552         "Towing",
553         "Towing: length exceeds 200m or breadth exceeds 25m",
554         "Dredging or underwater ops",
555         "Diving ops",
556         "Military ops",
557         "Sailing",
558         "Pleasure Craft",
559         "Reserved",
560         "Reserved",
561         "High speed craft (HSC) - all ships of this type",
562         "High speed craft (HSC) - Hazardous category A",
563         "High speed craft (HSC) - Hazardous category B",
564         "High speed craft (HSC) - Hazardous category C",
565         "High speed craft (HSC) - Hazardous category D",
566         "High speed craft (HSC) - Reserved for future use",
567         "High speed craft (HSC) - Reserved for future use",
568         "High speed craft (HSC) - Reserved for future use",
569         "High speed craft (HSC) - Reserved for future use",
570         "High speed craft (HSC) - No additional information",
571         "Pilot Vessel",
572         "Search and Rescue vessel",
573         "Tug",
574         "Port Tender",
575         "Anti-pollution equipment",
576         "Law Enforcement",
577         "Spare - Local Vessel",
578         "Spare - Local Vessel",
579         "Medical Transport",
580         "Ship according to RR Resolution No. 18",
581         "Passenger - all ships of this type",
582         "Passenger - Hazardous category A",
583         "Passenger - Hazardous category B",
584         "Passenger - Hazardous category C",
585         "Passenger - Hazardous category D",
586         "Passenger - Reserved for future use",
587         "Passenger - Reserved for future use",
588         "Passenger - Reserved for future use",
589         "Passenger - Reserved for future use",
590         "Passenger - No additional information",
591         "Cargo - all ships of this type",
592         "Cargo - Hazardous category A",
593         "Cargo - Hazardous category B",
594         "Cargo - Hazardous category C",
595         "Cargo - Hazardous category D",
596         "Cargo - Reserved for future use",
597         "Cargo - Reserved for future use",
598         "Cargo - Reserved for future use",
599         "Cargo - Reserved for future use",
600         "Cargo - No additional information",
601         "Tanker - all ships of this type",
602         "Tanker - Hazardous category A",
603         "Tanker - Hazardous category B",
604         "Tanker - Hazardous category C",
605         "Tanker - Hazardous category D",
606         "Tanker - Reserved for future use",
607         "Tanker - Reserved for future use",
608         "Tanker - Reserved for future use",
609         "Tanker - Reserved for future use",
610         "Tanker - No additional information",
611         "Other Type - all ships of this type",
612         "Other Type - Hazardous category A",
613         "Other Type - Hazardous category B",
614         "Other Type - Hazardous category C",
615         "Other Type - Hazardous category D",
616         "Other Type - Reserved for future use",
617         "Other Type - Reserved for future use",
618         "Other Type - Reserved for future use",
619         "Other Type - Reserved for future use",
620         "Other Type - no additional information",
621     };
622
623 #define SHIPTYPE_DISPLAY(n) (((n) < (unsigned int)NITEMS(ship_type_legends)) ? ship_type_legends[n] : "INVALID SHIP TYPE")
624
625     static char *station_type_legends[16] = {
626         "All types of mobiles",
627         "Reserved for future use",
628         "All types of Class B mobile stations",
629         "SAR airborne mobile station",
630         "Aid to Navigation station",
631         "Class B shipborne mobile station",
632         "Regional use and inland waterways",
633         "Regional use and inland waterways",
634         "Regional use and inland waterways",
635         "Regional use and inland waterways",
636         "Reserved for future use",
637         "Reserved for future use",
638         "Reserved for future use",
639         "Reserved for future use",
640         "Reserved for future use",
641         "Reserved for future use",
642     };
643
644 #define STATIONTYPE_DISPLAY(n) (((n) < (unsigned int)NITEMS(ship_type_legends)) ? station_type_legends[n] : "INVALID STATION TYPE")
645
646     static char *navaid_type_legends[] = {
647         "Unspcified",
648         "Reference point",
649         "RACON",
650         "Fixed offshore structure",
651         "Spare, Reserved for future use.",
652         "Light, without sectors",
653         "Light, with sectors",
654         "Leading Light Front",
655         "Leading Light Rear",
656         "Beacon, Cardinal N",
657         "Beacon, Cardinal E",
658         "Beacon, Cardinal S",
659         "Beacon, Cardinal W",
660         "Beacon, Port hand",
661         "Beacon, Starboard hand",
662         "Beacon, Preferred Channel port hand",
663         "Beacon, Preferred Channel starboard hand",
664         "Beacon, Isolated danger",
665         "Beacon, Safe water",
666         "Beacon, Special mark",
667         "Cardinal Mark N",
668         "Cardinal Mark E",
669         "Cardinal Mark S",
670         "Cardinal Mark W",
671         "Port hand Mark",
672         "Starboard hand Mark",
673         "Preferred Channel Port hand",
674         "Preferred Channel Starboard hand",
675         "Isolated danger",
676         "Safe Water",
677         "Special Mark",
678         "Light Vessel / LANBY / Rigs",
679     };
680
681 #define NAVAIDTYPE_DISPLAY(n) (((n) < (unsigned int)NITEMS(navaid_type_legends[0])) ? navaid_type_legends[n] : "INVALID NAVAID TYPE")
682
683 #define JSON_BOOL(x)    ((x)?"true":"false")
684     (void)snprintf(buf, buflen,
685                    "{\"class\":\"AIS\",\"type\":%u,\"repeat\":%u,"
686                    "\"mmsi\":%u,\"scaled\":%s,",
687                    ais->type, ais->repeat, ais->mmsi, JSON_BOOL(scaled));
688     /*@ -formatcode -mustfreefresh @*/
689     switch (ais->type) {
690     case 1:                     /* Position Report */
691     case 2:
692     case 3:
693         if (scaled) {
694             char turnlegend[20];
695             char speedlegend[20];
696
697             /*
698              * Express turn as nan if not available,
699              * "fastleft"/"fastright" for fast turns.
700              */
701             if (ais->type1.turn == -128)
702                 (void)strlcpy(turnlegend, "\"nan\"", sizeof(turnlegend));
703             else if (ais->type1.turn == -127)
704                 (void)strlcpy(turnlegend, "\"fastleft\"", sizeof(turnlegend));
705             else if (ais->type1.turn == 127)
706                 (void)strlcpy(turnlegend, "\"fastright\"",
707                               sizeof(turnlegend));
708             else {
709                 double rot1 = ais->type1.turn / 4.733;
710                 (void)snprintf(turnlegend, sizeof(turnlegend),
711                                "%.0f", rot1 * rot1);
712             }
713
714             /*
715              * Express speed as nan if not available,
716              * "fast" for fast movers.
717              */
718             if (ais->type1.speed == AIS_SPEED_NOT_AVAILABLE)
719                 (void)strlcpy(speedlegend, "\"nan\"", sizeof(speedlegend));
720             else if (ais->type1.speed == AIS_SPEED_FAST_MOVER)
721                 (void)strlcpy(speedlegend, "\"fast\"", sizeof(speedlegend));
722             else
723                 (void)snprintf(speedlegend, sizeof(speedlegend),
724                                "%.1f", ais->type1.speed / 10.0);
725
726             (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
727                            "\"status\":\"%s\",\"turn\":%s,\"speed\":%s,"
728                            "\"accuracy\":%s,\"lon\":%.4f,\"lat\":%.4f,"
729                            "\"course\":%u,\"heading\":%u,\"second\":%u,"
730                            "\"maneuver\":%u,\"raim\":%s,\"radio\":%u}\r\n",
731                            nav_legends[ais->type1.status],
732                            turnlegend,
733                            speedlegend,
734                            JSON_BOOL(ais->type1.accuracy),
735                            ais->type1.lon / AIS_LATLON_SCALE,
736                            ais->type1.lat / AIS_LATLON_SCALE,
737                            ais->type1.course,
738                            ais->type1.heading,
739                            ais->type1.second,
740                            ais->type1.maneuver,
741                            JSON_BOOL(ais->type1.raim), ais->type1.radio);
742         } else {
743             (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
744                            "\"status\":%u,\"turn\":%d,\"speed\":%u,"
745                            "\"accuracy\":%s,\"lon\":%d,\"lat\":%d,"
746                            "\"course\":%u,\"heading\":%u,\"second\":%u,"
747                            "\"maneuver\":%u,\"raim\":%s,\"radio\":%u}\r\n",
748                            ais->type1.status,
749                            ais->type1.turn,
750                            ais->type1.speed,
751                            JSON_BOOL(ais->type1.accuracy),
752                            ais->type1.lon,
753                            ais->type1.lat,
754                            ais->type1.course,
755                            ais->type1.heading,
756                            ais->type1.second,
757                            ais->type1.maneuver,
758                            JSON_BOOL(ais->type1.raim), ais->type1.radio);
759         }
760         break;
761     case 4:                     /* Base Station Report */
762     case 11:                    /* UTC/Date Response */
763         if (scaled) {
764             (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
765                            "\"timestamp\":\"%4u:%02u:%02uT%02u:%02u:%02uZ\","
766                            "\"accuracy\":%s,\"lon\":%.4f,\"lat\":%.4f,"
767                            "\"epfd\":\"%s\",\"raim\":%s,\"radio\":%u}\r\n",
768                            ais->type4.year,
769                            ais->type4.month,
770                            ais->type4.day,
771                            ais->type4.hour,
772                            ais->type4.minute,
773                            ais->type4.second,
774                            JSON_BOOL(ais->type4.accuracy),
775                            ais->type4.lon / AIS_LATLON_SCALE,
776                            ais->type4.lat / AIS_LATLON_SCALE,
777                            epfd_legends[ais->type4.epfd],
778                            JSON_BOOL(ais->type4.raim), ais->type4.radio);
779         } else {
780             (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
781                            "\"timestamp\":\"%04u-%02u-%02uT%02u:%02u:%02uZ\","
782                            "\"accuracy\":%s,\"lon\":%d,\"lat\":%d,"
783                            "\"epfd\":%u,\"raim\":%s,\"radio\":%u}\r\n",
784                            ais->type4.year,
785                            ais->type4.month,
786                            ais->type4.day,
787                            ais->type4.hour,
788                            ais->type4.minute,
789                            ais->type4.second,
790                            JSON_BOOL(ais->type4.accuracy),
791                            ais->type4.lon,
792                            ais->type4.lat,
793                            ais->type4.epfd,
794                            JSON_BOOL(ais->type4.raim), ais->type4.radio);
795         }
796         break;
797     case 5:                     /* Ship static and voyage related data */
798         if (scaled) {
799             /* *INDENT-OFF* */
800             (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
801                            "\"imo\":%u,\"ais_version\":%u,\"callsign\":\"%s\","
802                            "\"shipname\":\"%s\",\"shiptype\":\"%s\","
803                            "\"to_bow\":%u,\"to_stern\":%u,\"to_port\":%u,"
804                            "\"to_starboard\":%u,\"epfd\":\"%s\","
805                            "\"eta\":\"%02u-%02uT%02u:%02uZ\","
806                            "\"draught\":%.1f,\"destination\":\"%s\","
807                            "\"dte\":%u}\r\n",
808                            ais->type5.imo,
809                            ais->type5.ais_version,
810                            json_stringify(buf1, sizeof(buf1),
811                                           ais->type5.callsign),
812                            json_stringify(buf2, sizeof(buf2),
813                                           ais->type5.shipname),
814                            SHIPTYPE_DISPLAY(ais->type5.shiptype),
815                            ais->type5.to_bow, ais->type5.to_stern,
816                            ais->type5.to_port, ais->type5.to_starboard,
817                            epfd_legends[ais->type5.epfd], ais->type5.month,
818                            ais->type5.day, ais->type5.hour, ais->type5.minute,
819                            ais->type5.draught / 10.0,
820                            json_stringify(buf3, sizeof(buf3),
821                                           ais->type5.destination),
822                            ais->type5.dte);
823             /* *INDENT-ON* */
824         } else {
825             (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
826                            "\"imo\":%u,\"ais_version\":%u,\"callsign\":\"%s\","
827                            "\"shipname\":\"%s\",\"shiptype\":%u,"
828                            "\"to_bow\":%u,\"to_stern\":%u,\"to_port\":%u,"
829                            "\"to_starboard\":%u,\"epfd\":%u,"
830                            "\"eta\":\"%02u-%02uT%02u:%02uZ\","
831                            "\"draught\":%u,\"destination\":\"%s\","
832                            "\"dte\":%u}\r\n",
833                            ais->type5.imo,
834                            ais->type5.ais_version,
835                            json_stringify(buf1, sizeof(buf1),
836                                           ais->type5.callsign),
837                            json_stringify(buf2, sizeof(buf2),
838                                           ais->type5.shipname),
839                            ais->type5.shiptype, ais->type5.to_bow,
840                            ais->type5.to_stern, ais->type5.to_port,
841                            ais->type5.to_starboard, ais->type5.epfd,
842                            ais->type5.month, ais->type5.day, ais->type5.hour,
843                            ais->type5.minute, ais->type5.draught,
844                            json_stringify(buf3, sizeof(buf3),
845                                           ais->type5.destination),
846                            ais->type5.dte);
847         }
848         break;
849     case 6:                     /* Binary Message */
850         (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
851                        "\"seqno\":%u,\"dest_mmsi\":%u,"
852                        "\"retransmit\":%s,\"dac\":%u,\"fid\":%u,"
853                        "\"data\":\"%zd:%s\"}\r\n",
854                        ais->type6.seqno,
855                        ais->type6.dest_mmsi,
856                        JSON_BOOL(ais->type6.retransmit),
857                        ais->type6.dac,
858                        ais->type6.fid,
859                        ais->type6.bitcount,
860                        gpsd_hexdump(ais->type6.bitdata,
861                                     (ais->type6.bitcount + 7) / 8));
862         break;
863     case 7:                     /* Binary Acknowledge */
864     case 13:                    /* Safety Related Acknowledge */
865         (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
866                        "\"mmsi1\":%u,\"mmsi2\":%u,\"mmsi3\":%u,\"mmsi4\":%u}\r\n",
867                        ais->type7.mmsi1,
868                        ais->type7.mmsi2, ais->type7.mmsi3, ais->type7.mmsi4);
869         break;
870     case 8:                     /* Binary Broadcast Message */
871         (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
872                        "\"dac\":%u,\"fid\":%u,\"data\":\"%zd:%s\"}\r\n",
873                        ais->type8.dac,
874                        ais->type8.fid,
875                        ais->type8.bitcount,
876                        gpsd_hexdump(ais->type8.bitdata,
877                                     (ais->type8.bitcount + 7) / 8));
878         break;
879     case 9:                     /* Standard SAR Aircraft Position Report */
880         if (scaled) {
881             char altlegend[20];
882             char speedlegend[20];
883
884             /*
885              * Express altitude as nan if not available,
886              * "high" for above the reporting ceiling.
887              */
888             if (ais->type9.alt == AIS_ALT_NOT_AVAILABLE)
889                 (void)strlcpy(altlegend, "\"nan\"", sizeof(altlegend));
890             else if (ais->type9.alt == AIS_ALT_HIGH)
891                 (void)strlcpy(altlegend, "\"high\"", sizeof(altlegend));
892             else
893                 (void)snprintf(altlegend, sizeof(altlegend),
894                                "%.1f", ais->type9.alt / 10.0);
895
896             /*
897              * Express speed as nan if not available,
898              * "high" for above the reporting ceiling.
899              */
900             if (ais->type9.speed == AIS_SAR_SPEED_NOT_AVAILABLE)
901                 (void)strlcpy(speedlegend, "\"nan\"", sizeof(speedlegend));
902             else if (ais->type9.speed == AIS_SAR_FAST_MOVER)
903                 (void)strlcpy(speedlegend, "\"fast\"", sizeof(speedlegend));
904             else
905                 (void)snprintf(speedlegend, sizeof(speedlegend),
906                                "%.1f", ais->type1.speed / 10.0);
907
908             (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
909                            "\"alt\":%s,\"speed\":%s,\"accuracy\":%s,"
910                            "\"lon\":%.4f,\"lat\":%.4f,\"course\":%.1f,"
911                            "\"second\":%u,\"regional\":%u,\"dte\":%u,"
912                            "\"raim\":%s,\"radio\":%u}\r\n",
913                            altlegend,
914                            speedlegend,
915                            JSON_BOOL(ais->type9.accuracy),
916                            ais->type9.lon / AIS_LATLON_SCALE,
917                            ais->type9.lat / AIS_LATLON_SCALE,
918                            ais->type9.course / 10.0,
919                            ais->type9.second,
920                            ais->type9.regional,
921                            ais->type9.dte,
922                            JSON_BOOL(ais->type9.raim), ais->type9.radio);
923         } else {
924             (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
925                            "\"alt\":%u,\"speed\":%u,\"accuracy\":%s,"
926                            "\"lon\":%d,\"lat\":%d,\"course\":%u,"
927                            "\"second\":%u,\"regional\":%u,\"dte\":%u,"
928                            "\"raim\":%s,\"radio\":%u}\r\n",
929                            ais->type9.alt,
930                            ais->type9.speed,
931                            JSON_BOOL(ais->type9.accuracy),
932                            ais->type9.lon,
933                            ais->type9.lat,
934                            ais->type9.course,
935                            ais->type9.second,
936                            ais->type9.regional,
937                            ais->type9.dte,
938                            JSON_BOOL(ais->type9.raim), ais->type9.radio);
939         }
940         break;
941     case 10:                    /* UTC/Date Inquiry */
942         (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
943                        "\"dest_mmsi\":%u}\r\n", ais->type10.dest_mmsi);
944         break;
945     case 12:                    /* Safety Related Message */
946         (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
947                        "\"seqno\":%u,\"dest_mmsi\":%u,\"retransmit\":%s,\"text\":\"%s\"}\r\n",
948                        ais->type12.seqno,
949                        ais->type12.dest_mmsi,
950                        JSON_BOOL(ais->type12.retransmit),
951                        json_stringify(buf1, sizeof(buf1), ais->type12.text));
952         break;
953     case 14:                    /* Safety Related Broadcast Message */
954         (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
955                        "\"text\":\"%s\"}\r\n",
956                        json_stringify(buf1, sizeof(buf1), ais->type14.text));
957         break;
958     case 15:                    /* Interrogation */
959         (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
960                        "\"mmsi1\":%u,\"type1_1\":%u,\"offset1_1\":%u,"
961                        "\"type1_2\":%u,\"offset1_2\":%u,\"mmsi2\":%u,"
962                        "\"type2_1\":%u,\"offset2_1\":%u}\r\n",
963                        ais->type15.mmsi1,
964                        ais->type15.type1_1,
965                        ais->type15.offset1_1,
966                        ais->type15.type1_2,
967                        ais->type15.offset1_2,
968                        ais->type15.mmsi2,
969                        ais->type15.type2_1, ais->type15.offset2_1);
970         break;
971     case 16:
972         (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
973                        "\"mmsi1\":%u,\"offset1\":%u,\"increment1\":%u,"
974                        "\"mmsi2\":%u,\"offset2\":%u,\"increment2\":%u}\r\n",
975                        ais->type16.mmsi1,
976                        ais->type16.offset1,
977                        ais->type16.increment1,
978                        ais->type16.mmsi2,
979                        ais->type16.offset2, ais->type16.increment2);
980         break;
981     case 17:
982         if (scaled) {
983             (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
984                            "\"lon\":%.1f,\"lat\":%.1f,\"data\":\"%zd:%s\"}\r\n",
985                            ais->type17.lon / AIS_GNSS_LATLON_SCALE,
986                            ais->type17.lat / AIS_GNSS_LATLON_SCALE,
987                            ais->type17.bitcount,
988                            gpsd_hexdump(ais->type17.bitdata,
989                                         (ais->type17.bitcount + 7) / 8));
990         } else {
991             (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
992                            "\"lon\":%d,\"lat\":%d,\"data\":\"%zd:%s\"}\r\n",
993                            ais->type17.lon,
994                            ais->type17.lat,
995                            ais->type17.bitcount,
996                            gpsd_hexdump(ais->type17.bitdata,
997                                         (ais->type17.bitcount + 7) / 8));
998         }
999         break;
1000     case 18:
1001         if (scaled) {
1002             (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
1003                            "\"reserved\":%u,\"speed\":%.1f,\"accuracy\":%s,"
1004                            "\"lon\":%.4f,\"lat\":%.4f,\"course\":%.1f,"
1005                            "\"heading\":%u,\"second\":%u,\"regional\":%u,"
1006                            "\"cs\":%s,\"display\":%s,\"dsc\":%s,\"band\":%s,"
1007                            "\"msg22\":%s,\"raim\":%s,\"radio\":%u}\r\n",
1008                            ais->type18.reserved,
1009                            ais->type18.speed / 10.0,
1010                            JSON_BOOL(ais->type18.accuracy),
1011                            ais->type18.lon / AIS_LATLON_SCALE,
1012                            ais->type18.lat / AIS_LATLON_SCALE,
1013                            ais->type18.course / 10.0,
1014                            ais->type18.heading,
1015                            ais->type18.second,
1016                            ais->type18.regional,
1017                            JSON_BOOL(ais->type18.cs),
1018                            JSON_BOOL(ais->type18.display),
1019                            JSON_BOOL(ais->type18.dsc),
1020                            JSON_BOOL(ais->type18.band),
1021                            JSON_BOOL(ais->type18.msg22),
1022                            JSON_BOOL(ais->type18.raim), ais->type18.radio);
1023         } else {
1024             (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
1025                            "\"reserved\":%u,\"speed\":%u,\"accuracy\":%s,"
1026                            "\"lon\":%d,\"lat\":%d,\"course\":%u,"
1027                            "\"heading\":%u,\"second\":%u,\"regional\":%u,"
1028                            "\"cs\":%s,\"display\":%s,\"dsc\":%s,\"band\":%s,"
1029                            "\"msg22\":%s,\"raim\":%s,\"radio\":%u}\r\n",
1030                            ais->type18.reserved,
1031                            ais->type18.speed,
1032                            JSON_BOOL(ais->type18.accuracy),
1033                            ais->type18.lon,
1034                            ais->type18.lat,
1035                            ais->type18.course,
1036                            ais->type18.heading,
1037                            ais->type18.second,
1038                            ais->type18.regional,
1039                            JSON_BOOL(ais->type18.cs),
1040                            JSON_BOOL(ais->type18.display),
1041                            JSON_BOOL(ais->type18.dsc),
1042                            JSON_BOOL(ais->type18.band),
1043                            JSON_BOOL(ais->type18.msg22),
1044                            JSON_BOOL(ais->type18.raim), ais->type18.radio);
1045         }
1046         break;
1047     case 19:
1048         if (scaled) {
1049             (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
1050                            "\"reserved\":%u,\"speed\":%.1f,\"accuracy\":%s,"
1051                            "\"lon\":%.4f,\"lat\":%.4f,\"course\":%.1f,"
1052                            "\"heading\":%u,\"second\":%u,\"regional\":%u,"
1053                            "\"shipname\":\"%s\",\"shiptype\":\"%s\","
1054                            "\"to_bow\":%u,\"to_stern\":%u,\"to_port\":%u,"
1055                            "\"to_starboard\":%u,\"epfd\":\"%s\",\"raim\":%s,"
1056                            "\"dte\":%u,\"assigned\":%s}\r\n",
1057                            ais->type19.reserved,
1058                            ais->type19.speed / 10.0,
1059                            JSON_BOOL(ais->type19.accuracy),
1060                            ais->type19.lon / AIS_LATLON_SCALE,
1061                            ais->type19.lat / AIS_LATLON_SCALE,
1062                            ais->type19.course / 10.0,
1063                            ais->type19.heading,
1064                            ais->type19.second,
1065                            ais->type19.regional,
1066                            ais->type19.shipname,
1067                            SHIPTYPE_DISPLAY(ais->type19.shiptype),
1068                            ais->type19.to_bow,
1069                            ais->type19.to_stern,
1070                            ais->type19.to_port,
1071                            ais->type19.to_starboard,
1072                            epfd_legends[ais->type19.epfd],
1073                            JSON_BOOL(ais->type19.raim),
1074                            ais->type19.dte, JSON_BOOL(ais->type19.assigned));
1075         } else {
1076             (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
1077                            "\"reserved\":%u,\"speed\":%u,\"accuracy\":%s,"
1078                            "\"lon\":%d,\"lat\":%d,\"course\":%u,"
1079                            "\"heading\":%u,\"second\":%u,\"regional\":%u,"
1080                            "\"shipname\":\"%s\",\"shiptype\":%u,"
1081                            "\"to_bow\":%u,\"to_stern\":%u,\"to_port\":%u,"
1082                            "\"to_starboard\":%u,\"epfd\":%u,\"raim\":%s,"
1083                            "\"dte\":%u,\"assigned\":%s}\r\n",
1084                            ais->type19.reserved,
1085                            ais->type19.speed,
1086                            JSON_BOOL(ais->type19.accuracy),
1087                            ais->type19.lon,
1088                            ais->type19.lat,
1089                            ais->type19.course,
1090                            ais->type19.heading,
1091                            ais->type19.second,
1092                            ais->type19.regional,
1093                            ais->type19.shipname,
1094                            ais->type19.shiptype,
1095                            ais->type19.to_bow,
1096                            ais->type19.to_stern,
1097                            ais->type19.to_port,
1098                            ais->type19.to_starboard,
1099                            ais->type19.epfd,
1100                            JSON_BOOL(ais->type19.raim),
1101                            ais->type19.dte, JSON_BOOL(ais->type19.assigned));
1102         }
1103         break;
1104     case 20:                    /* Data Link Management Message */
1105         (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
1106                        "\"offset1\":%u,\"number1\":%u,"
1107                        "\"timeout1\":%u,\"increment1\":%u,"
1108                        "\"offset2\":%u,\"number2\":%u,"
1109                        "\"timeout2\":%u,\"increment2\":%u,"
1110                        "\"offset3\":%u,\"number3\":%u,"
1111                        "\"timeout3\":%u,\"increment3\":%u,"
1112                        "\"offset4\":%u,\"number4\":%u,"
1113                        "\"timeout4\":%u,\"increment4\":%u}\r\n",
1114                        ais->type20.offset1,
1115                        ais->type20.number1,
1116                        ais->type20.timeout1,
1117                        ais->type20.increment1,
1118                        ais->type20.offset2,
1119                        ais->type20.number2,
1120                        ais->type20.timeout2,
1121                        ais->type20.increment2,
1122                        ais->type20.offset3,
1123                        ais->type20.number3,
1124                        ais->type20.timeout3,
1125                        ais->type20.increment3,
1126                        ais->type20.offset4,
1127                        ais->type20.number4,
1128                        ais->type20.timeout4, ais->type20.increment4);
1129         break;
1130     case 21:                    /* Aid to Navigation */
1131         if (scaled) {
1132             (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
1133                            "\"aid_type\":\"%s\",\"name\":\"%s\",\"lon\":%.4f,"
1134                            "\"lat\":%.4f,\"accuracy\":%s,\"to_bow\":%u,"
1135                            "\"to_stern\":%u,\"to_port\":%u,"
1136                            "\"to_starboard\":%u,\"epfd\":\"%s\","
1137                            "\"second\":%u,\"regional\":%u,"
1138                            "\"off_position\":%s,\"raim\":%s,"
1139                            "\"virtual_aid\":%s}\r\n",
1140                            NAVAIDTYPE_DISPLAY(ais->type21.aid_type),
1141                            json_stringify(buf1, sizeof(buf1),
1142                                           ais->type21.name),
1143                            ais->type21.lon / AIS_LATLON_SCALE,
1144                            ais->type21.lat / AIS_LATLON_SCALE,
1145                            JSON_BOOL(ais->type21.accuracy),
1146                            ais->type21.to_bow, ais->type21.to_stern,
1147                            ais->type21.to_port, ais->type21.to_starboard,
1148                            epfd_legends[ais->type21.epfd], ais->type21.second,
1149                            ais->type21.regional,
1150                            JSON_BOOL(ais->type21.off_position),
1151                            JSON_BOOL(ais->type21.raim),
1152                            JSON_BOOL(ais->type21.virtual_aid));
1153         } else {
1154             (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
1155                            "\"aid_type\":%u,\"name\":\"%s\",\"accuracy\":%s,"
1156                            "\"lon\":%d,\"lat\":%d,\"to_bow\":%u,"
1157                            "\"to_stern\":%u,\"to_port\":%u,\"to_starboard\":%u,"
1158                            "\"epfd\":%u,\"second\":%u,\"regional\":%u,"
1159                            "\"off_position\":%s,\"raim\":%s,"
1160                            "\"virtual_aid\":%s}\r\n",
1161                            ais->type21.aid_type,
1162                            ais->type21.name,
1163                            JSON_BOOL(ais->type21.accuracy),
1164                            ais->type21.lon,
1165                            ais->type21.lat,
1166                            ais->type21.to_bow,
1167                            ais->type21.to_stern,
1168                            ais->type21.to_port,
1169                            ais->type21.to_starboard,
1170                            ais->type21.epfd,
1171                            ais->type21.second,
1172                            ais->type21.regional,
1173                            JSON_BOOL(ais->type21.off_position),
1174                            JSON_BOOL(ais->type21.raim),
1175                            JSON_BOOL(ais->type21.virtual_aid));
1176         }
1177         break;
1178     case 22:                    /* Channel Management */
1179         (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
1180                        "\"channel_a\":%u,\"channel_b\":%u,"
1181                        "\"txrx\":%u,\"power\":%s,",
1182                        ais->type22.channel_a,
1183                        ais->type22.channel_b,
1184                        ais->type22.txrx, JSON_BOOL(ais->type22.power));
1185         if (ais->type22.addressed) {
1186             (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
1187                            "\"dest1\":%u,\"dest2\":%u",
1188                            ais->type22.mmsi.dest1, ais->type22.mmsi.dest2);
1189         } else if (scaled) {
1190             (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
1191                            "\"ne_lon\":\"%f\",\"ne_lat\":\"%f\","
1192                            "\"sw_lon\":\"%f\",\"sw_lat\":\"%f\",",
1193                            ais->type22.area.ne_lon / AIS_CHANNEL_LATLON_SCALE,
1194                            ais->type22.area.ne_lat / AIS_CHANNEL_LATLON_SCALE,
1195                            ais->type22.area.sw_lon / AIS_CHANNEL_LATLON_SCALE,
1196                            ais->type22.area.sw_lat /
1197                            AIS_CHANNEL_LATLON_SCALE);
1198         } else {
1199             (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
1200                            "\"ne_lon\":%d,\"ne_lat\":%d,"
1201                            "\"sw_lon\":%d,\"sw_lat\":%d,",
1202                            ais->type22.area.ne_lon,
1203                            ais->type22.area.ne_lat,
1204                            ais->type22.area.sw_lon, ais->type22.area.sw_lat);
1205         }
1206         (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
1207                        "\"addressed\":%s,\"band_a\":%s,"
1208                        "\"band_b\":%s,\"zonesize\":%u}\r\n",
1209                        JSON_BOOL(ais->type22.addressed),
1210                        JSON_BOOL(ais->type22.band_a),
1211                        JSON_BOOL(ais->type22.band_b), ais->type22.zonesize);
1212         break;
1213     case 23:                    /* Group Assignment Command */
1214         if (scaled) {
1215             (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
1216                            "\"ne_lon\":\"%f\",\"ne_lat\":\"%f\","
1217                            "\"sw_lon\":\"%f\",\"sw_lat\":\"%f\","
1218                            "\"stationtype\":\"%s\",\"shiptype\":\"%s\","
1219                            "\"interval\":%u,\"quiet\":%u}\r\n",
1220                            ais->type23.ne_lon / AIS_CHANNEL_LATLON_SCALE,
1221                            ais->type23.ne_lat / AIS_CHANNEL_LATLON_SCALE,
1222                            ais->type23.sw_lon / AIS_CHANNEL_LATLON_SCALE,
1223                            ais->type23.sw_lat / AIS_CHANNEL_LATLON_SCALE,
1224                            STATIONTYPE_DISPLAY(ais->type23.stationtype),
1225                            SHIPTYPE_DISPLAY(ais->type23.shiptype),
1226                            ais->type23.interval, ais->type23.quiet);
1227         } else {
1228             (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
1229                            "\"ne_lon\":%d,\"ne_lat\":%d,"
1230                            "\"sw_lon\":%d,\"sw_lat\":%d,"
1231                            "\"stationtype\":%u,\"shiptype\":%u,"
1232                            "\"interval\":%u,\"quiet\":%u}\r\n",
1233                            ais->type23.ne_lon,
1234                            ais->type23.ne_lat,
1235                            ais->type23.sw_lon,
1236                            ais->type23.sw_lat,
1237                            ais->type23.stationtype,
1238                            ais->type23.shiptype,
1239                            ais->type23.interval, ais->type23.quiet);
1240         }
1241         break;
1242     case 24:                    /* Class B CS Static Data Report */
1243         (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
1244                        "\"shipname\":\"%s\",",
1245                        json_stringify(buf1, sizeof(buf1),
1246                                       ais->type24.shipname));
1247         if (scaled) {
1248             (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
1249                            "\"shiptype\":\"%s\",",
1250                            SHIPTYPE_DISPLAY(ais->type24.shiptype));
1251         } else {
1252             (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
1253                            "\"shiptype\":%u,", ais->type24.shiptype);
1254         }
1255         (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
1256                        "\"vendorid\":\"%s\",\"callsign\":\"%s\",",
1257                        ais->type24.vendorid, ais->type24.callsign);
1258         if (AIS_AUXILIARY_MMSI(ais->mmsi)) {
1259             (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
1260                            "mothership_\"mmsi\":%u}\r\n",
1261                            ais->type24.mothership_mmsi);
1262         } else {
1263             (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
1264                            "\"to_bow\":%u,\"to_stern\":%u,"
1265                            "\"to_port\":%u,\"to_starboard\":%u}\r\n",
1266                            ais->type24.dim.to_bow,
1267                            ais->type24.dim.to_stern,
1268                            ais->type24.dim.to_port,
1269                            ais->type24.dim.to_starboard);
1270         }
1271         break;
1272     case 25:                    /* Binary Message, Single Slot */
1273         (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
1274                        "\"addressed\":%s,\"structured\":%s,\"dest_mmsi\":%u,"
1275                        "\"app_id\":%u,\"data\":\"%zd:%s\"}\r\n",
1276                        JSON_BOOL(ais->type25.addressed),
1277                        JSON_BOOL(ais->type25.structured),
1278                        ais->type25.dest_mmsi,
1279                        ais->type25.app_id,
1280                        ais->type25.bitcount,
1281                        gpsd_hexdump(ais->type25.bitdata,
1282                                     (ais->type25.bitcount + 7) / 8));
1283         break;
1284     case 26:                    /* Binary Message, Multiple Slot */
1285         (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
1286                        "\"addressed\":%s,\"structured\":%s,\"dest_mmsi\":%u,"
1287                        "\"app_id\":%u,\"data\":\"%zd:%s\"\"radio\":%u}\r\n",
1288                        JSON_BOOL(ais->type26.addressed),
1289                        JSON_BOOL(ais->type26.structured),
1290                        ais->type26.dest_mmsi,
1291                        ais->type26.app_id,
1292                        ais->type26.bitcount,
1293                        gpsd_hexdump(ais->type26.bitdata,
1294                                     (ais->type26.bitcount + 7) / 8),
1295                        ais->type26.radio);
1296         break;
1297     default:
1298         if (buf[strlen(buf) - 1] == ',')
1299             buf[strlen(buf) - 1] = '\0';
1300         (void)strlcat(buf, "}\r\n", buflen);
1301         break;
1302     }
1303     /*@ +formatcode +mustfreefresh @*/
1304 #undef SHOW_BOOL
1305 }
1306 #endif /* defined(AIVDM_ENABLE) */
1307
1308 #ifdef COMPASS_ENABLE
1309 void json_att_dump(const struct gps_data_t *gpsdata,
1310                    /*@out@*/ char *reply, size_t replylen)
1311 /* dump the contents of an attitude_t structure as JSON */
1312 {
1313     assert(replylen > 2);
1314     (void)strlcpy(reply, "{\"class\":\"ATT\",", replylen);
1315     (void)snprintf(reply + strlen(reply),
1316                    replylen - strlen(reply),
1317                    "\"tag\":\"%s\",",
1318                    gpsdata->tag[0] != '\0' ? gpsdata->tag : "-");
1319     (void)snprintf(reply + strlen(reply),
1320                    replylen - strlen(reply),
1321                    "\"device\":\"%s\",", gpsdata->dev.path);
1322     if (isnan(gpsdata->attitude.heading) == 0) {
1323         (void)snprintf(reply + strlen(reply),
1324                        replylen - strlen(reply),
1325                        "\"heading\":%.2f,", gpsdata->attitude.heading);
1326         if (gpsdata->attitude.mag_st != '\0')
1327             (void)snprintf(reply + strlen(reply),
1328                            replylen - strlen(reply),
1329                            "\"mag_st\":\"%c\",", gpsdata->attitude.mag_st);
1330
1331     }
1332     if (isnan(gpsdata->attitude.pitch) == 0) {
1333         (void)snprintf(reply + strlen(reply),
1334                        replylen - strlen(reply),
1335                        "\"pitch\":%.2f,", gpsdata->attitude.pitch);
1336         if (gpsdata->attitude.pitch_st != '\0')
1337             (void)snprintf(reply + strlen(reply),
1338                            replylen - strlen(reply),
1339                            "\"pitch_st\":\"%c\",",
1340                            gpsdata->attitude.pitch_st);
1341
1342     }
1343     if (isnan(gpsdata->attitude.yaw) == 0) {
1344         (void)snprintf(reply + strlen(reply),
1345                        replylen - strlen(reply),
1346                        "\"yaw\":%.2f,", gpsdata->attitude.yaw);
1347         if (gpsdata->attitude.yaw_st != '\0')
1348             (void)snprintf(reply + strlen(reply),
1349                            replylen - strlen(reply),
1350                            "\"yaw_st\":\"%c\",", gpsdata->attitude.yaw_st);
1351
1352     }
1353     if (isnan(gpsdata->attitude.roll) == 0) {
1354         (void)snprintf(reply + strlen(reply),
1355                        replylen - strlen(reply),
1356                        "\"roll\":%.2f,", gpsdata->attitude.roll);
1357         if (gpsdata->attitude.roll_st != '\0')
1358             (void)snprintf(reply + strlen(reply),
1359                            replylen - strlen(reply),
1360                            "\"roll_st\":\"%c\",", gpsdata->attitude.roll_st);
1361
1362     }
1363     if (isnan(gpsdata->attitude.yaw) == 0) {
1364         (void)snprintf(reply + strlen(reply),
1365                        replylen - strlen(reply),
1366                        "\"yaw\":%.2f,", gpsdata->attitude.yaw);
1367         if (gpsdata->attitude.yaw_st != '\0')
1368             (void)snprintf(reply + strlen(reply),
1369                            replylen - strlen(reply),
1370                            "\"yaw_st\":\"%c\",", gpsdata->attitude.yaw_st);
1371
1372     }
1373     if (isnan(gpsdata->attitude.dip) == 0)
1374         (void)snprintf(reply + strlen(reply),
1375                        replylen - strlen(reply),
1376                        "\"dip\":%.3f,", gpsdata->attitude.dip);
1377
1378     if (isnan(gpsdata->attitude.mag_len) == 0)
1379         (void)snprintf(reply + strlen(reply),
1380                        replylen - strlen(reply),
1381                        "\"mag_len\":%.3f,", gpsdata->attitude.mag_len);
1382     if (isnan(gpsdata->attitude.mag_x) == 0)
1383         (void)snprintf(reply + strlen(reply),
1384                        replylen - strlen(reply),
1385                        "\"mag_x\":%.3f,", gpsdata->attitude.mag_x);
1386     if (isnan(gpsdata->attitude.mag_y) == 0)
1387         (void)snprintf(reply + strlen(reply),
1388                        replylen - strlen(reply),
1389                        "\"mag_y\":%.3f,", gpsdata->attitude.mag_y);
1390     if (isnan(gpsdata->attitude.mag_z) == 0)
1391         (void)snprintf(reply + strlen(reply),
1392                        replylen - strlen(reply),
1393                        "\"mag_z\":%.3f,", gpsdata->attitude.mag_z);
1394
1395     if (isnan(gpsdata->attitude.acc_len) == 0)
1396         (void)snprintf(reply + strlen(reply),
1397                        replylen - strlen(reply),
1398                        "\"acc_len\":%.3f,", gpsdata->attitude.acc_len);
1399     if (isnan(gpsdata->attitude.acc_x) == 0)
1400         (void)snprintf(reply + strlen(reply),
1401                        replylen - strlen(reply),
1402                        "\"acc_x\":%.3f,", gpsdata->attitude.acc_x);
1403     if (isnan(gpsdata->attitude.acc_y) == 0)
1404         (void)snprintf(reply + strlen(reply),
1405                        replylen - strlen(reply),
1406                        "\"acc_y\":%.3f,", gpsdata->attitude.acc_y);
1407     if (isnan(gpsdata->attitude.acc_z) == 0)
1408         (void)snprintf(reply + strlen(reply),
1409                        replylen - strlen(reply),
1410                        "\"acc_z\":%.3f,", gpsdata->attitude.acc_z);
1411
1412     if (isnan(gpsdata->attitude.gyro_x) == 0)
1413         (void)snprintf(reply + strlen(reply),
1414                        replylen - strlen(reply),
1415                        "\"gyro_x\":%.3f,", gpsdata->attitude.gyro_x);
1416     if (isnan(gpsdata->attitude.gyro_y) == 0)
1417         (void)snprintf(reply + strlen(reply),
1418                        replylen - strlen(reply),
1419                        "\"gyro_y\":%.3f,", gpsdata->attitude.gyro_y);
1420
1421     if (isnan(gpsdata->attitude.temp) == 0)
1422         (void)snprintf(reply + strlen(reply),
1423                        replylen - strlen(reply),
1424                        "\"temp\":%.3f,", gpsdata->attitude.temp);
1425     if (isnan(gpsdata->attitude.depth) == 0)
1426         (void)snprintf(reply + strlen(reply),
1427                        replylen - strlen(reply),
1428                        "\"depth\":%.3f,", gpsdata->attitude.depth);
1429
1430     if (reply[strlen(reply) - 1] == ',')
1431         reply[strlen(reply) - 1] = '\0';        /* trim trailing comma */
1432     (void)strlcat(reply, "}\r\n", replylen);
1433 }
1434 #endif /* COMPASS_ENABLE */
1435
1436
1437 /* gpsd_json.c ends here */