Devuan fork of gpsd
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

3755 lines
127 KiB

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