/src/gpsd/gpsd-3.27.6~dev/gpsd/pseudonmea.c
Line | Count | Source |
1 | | /* |
2 | | * Create pseudo NMEA (and AIVDM) messaegs. |
3 | | * |
4 | | * This file is Copyright 2010 by the GPSD project |
5 | | * SPDX-License-Identifier: BSD-2-clause |
6 | | */ |
7 | | |
8 | | #include "../include/gpsd_config.h" // must be before all includes |
9 | | |
10 | | #include <math.h> |
11 | | #include <stdio.h> |
12 | | #include <stdlib.h> |
13 | | #include <string.h> |
14 | | #include <time.h> |
15 | | |
16 | | #include "../include/gpsd.h" |
17 | | #include "../include/strfuncs.h" |
18 | | |
19 | | /* |
20 | | * Support for generic binary drivers. These functions dump NMEA for passing |
21 | | * to the client in raw mode. They assume that (a) the public gps.h structure |
22 | | * members are in a valid state, (b) that the private members hours, minutes, |
23 | | * and seconds have also been filled in, (c) that if the private member |
24 | | * mag_var is not NAN it is a magnetic variation in degrees that should be |
25 | | * passed on, and (d) if the private member separation does not have the |
26 | | * value NAN, it is a valid WGS84 geoidal separation in meters for the fix. |
27 | | */ |
28 | | |
29 | 0 | #define BUF_SZ 20 |
30 | | /* decimal degrees to GPS-style, degrees first followed by minutes |
31 | | * |
32 | | * Return: pointer to passed in buffer (buf) with string |
33 | | */ |
34 | | static char *degtodm_str(double angle, const char *fmt, char *buf) |
35 | 0 | { |
36 | 0 | if (0 == isfinite(angle)) { |
37 | 0 | buf[0] = '\0'; |
38 | 0 | } else { |
39 | 0 | double fraction, integer; |
40 | |
|
41 | 0 | angle = fabs(angle); |
42 | 0 | fraction = modf(angle, &integer); |
43 | 0 | (void)snprintf(buf, BUF_SZ, fmt, floor(angle) * 100 + fraction * 60); |
44 | 0 | } |
45 | 0 | return buf; |
46 | 0 | } |
47 | | |
48 | | /* format a float/lon/alt into a string, handle NAN, INFINITE |
49 | | * |
50 | | * Return: pointer to passed in buffer (buf) with string |
51 | | */ |
52 | | static char *f_str(double f, const char *fmt, char *buf) |
53 | 0 | { |
54 | 0 | if (0 == isfinite(f)) { |
55 | 0 | buf[0] = '\0'; |
56 | 0 | } else { |
57 | 0 | (void)snprintf(buf, BUF_SZ, fmt, f); |
58 | 0 | } |
59 | 0 | return buf; |
60 | 0 | } |
61 | | |
62 | | // make size of time string buffer large to shut up paranoid cc's |
63 | | #define TIMESTR_SZ 48 |
64 | | |
65 | | /* convert UTC to time str (hh:mm:ss.ss) and tm |
66 | | * |
67 | | * Return: pointer to passed in buffer (buf) with string |
68 | | */ |
69 | | static void utc_to_hhmmss(timespec_t time, char *buf, ssize_t buf_sz, |
70 | | struct tm *tm) |
71 | 0 | { |
72 | 0 | time_t integer; |
73 | 0 | long fractional; |
74 | |
|
75 | 0 | if (0 >= time.tv_sec) { |
76 | 0 | buf[0] = '\0'; |
77 | 0 | return; |
78 | 0 | } |
79 | | |
80 | 0 | integer = time.tv_sec; |
81 | | // round to 100ths |
82 | 0 | fractional = (time.tv_nsec + 5000000L) / 10000000L; |
83 | 0 | if (99 < fractional) { |
84 | 0 | integer++; |
85 | 0 | fractional = 0; |
86 | 0 | } |
87 | |
|
88 | 0 | if (NULL == gmtime_r(&integer, tm)) { |
89 | | // WTF? |
90 | 0 | buf[0] = '\0'; |
91 | 0 | return; |
92 | 0 | } |
93 | | |
94 | | |
95 | 0 | (void)snprintf(buf, buf_sz, "%02d%02d%02d.%02ld", |
96 | 0 | tm->tm_hour, tm->tm_min, tm->tm_sec, fractional); |
97 | 0 | return; |
98 | 0 | } |
99 | | |
100 | | /* dbl_to_str() -- convert a double to a string in bufp |
101 | | * |
102 | | * Return: void |
103 | | */ |
104 | | static void dbl_to_str(const char *fmt, double val, char *bufp, size_t len, |
105 | | const char *suffix) |
106 | 0 | { |
107 | 0 | if (0 == isfinite(val)) { |
108 | 0 | (void)strlcat(bufp, ",", len); |
109 | 0 | } else { |
110 | 0 | str_appendf(bufp, len, fmt, val); |
111 | 0 | } |
112 | 0 | if (NULL != suffix) { |
113 | 0 | str_appendf(bufp, len, "%s", suffix); |
114 | 0 | } |
115 | 0 | } |
116 | | |
117 | | /* GPS Quality Indicator values for xxGGA |
118 | | * Almost, not quite, the same as STATUS_* from gps.h */ |
119 | 0 | #define FIX_QUALITY_INVALID 0 |
120 | 0 | #define FIX_QUALITY_GPS 1 |
121 | 0 | #define FIX_QUALITY_DGPS 2 |
122 | | #define FIX_QUALITY_PPS 3 |
123 | 0 | #define FIX_QUALITY_RTK 4 |
124 | 0 | #define FIX_QUALITY_RTK_FLT 5 |
125 | 0 | #define FIX_QUALITY_DR 6 |
126 | 0 | #define FIX_QUALITY_MANUAL 7 |
127 | 0 | #define FIX_QUALITY_SIMULATED 8 |
128 | | |
129 | | /* |
130 | | * If possible, create a $GPGGA message (full time, position and fix data) |
131 | | * from the fix data, taking care about the validity of subsidiary fields |
132 | | * like HDOP and altitude. |
133 | | * |
134 | | * looks like this is only called from net_ntrip.c and nmea_tpv_dump() |
135 | | * |
136 | | * Return: length of generated GGA string at bufp |
137 | | */ |
138 | | int gpsd_position_fix_dump(struct gps_device_t *session, |
139 | | char bufp[], size_t len) |
140 | 0 | { |
141 | 0 | struct tm tm; |
142 | 0 | char time_str[TIMESTR_SZ]; |
143 | 0 | unsigned char fixquality; |
144 | 0 | char lat_str[BUF_SZ]; |
145 | 0 | char lon_str[BUF_SZ]; |
146 | 0 | int blen = 0; // length, so far, of string in bufp |
147 | |
|
148 | 0 | *bufp = '\0'; |
149 | | |
150 | | // Maybe send an empty $GGA on no fix? |
151 | 0 | if (MODE_NO_FIX >= session->gpsdata.fix.mode) { |
152 | 0 | return 0; |
153 | 0 | } |
154 | | |
155 | 0 | utc_to_hhmmss(session->gpsdata.fix.time, time_str, sizeof(time_str), &tm); |
156 | 0 | switch(session->gpsdata.fix.status) { |
157 | 0 | case STATUS_UNK: |
158 | | // should not happen as we send no GGA when NO_FIX |
159 | 0 | FALLTHROUGH |
160 | 0 | case STATUS_GPS: |
161 | 0 | fixquality = FIX_QUALITY_GPS; |
162 | 0 | break; |
163 | 0 | case STATUS_DGPS: |
164 | 0 | fixquality = FIX_QUALITY_DGPS; |
165 | 0 | break; |
166 | 0 | case STATUS_RTK_FIX: |
167 | 0 | fixquality = FIX_QUALITY_RTK; |
168 | 0 | break; |
169 | 0 | case STATUS_RTK_FLT: |
170 | 0 | fixquality = FIX_QUALITY_RTK_FLT; |
171 | 0 | break; |
172 | 0 | case STATUS_DR: |
173 | 0 | fixquality = FIX_QUALITY_DR; |
174 | 0 | break; |
175 | 0 | case STATUS_GNSSDR: |
176 | | /* NMEA doesn't know GNSS+DR from DR, so report GNSS fix |
177 | | * What does it even mean? */ |
178 | 0 | fixquality = FIX_QUALITY_GPS; |
179 | 0 | break; |
180 | 0 | case STATUS_TIME: |
181 | 0 | fixquality = FIX_QUALITY_MANUAL; |
182 | 0 | break; |
183 | 0 | case STATUS_SIM: |
184 | 0 | fixquality = FIX_QUALITY_SIMULATED; |
185 | 0 | break; |
186 | 0 | default: |
187 | 0 | fixquality = FIX_QUALITY_INVALID; |
188 | 0 | break; |
189 | 0 | } |
190 | | |
191 | | /* Use lat/lon precision .7 because u-blox ZED-F9P does. |
192 | | * Use lat 4 digits of integer, lon 5 digits, because: |
193 | | * http://sapos.geonord-od.de:2101/ wants that minimum */ |
194 | 0 | blen = snprintf(bufp, len, |
195 | 0 | "$GPGGA,%s,%s,%c,%s,%c,%d,%02d,", |
196 | 0 | time_str, |
197 | 0 | degtodm_str(session->gpsdata.fix.latitude, "%012.7f", |
198 | 0 | lat_str), |
199 | 0 | ((session->gpsdata.fix.latitude > 0) ? 'N' : 'S'), |
200 | 0 | degtodm_str(session->gpsdata.fix.longitude, "%013.7f", |
201 | 0 | lon_str), |
202 | 0 | ((session->gpsdata.fix.longitude > 0) ? 'E' : 'W'), |
203 | 0 | fixquality, |
204 | 0 | session->gpsdata.satellites_used); |
205 | 0 | dbl_to_str("%.2f,", session->gpsdata.dop.hdop, bufp + blen, len - blen, |
206 | 0 | NULL); |
207 | 0 | dbl_to_str("%.2f,", session->gpsdata.fix.altMSL, bufp, len, "M,"); |
208 | 0 | dbl_to_str("%.3f,", session->gpsdata.fix.geoid_sep, bufp, len, "M,"); |
209 | | // Age of correction data, and Differential base station ID |
210 | 0 | if (0.0 > session->gpsdata.fix.dgps_age || |
211 | 0 | 0 > session->gpsdata.fix.dgps_station) { |
212 | | // n/a |
213 | 0 | (void)strlcat(bufp, ",", len); |
214 | 0 | } else { |
215 | 0 | char dgps_str[BUF_SZ]; |
216 | | |
217 | | // Warning, u-blox dgps_station may be 16 bits. |
218 | 0 | (void)snprintf(dgps_str, BUF_SZ, "%.1f,%04d", |
219 | 0 | session->gpsdata.fix.dgps_age, |
220 | 0 | session->gpsdata.fix.dgps_station); |
221 | 0 | } |
222 | 0 | nmea_add_checksum(bufp); |
223 | |
|
224 | 0 | return( strnlen(bufp, len)); |
225 | 0 | } |
226 | | |
227 | | |
228 | | static void gpsd_transit_fix_dump(struct gps_device_t *session, |
229 | | char bufp[], size_t len) |
230 | 0 | { |
231 | 0 | char time_str[TIMESTR_SZ]; |
232 | 0 | char time2_str[TIMESTR_SZ]; |
233 | 0 | char lat_str[BUF_SZ]; |
234 | 0 | char lon_str[BUF_SZ]; |
235 | 0 | char speed_str[BUF_SZ]; |
236 | 0 | char track_str[BUF_SZ]; |
237 | 0 | char var_str[BUF_SZ]; |
238 | 0 | char *var_dir = ""; |
239 | 0 | struct tm tm; |
240 | 0 | char valid; |
241 | |
|
242 | 0 | utc_to_hhmmss(session->gpsdata.fix.time, time_str, sizeof(time_str), &tm); |
243 | 0 | if ('\0' == time_str[0]) { |
244 | 0 | time2_str[0] = '\0'; |
245 | 0 | } else { |
246 | 0 | tm.tm_mon++; |
247 | 0 | tm.tm_year %= 100; |
248 | |
|
249 | 0 | (void)snprintf(time2_str, sizeof(time2_str), |
250 | 0 | "%02d%02d%02d", |
251 | 0 | tm.tm_mday, tm.tm_mon, tm.tm_year); |
252 | 0 | } |
253 | 0 | if (0 == isfinite(session->gpsdata.fix.magnetic_var)) { |
254 | 0 | var_str[0] = '\0'; |
255 | 0 | } else { |
256 | 0 | f_str(session->gpsdata.fix.magnetic_var, "%.1f", var_str), |
257 | 0 | var_dir = (session->gpsdata.fix.magnetic_var > 0) ? "E" : "W"; |
258 | 0 | } |
259 | |
|
260 | 0 | if (MODE_2D > session->gpsdata.fix.mode) { |
261 | 0 | valid = 'V'; // Status: Warning |
262 | 0 | } else { |
263 | 0 | valid = 'A'; // Status: Valid |
264 | 0 | } |
265 | | |
266 | | // FIXME! add in "Mode Indicator" after var_dir |
267 | | // Use lat %011.7f and lon %12.7f, because u-blox ZED-F9P does. |
268 | 0 | (void)snprintf(bufp, len, |
269 | 0 | "$GPRMC,%s,%c,%s,%c,%s,%c,%s,%s,%s,%s,%s", |
270 | 0 | time_str, |
271 | 0 | valid, |
272 | 0 | degtodm_str(session->gpsdata.fix.latitude, "%011.7f", |
273 | 0 | lat_str), |
274 | 0 | ((session->gpsdata.fix.latitude > 0) ? 'N' : 'S'), |
275 | 0 | degtodm_str(session->gpsdata.fix.longitude, "%012.7f", |
276 | 0 | lon_str), |
277 | 0 | ((session->gpsdata.fix.longitude > 0) ? 'E' : 'W'), |
278 | 0 | f_str(session->gpsdata.fix.speed * MPS_TO_KNOTS, "%.4f", |
279 | 0 | speed_str), |
280 | 0 | f_str(session->gpsdata.fix.track, "%.3f", track_str), |
281 | 0 | time2_str, |
282 | 0 | var_str, var_dir); |
283 | 0 | nmea_add_checksum(bufp); |
284 | 0 | } |
285 | | |
286 | | static void gpsd_binary_satellite_dump(struct gps_device_t *session, |
287 | | char bufp[], size_t len) |
288 | 0 | { |
289 | 0 | unsigned i; // index into skyview[] |
290 | 0 | unsigned j; // index into GPGSV |
291 | 0 | char *bufp2 = bufp; |
292 | 0 | unsigned satellites_visible = 0; |
293 | 0 | bufp[0] = '\0'; |
294 | |
|
295 | 0 | if (MAXCHANNELS < session->gpsdata.satellites_visible) { |
296 | 0 | GPSD_LOG(LOG_WARN, &session->context->errout, |
297 | 0 | "gpsd_binary_satellite_dump() too many satellites %d\n", |
298 | 0 | session->gpsdata.satellites_visible); |
299 | 0 | session->gpsdata.satellites_visible = MAXCHANNELS; |
300 | 0 | } |
301 | | // check skyview[] for valid sats first |
302 | 0 | for (i = 0; i < session->gpsdata.satellites_visible; i++) { |
303 | 0 | if (1 > session->gpsdata.skyview[i].PRN) { |
304 | | // bad PRN, ignore |
305 | 0 | continue; |
306 | 0 | } |
307 | 0 | if (0 == isfinite(session->gpsdata.skyview[i].elevation) || |
308 | 0 | 90 < fabs(session->gpsdata.skyview[i].elevation)) { |
309 | | // bad elevation, ignore |
310 | 0 | continue; |
311 | 0 | } |
312 | 0 | if (0 == isfinite(session->gpsdata.skyview[i].azimuth) || |
313 | 0 | 0 > session->gpsdata.skyview[i].azimuth || |
314 | 0 | 359 < session->gpsdata.skyview[i].azimuth) { |
315 | | // bad azimuth, ignore |
316 | 0 | continue; |
317 | 0 | } |
318 | 0 | satellites_visible++; |
319 | 0 | } |
320 | 0 | for (i = 0, j= 0; i < session->gpsdata.satellites_visible; i++) { |
321 | 0 | if (1 > session->gpsdata.skyview[i].PRN) { |
322 | | // bad prn, skip |
323 | 0 | continue; |
324 | 0 | } |
325 | 0 | if (0 == isfinite(session->gpsdata.skyview[i].elevation) || |
326 | 0 | 90 < fabs(session->gpsdata.skyview[i].elevation)) { |
327 | | // bad elevation, ignore |
328 | 0 | continue; |
329 | 0 | } |
330 | 0 | if (0 == isfinite(session->gpsdata.skyview[i].azimuth) || |
331 | 0 | 0 > session->gpsdata.skyview[i].azimuth || |
332 | 0 | 359 < session->gpsdata.skyview[i].azimuth) { |
333 | | // bad azimuth, ignore |
334 | 0 | continue; |
335 | 0 | } |
336 | 0 | if (0 == isfinite(session->gpsdata.skyview[i].ss)) { |
337 | | // bad snr, ignore |
338 | 0 | continue; |
339 | 0 | } |
340 | 0 | if (0 == (j % 4)) { |
341 | 0 | bufp2 = bufp + strlen(bufp); |
342 | 0 | str_appendf(bufp, len, |
343 | 0 | "$GPGSV,%d,%d,%02d", |
344 | 0 | ((satellites_visible - 1) / 4) + 1, (j / 4) + 1, |
345 | 0 | satellites_visible); |
346 | 0 | } |
347 | 0 | str_appendf(bufp, len, ",%02d,%02.0f,%03.0f,%02.0f", |
348 | 0 | session->gpsdata.skyview[i].PRN, |
349 | 0 | session->gpsdata.skyview[i].elevation, |
350 | 0 | session->gpsdata.skyview[i].azimuth, |
351 | 0 | session->gpsdata.skyview[i].ss); |
352 | |
|
353 | 0 | if (3 == (j % 4) || |
354 | 0 | (satellites_visible - 1) == j) { |
355 | 0 | nmea_add_checksum(bufp2); |
356 | 0 | } |
357 | 0 | j++; |
358 | 0 | } |
359 | |
|
360 | 0 | #ifdef ZODIAC_ENABLE |
361 | 0 | if (ZODIAC_PACKET == session->lexer.type && |
362 | 0 | 0 != session->driver.zodiac.Zs[0]) { |
363 | 0 | bufp2 = bufp + strlen(bufp); |
364 | 0 | str_appendf(bufp, len, "$PRWIZCH"); |
365 | 0 | for (i = 0; i < ZODIAC_CHANNELS; i++) { |
366 | 0 | str_appendf(bufp, len, |
367 | 0 | ",%02u,%X", |
368 | 0 | session->driver.zodiac.Zs[i], |
369 | 0 | session->driver.zodiac.Zv[i] & 0x0f); |
370 | 0 | } |
371 | 0 | nmea_add_checksum(bufp2); |
372 | 0 | } |
373 | 0 | #endif // ZODIAC_ENABLE |
374 | 0 | } |
375 | | |
376 | | static void gpsd_binary_quality_dump(struct gps_device_t *session, |
377 | | char bufp[], size_t len) |
378 | 0 | { |
379 | 0 | char *bufp2; |
380 | 0 | bufp[0] = '\0'; |
381 | |
|
382 | 0 | if (NULL != session->device_type) { |
383 | 0 | unsigned i, j; |
384 | 0 | unsigned max_channels = session->device_type->channels; |
385 | 0 | int mode = session->gpsdata.fix.mode; |
386 | | |
387 | | // GPGSA commonly has exactly 12 channels, enforce that as a MAX |
388 | 0 | if (12 < max_channels) { |
389 | | // what to do with the excess channels? |
390 | 0 | max_channels = 12; |
391 | 0 | } |
392 | |
|
393 | 0 | bufp2 = bufp + strlen(bufp); |
394 | | // fix.mode can be zero, legal values are only 1, 2 or 3 |
395 | 0 | if (MODE_NO_FIX > mode) { |
396 | 0 | mode = MODE_NO_FIX; // 1 |
397 | 0 | } else if (MODE_3D < mode) { |
398 | 0 | mode = MODE_3D; // 3 |
399 | 0 | } |
400 | | // We don't know if we are manual (M) or automatic (A), force "A". |
401 | 0 | (void)snprintf(bufp, len, "$GPGSA,%c,%d,", 'A', mode); |
402 | 0 | j = 0; |
403 | 0 | for (i = 0; i < max_channels; i++) { |
404 | 0 | if (true == session->gpsdata.skyview[i].used){ |
405 | 0 | str_appendf(bufp, len, "%d,", session->gpsdata.skyview[i].PRN); |
406 | 0 | j++; |
407 | 0 | } |
408 | 0 | } |
409 | 0 | for (i = j; i < max_channels; i++) { |
410 | | // fill out the empty slots |
411 | 0 | (void)strlcat(bufp, ",", len); |
412 | 0 | } |
413 | 0 | if (MODE_NO_FIX == session->gpsdata.fix.mode) { |
414 | 0 | (void)strlcat(bufp, ",,,", len); |
415 | 0 | } else { |
416 | | // output the DOPs, NaN as blanks |
417 | 0 | if (0 != isfinite(session->gpsdata.dop.pdop)) { |
418 | 0 | str_appendf(bufp, len, "%.1f,", session->gpsdata.dop.pdop); |
419 | 0 | } else { |
420 | 0 | (void)strlcat(bufp, ",", len); |
421 | 0 | } |
422 | 0 | if (0 != isfinite(session->gpsdata.dop.hdop)) { |
423 | 0 | str_appendf(bufp, len, "%.1f,", session->gpsdata.dop.hdop); |
424 | 0 | } else { |
425 | 0 | (void)strlcat(bufp, ",", len); |
426 | 0 | } |
427 | 0 | if (0 != isfinite(session->gpsdata.dop.vdop)) { |
428 | 0 | str_appendf(bufp, len, "%.1f*", session->gpsdata.dop.vdop); |
429 | 0 | } else { |
430 | 0 | (void)strlcat(bufp, "*", len); |
431 | 0 | } |
432 | 0 | } |
433 | 0 | nmea_add_checksum(bufp2); |
434 | 0 | } |
435 | | |
436 | | /* create $GPGBS if we have time, epx and epy. Optional epv. |
437 | | * Not really kosher, not have enough info to compute the RAIM |
438 | | */ |
439 | 0 | if (0 != isfinite(session->gpsdata.fix.epx) && |
440 | 0 | 0 != isfinite(session->gpsdata.fix.epy) && |
441 | 0 | 0 != session->gpsdata.fix.time.tv_sec) { |
442 | |
|
443 | 0 | struct tm tm; |
444 | 0 | char time_str[TIMESTR_SZ]; |
445 | 0 | char epv_str[BUF_SZ]; |
446 | |
|
447 | 0 | (void)utc_to_hhmmss(session->gpsdata.fix.time, |
448 | 0 | time_str, sizeof(time_str), &tm); |
449 | |
|
450 | 0 | bufp2 = bufp + strlen(bufp); |
451 | 0 | str_appendf(bufp, len, |
452 | 0 | "$GPGBS,%s,%.3f,%.3f,%s,,,,", |
453 | 0 | time_str, |
454 | 0 | session->gpsdata.fix.epx, |
455 | 0 | session->gpsdata.fix.epy, |
456 | 0 | f_str(session->gpsdata.fix.epv, "%.3f", epv_str)); |
457 | 0 | nmea_add_checksum(bufp2); |
458 | 0 | } |
459 | 0 | } |
460 | | |
461 | | // Dump $GPZDA if we have time and a fix |
462 | | // return length added |
463 | | static ssize_t gpsd_binary_time_dump(struct gps_device_t *session, |
464 | | char bufp[], size_t len) |
465 | 0 | { |
466 | |
|
467 | 0 | ssize_t blen = 0; |
468 | |
|
469 | 0 | if (MODE_NO_FIX < session->gpsdata.fix.mode && |
470 | 0 | 0 <= session->gpsdata.fix.time.tv_sec) { |
471 | | // shut up clang about uninitialized |
472 | 0 | struct tm tm = {0}; |
473 | 0 | char time_str[TIMESTR_SZ]; |
474 | |
|
475 | 0 | utc_to_hhmmss(session->gpsdata.fix.time, time_str, sizeof(time_str), |
476 | 0 | &tm); |
477 | | /* There used to be confusion, but we now know NMEA times are UTC, |
478 | | * when available. */ |
479 | 0 | blen = snprintf(bufp, len, |
480 | 0 | "$GPZDA,%s,%02d,%02d,%04d,00,00", |
481 | 0 | time_str, |
482 | 0 | tm.tm_mday, |
483 | 0 | tm.tm_mon + 1, |
484 | 0 | tm.tm_year + 1900); |
485 | 0 | nmea_add_checksum(bufp); |
486 | 0 | blen += 5; |
487 | 0 | } |
488 | 0 | return blen; |
489 | 0 | } |
490 | | |
491 | | static void gpsd_binary_almanac_dump(struct gps_device_t *session, |
492 | | char bufp[], size_t len) |
493 | 0 | { |
494 | 0 | if (session->gpsdata.subframe.is_almanac) { |
495 | 0 | (void)snprintf(bufp, len, |
496 | 0 | "$GPALM,1,1,%02d,%04d,%02x,%04x,%02x,%04x,%04x,%05x," |
497 | 0 | "%06x,%06x,%06x,%03x,%03x", |
498 | 0 | (int)session->gpsdata.subframe.sub5.almanac.sv, |
499 | 0 | (int)session->context->gps_week % 1024, |
500 | 0 | (unsigned int)session->gpsdata.subframe.sub5.almanac.svh, |
501 | 0 | (unsigned int)session->gpsdata.subframe.sub5.almanac.e, |
502 | 0 | (unsigned int)session->gpsdata.subframe.sub5.almanac.toa, |
503 | 0 | (unsigned int)session->gpsdata.subframe.sub5.almanac.deltai, |
504 | 0 | (unsigned int)session->gpsdata.subframe.sub5.almanac.Omegad, |
505 | 0 | (unsigned int)session->gpsdata.subframe.sub5.almanac.sqrtA, |
506 | 0 | (unsigned int)session->gpsdata.subframe.sub5.almanac.omega, |
507 | 0 | (unsigned int)session->gpsdata.subframe.sub5.almanac.Omega0, |
508 | 0 | (unsigned int)session->gpsdata.subframe.sub5.almanac.M0, |
509 | 0 | (unsigned int)session->gpsdata.subframe.sub5.almanac.af0, |
510 | 0 | (unsigned int)session->gpsdata.subframe.sub5.almanac.af1); |
511 | 0 | nmea_add_checksum(bufp); |
512 | 0 | } |
513 | 0 | } |
514 | | |
515 | | #ifdef AIVDM_ENABLE |
516 | | |
517 | 0 | #define GETLEFT(a) (((a%6) == 0) ? 0 : (6 - (a%6))) |
518 | | |
519 | | static void gpsd_binary_ais_dump(struct gps_device_t *session, |
520 | | char bufp[], size_t len) |
521 | 0 | { |
522 | 0 | char type[8] = "!AIVDM"; |
523 | 0 | unsigned char data[256]; |
524 | 0 | unsigned int msg1, msg2; |
525 | 0 | char numc[4]; |
526 | 0 | char channel; |
527 | 0 | unsigned int left; |
528 | 0 | unsigned int datalen; |
529 | 0 | unsigned int offset; |
530 | |
|
531 | 0 | channel = 'A'; |
532 | 0 | if ('B' == session->driver.aivdm.ais_channel) { |
533 | 0 | channel = 'B'; |
534 | 0 | } |
535 | |
|
536 | 0 | memset(data, 0, sizeof(data)); |
537 | 0 | datalen = ais_binary_encode(&session->gpsdata.ais, &data[0], 0); |
538 | 0 | if ((6 * 60) < datalen) { |
539 | 0 | static int number1 = 0; |
540 | |
|
541 | 0 | msg1 = datalen / (6 * 60); |
542 | 0 | if (0 != (datalen % (6 * 60))) { |
543 | 0 | msg1 += 1; |
544 | 0 | } |
545 | 0 | numc[0] = '0' + (char)(number1 & 0x0f); |
546 | 0 | numc[1] = '\0'; |
547 | 0 | number1 += 1; |
548 | 0 | if (9 < number1) { |
549 | 0 | number1 = 0; |
550 | 0 | } |
551 | 0 | offset = 0; |
552 | 0 | for (msg2 = 1; msg2 <= msg1; msg2++) { |
553 | 0 | unsigned char old; |
554 | |
|
555 | 0 | old = '\0'; |
556 | 0 | if (60 < strlen((char *)&data[(msg2 - 1) * 60])) { |
557 | 0 | old = data[(msg2 - 0) * 60]; |
558 | 0 | data[(msg2 - 0) * 60] = '\0'; |
559 | 0 | } |
560 | 0 | if ((6 * 60) <= datalen) { |
561 | 0 | left = 0; |
562 | 0 | datalen -= 6 * 60; |
563 | 0 | } else { |
564 | 0 | left = GETLEFT(datalen); |
565 | 0 | } |
566 | 0 | (void)snprintf(&bufp[offset], len-offset, |
567 | 0 | "%s,%u,%u,%s,%c,%s,%u", |
568 | 0 | type, |
569 | 0 | msg1, |
570 | 0 | msg2, |
571 | 0 | numc, |
572 | 0 | channel, |
573 | 0 | (char *)&data[(msg2-1)*60], |
574 | 0 | left); |
575 | |
|
576 | 0 | nmea_add_checksum(&bufp[offset]); |
577 | 0 | if ((unsigned char)'\0' != old) { |
578 | 0 | data[(msg2 - 0) * 60] = old; |
579 | 0 | } |
580 | 0 | offset = (unsigned int)strlen(bufp); |
581 | 0 | } |
582 | 0 | } else if (0 < datalen) { |
583 | 0 | msg1 = 1; |
584 | 0 | msg2 = 1; |
585 | 0 | numc[0] = '\0'; |
586 | 0 | left = GETLEFT(datalen); |
587 | 0 | (void)snprintf(bufp, len, |
588 | 0 | "%s,%u,%u,%s,%c,%s,%u", |
589 | 0 | type, |
590 | 0 | msg1, |
591 | 0 | msg2, |
592 | 0 | numc, |
593 | 0 | channel, |
594 | 0 | (char *)data, |
595 | 0 | left); |
596 | |
|
597 | 0 | nmea_add_checksum(bufp); |
598 | 0 | } |
599 | |
|
600 | 0 | if (24 == session->gpsdata.ais.type) { |
601 | 0 | msg1 = 1; |
602 | 0 | msg2 = 1; |
603 | 0 | numc[0] = '\0'; |
604 | |
|
605 | 0 | memset(data, 0, sizeof(data)); |
606 | 0 | datalen = ais_binary_encode(&session->gpsdata.ais, &data[0], 1); |
607 | 0 | if (0 < datalen) { |
608 | 0 | left = GETLEFT(datalen); |
609 | 0 | offset = (unsigned int)strlen(bufp); |
610 | 0 | (void)snprintf(&bufp[offset], len-offset, |
611 | 0 | "%s,%u,%u,%s,%c,%s,%u", |
612 | 0 | type, |
613 | 0 | msg1, |
614 | 0 | msg2, |
615 | 0 | numc, |
616 | 0 | channel, |
617 | 0 | (char *)data, |
618 | 0 | left); |
619 | 0 | nmea_add_checksum(bufp+offset); |
620 | 0 | } |
621 | 0 | } |
622 | 0 | } |
623 | | #endif // AIVDM_ENABLE |
624 | | |
625 | | /* nmea_tpv_dump() |
626 | | * dump current fix as NMEA |
627 | | * |
628 | | * Return: void |
629 | | */ |
630 | | void nmea_tpv_dump(struct gps_device_t *session, |
631 | | char bufp[], size_t len) |
632 | 0 | { |
633 | 0 | ssize_t blen = 0; |
634 | 0 | bufp[0] = '\0'; |
635 | | |
636 | | // maybe all we need is REPORT_IS? |
637 | 0 | if (0 != (session->gpsdata.set & (TIME_SET | REPORT_IS))) { |
638 | 0 | blen = gpsd_binary_time_dump(session, bufp, len); |
639 | 0 | } |
640 | 0 | if (0 != (session->gpsdata.set & (LATLON_SET | MODE_SET | REPORT_IS))) { |
641 | 0 | blen += gpsd_position_fix_dump(session, bufp + blen, len - blen); |
642 | 0 | gpsd_transit_fix_dump(session, bufp + blen, len - blen); |
643 | 0 | } |
644 | 0 | if (0 != (session->gpsdata.set & |
645 | 0 | (MODE_SET | DOP_SET | USED_IS | HERR_SET | REPORT_IS))) { |
646 | 0 | gpsd_binary_quality_dump(session, bufp + strlen(bufp), |
647 | 0 | len - strlen(bufp)); |
648 | 0 | } |
649 | 0 | } |
650 | | |
651 | | void nmea_sky_dump(struct gps_device_t *session, |
652 | | char bufp[], size_t len) |
653 | 0 | { |
654 | 0 | bufp[0] = '\0'; |
655 | 0 | if (0 != (session->gpsdata.set & SATELLITE_SET)) { |
656 | 0 | gpsd_binary_satellite_dump(session, bufp + strlen(bufp), |
657 | 0 | len - strlen(bufp)); |
658 | 0 | } |
659 | 0 | } |
660 | | |
661 | | // FIXME: this shim should go away... |
662 | | void nmea_subframe_dump(struct gps_device_t *session, |
663 | | char bufp[], size_t len) |
664 | 0 | { |
665 | 0 | bufp[0] = '\0'; |
666 | 0 | if (0 != (session->gpsdata.set & SUBFRAME_SET)) { |
667 | 0 | size_t buflen = strnlen(bufp, len); |
668 | 0 | gpsd_binary_almanac_dump(session, bufp + buflen, len - buflen); |
669 | 0 | } |
670 | 0 | } |
671 | | |
672 | | #ifdef AIVDM_ENABLE |
673 | | void nmea_ais_dump(struct gps_device_t *session, |
674 | | char bufp[], size_t len) |
675 | 0 | { |
676 | 0 | bufp[0] = '\0'; |
677 | 0 | if (0 != (session->gpsdata.set & AIS_SET)) { |
678 | 0 | gpsd_binary_ais_dump(session, bufp + strlen(bufp), len - strlen(bufp)); |
679 | 0 | } |
680 | 0 | } |
681 | | #endif // AIVDM_ENABLE |
682 | | |
683 | | // vim: set expandtab shiftwidth=4 |