/src/gpsd/gpsd-3.27.6~dev/drivers/driver_nmea2000.c
Line | Count | Source |
1 | | /* |
2 | | * NMEA2000 over CAN. |
3 | | * |
4 | | * NMEA2000 is proprietary and the doc is not public. |
5 | | * Much of this code is reverse engineered or built from |
6 | | * the sparse doc some vendors provide on their interpretation |
7 | | * of the specification. |
8 | | * |
9 | | * Here is one good source of reverse engineered info: |
10 | | * https://github.com/canboat/canboat |
11 | | * https://canboat.github.io/canboat/canboat.html |
12 | | * |
13 | | * Message contents can be had from canboat/analyzer: |
14 | | * analyzer -explain |
15 | | * |
16 | | * This file is Copyright by the GPSD project |
17 | | * SPDX-License-Identifier: BSD-2-clause |
18 | | */ |
19 | | |
20 | | #include "../include/gpsd_config.h" // must be before all includes |
21 | | |
22 | | #if defined(NMEA2000_ENABLE) |
23 | | |
24 | | #include <ctype.h> |
25 | | #include <errno.h> // for strerror(errno), errno) |
26 | | #include <fcntl.h> |
27 | | #include <linux/can.h> // for struct can_frame |
28 | | #include <linux/can/error.h> // for CAN_ERR_BUSOFF, etc. |
29 | | #include <linux/can/raw.h> |
30 | | #include <math.h> |
31 | | #include <net/if.h> |
32 | | #include <stdarg.h> |
33 | | #include <stdbool.h> |
34 | | #include <stdio.h> |
35 | | #include <stdlib.h> |
36 | | #include <string.h> |
37 | | #include <sys/ioctl.h> |
38 | | #include <sys/socket.h> |
39 | | #include <time.h> |
40 | | #include <unistd.h> |
41 | | |
42 | | #include "../include/gpsd.h" |
43 | | #include "../include/libgps.h" |
44 | | #include "../include/driver_nmea2000.h" |
45 | | #include "../include/bits.h" |
46 | | #include "../include/timespec.h" |
47 | | |
48 | | |
49 | | #define LOG_FILE 1 |
50 | 0 | #define NMEA2000_NETS 4 |
51 | | /* NMEA 2000 source addr (SA) is a byte, |
52 | | * but 254 is "request for address claim" |
53 | | * and 255 is broadcast address. So 254 is the number of addresses possible, |
54 | | * and 253 the highest addresses number. */ |
55 | 0 | #define NMEA2000_ADDRS 254 |
56 | | #define CAN_NAMELEN 32 |
57 | 0 | #define MIN(a,b) ((a < b) ? a : b) |
58 | | |
59 | | #define NMEA2000_DEBUG_AIS 0 |
60 | | |
61 | | static struct gps_device_t *nmea2000_units[NMEA2000_NETS][NMEA2000_ADDRS]; |
62 | | static char can_interface_name[NMEA2000_NETS][CAN_NAMELEN + 1]; |
63 | | |
64 | | typedef struct PGN { |
65 | | unsigned int pgn; |
66 | | char fast; |
67 | | char type; |
68 | | gps_mask_t (* func)(struct gps_device_t *session); |
69 | | const char *name; |
70 | | } PGN; |
71 | | |
72 | | #if LOG_FILE |
73 | | FILE *logFile = NULL; |
74 | | #endif // of if LOG_FILE |
75 | | |
76 | | // WTF??? |
77 | | extern bool __attribute__ ((weak)) gpsd_add_device(const char *device_name, |
78 | | bool flag_nowait); |
79 | | |
80 | | /* Industry ids |
81 | | * https://canboat.github.io/canboat/canboat.html#lookup-INDUSTRY_CODE |
82 | | */ |
83 | | static const struct vlist_t indus_ids[] = { |
84 | | {0, "Global"}, |
85 | | {1, "Highway"}, |
86 | | {2, "Agriculture"}, |
87 | | {3, "Construction"}, |
88 | | {4, "Marine Industry"}, |
89 | | {5, "Industrial"}, |
90 | | {0, NULL} |
91 | | }; |
92 | | |
93 | | /* Manfacturer ids |
94 | | * https://canboat.github.io/canboat/canboat.html#lookup-MANUFACTURER_CODE |
95 | | */ |
96 | | static const struct vlist_t mfg_ids[] = { |
97 | | {69, "ARKS Enterprises, Inc."}, |
98 | | {78, "FW Murphy/Enovation Controls"}, |
99 | | {80, "Twin Disc"}, |
100 | | {85, "Kohler Power Systems"}, |
101 | | {88, "Hemisphere GPS Inc"}, |
102 | | {116, "BEP Marine"}, |
103 | | {135, "Airmar"}, |
104 | | {137, "Maretron"}, |
105 | | {140, "Lowrance"}, |
106 | | {144, "Mercury Marine"}, |
107 | | {147, "Nautibus Electronic GmbH"}, |
108 | | {148, "Blue Water Data"}, |
109 | | {154, "Westerbeke"}, |
110 | | {157, "ISSPRO Inc"}, |
111 | | {161, "Offshore Systems (UK) Ltd."}, |
112 | | {163, "Evinrude/BRP"}, |
113 | | {165, "CPAC Systems AB"}, |
114 | | {168, "Xantrex Technology Inc."}, |
115 | | {169, "Marlin Technologies, Inc."}, |
116 | | {172, "Yanmar Marine"}, |
117 | | {174, "Volvo Penta"}, |
118 | | {175, "Honda Marine"}, |
119 | | {176, "Carling Technologies Inc. (Moritz Aerospace)"}, |
120 | | {185, "Beede Instruments"}, |
121 | | {192, "Floscan Instrument Co. Inc."}, |
122 | | {193, "Nobletec"}, |
123 | | {198, "Mystic Valley Communications"}, |
124 | | {199, "Actia"}, |
125 | | {200, "Honda Marine"}, |
126 | | {201, "Disenos Y Technologia"}, |
127 | | {211, "Digital Switching Systems"}, |
128 | | {215, "Xintex/Atena"}, |
129 | | {224, "EMMI NETWORK S.L."}, |
130 | | {225, "Honda Marine"}, |
131 | | {228, "ZF"}, |
132 | | {229, "Garmin"}, |
133 | | {233, "Yacht Monitoring Solutions"}, |
134 | | {235, "Sailormade Marine Telemetry/Tetra Technology LTD"}, |
135 | | {243, "Eride"}, |
136 | | {250, "Honda Marine"}, |
137 | | {257, "Honda Motor Company LTD"}, |
138 | | {272, "Groco"}, |
139 | | {273, "Actisense"}, |
140 | | {274, "Amphenol LTW Technology"}, |
141 | | {275, "Navico"}, |
142 | | {283, "Hamilton Jet"}, |
143 | | {285, "Sea Recovery"}, |
144 | | {286, "Coelmo SRL Italy"}, |
145 | | {295, "BEP Marine"}, |
146 | | {304, "Empir Bus"}, |
147 | | {305, "NovAtel"}, |
148 | | {306, "Sleipner Motor AS"}, |
149 | | {307, "MBW Technologies"}, |
150 | | {311, "Fischer Panda"}, |
151 | | {315, "ICOM"}, |
152 | | {328, "Qwerty"}, |
153 | | {329, "Dief"}, |
154 | | {341, "Boening Automationstechnologie GmbH & Co. KG"}, |
155 | | {345, "Korean Maritime University"}, |
156 | | {351, "Thrane and Thrane"}, |
157 | | {355, "Mastervolt"}, |
158 | | {356, "Fischer Panda Generators"}, |
159 | | {358, "Victron Energy"}, |
160 | | {370, "Rolls Royce Marine"}, |
161 | | {373, "Electronic Design"}, |
162 | | {374, "Northern Lights"}, |
163 | | {378, "Glendinning"}, |
164 | | {381, "B & G"}, |
165 | | {384, "Rose Point Navigation Systems"}, |
166 | | {385, "Johnson Outdoors Marine Electronics Inc Geonav"}, |
167 | | {394, "Capi 2"}, |
168 | | {396, "Beyond Measure"}, |
169 | | {400, "Livorsi Marine"}, |
170 | | {404, "ComNav"}, |
171 | | {409, "Chetco"}, |
172 | | {419, "Fusion Electronics"}, |
173 | | {421, "Standard Horizon"}, |
174 | | {422, "True Heading AB"}, |
175 | | {426, "Egersund Marine Electronics AS"}, |
176 | | {427, "em-trak Marine Electronics"}, |
177 | | {431, "Tohatsu Co, JP"}, |
178 | | {437, "Digital Yacht"}, |
179 | | {438, "Comar Systems Limited"}, |
180 | | {440, "Cummins"}, |
181 | | {443, "VDO (aka Continental-Corporation)"}, |
182 | | {451, "Parker Hannifin aka Village Marine Tech"}, |
183 | | {459, "Alltek Marine Electronics Corp"}, |
184 | | {460, "SAN GIORGIO S.E.I.N"}, |
185 | | {466, "Veethree Electronics & Marine"}, |
186 | | {467, "Humminbird Marine Electronics"}, |
187 | | {470, "SI-TEX Marine Electronics"}, |
188 | | {471, "Sea Cross Marine AB"}, |
189 | | {475, "GME aka Standard Communications Pty LTD"}, |
190 | | {476, "Humminbird Marine Electronics"}, |
191 | | {478, "Ocean Sat BV"}, |
192 | | {481, "Chetco Digitial Instruments"}, |
193 | | {493, "Watcheye"}, |
194 | | {499, "Lcj Capteurs"}, |
195 | | {502, "Attwood Marine"}, |
196 | | {503, "Naviop S.R.L."}, |
197 | | {504, "Vesper Marine Ltd"}, |
198 | | {510, "Marinesoft Co. LTD"}, |
199 | | {513, "Simarine"}, |
200 | | {517, "NoLand Engineering"}, |
201 | | {518, "Transas USA"}, |
202 | | {529, "National Instruments Korea"}, |
203 | | {530, "National Marine Electronics Association"}, |
204 | | {532, "Onwa Marine"}, |
205 | | {540, "Webasto"}, |
206 | | {571, "Marinecraft (South Korea)"}, |
207 | | {573, "McMurdo Group aka Orolia LTD"}, |
208 | | {578, "Advansea"}, |
209 | | {579, "KVH"}, |
210 | | {580, "San Jose Technology"}, |
211 | | {583, "Yacht Control"}, |
212 | | {586, "Suzuki Motor Corporation"}, |
213 | | {591, "US Coast Guard"}, |
214 | | {595, "Ship Module aka Customware"}, |
215 | | {600, "Aquatic AV"}, |
216 | | {605, "Aventics GmbH"}, |
217 | | {606, "Intellian"}, |
218 | | {612, "SamwonIT"}, |
219 | | {614, "Arlt Tecnologies"}, |
220 | | {637, "Bavaria Yacts"}, |
221 | | {641, "Diverse Yacht Services"}, |
222 | | {644, "Wema U.S.A dba KUS"}, |
223 | | {645, "Garmin"}, |
224 | | {658, "Shenzhen Jiuzhou Himunication"}, |
225 | | {688, "Rockford Corp"}, |
226 | | {699, "Harman International"}, |
227 | | {704, "JL Audio"}, |
228 | | {708, "Lars Thrane"}, |
229 | | {715, "Autonnic"}, |
230 | | {717, "Yacht Devices"}, |
231 | | {734, "REAP Systems"}, |
232 | | {735, "Au Electronics Group"}, |
233 | | {739, "LxNav"}, |
234 | | {741, "Littelfuse, Inc (formerly Carling Technologies)"}, |
235 | | {743, "DaeMyung"}, |
236 | | {744, "Woosung"}, |
237 | | {748, "ISOTTA IFRA srl"}, |
238 | | {773, "Clarion US"}, |
239 | | {776, "HMI Systems"}, |
240 | | {777, "Ocean Signal"}, |
241 | | {778, "Seekeeper"}, |
242 | | {781, "Poly Planar"}, |
243 | | {785, "Fischer Panda DE"}, |
244 | | {795, "Broyda Industries"}, |
245 | | {796, "Canadian Automotive"}, |
246 | | {797, "Tides Marine"}, |
247 | | {798, "Lumishore"}, |
248 | | {799, "Still Water Designs and Audio"}, |
249 | | {802, "BJ Technologies (Beneteau)"}, |
250 | | {803, "Gill Sensors"}, |
251 | | {811, "Blue Water Desalination"}, |
252 | | {815, "FLIR"}, |
253 | | {824, "Undheim Systems"}, |
254 | | {826, "Lewmar Inc"}, |
255 | | {838, "TeamSurv"}, |
256 | | {844, "Fell Marine"}, |
257 | | {847, "Oceanvolt"}, |
258 | | {862, "Prospec"}, |
259 | | {868, "Data Panel Corp"}, |
260 | | {890, "L3 Technologies"}, |
261 | | {894, "Rhodan Marine Systems"}, |
262 | | {896, "Nexfour Solutions"}, |
263 | | {905, "ASA Electronics"}, |
264 | | {909, "Marines Co (South Korea)"}, |
265 | | {911, "Nautic-on"}, |
266 | | {917, "Sentinel"}, |
267 | | {929, "JL Marine ystems"}, |
268 | | {930, "Ecotronix"}, |
269 | | {944, "Zontisa Marine"}, |
270 | | {951, "EXOR International"}, |
271 | | {962, "Timbolier Industries"}, |
272 | | {963, "TJC Micro"}, |
273 | | {968, "Cox Powertrain"}, |
274 | | {969, "Blue Seas"}, |
275 | | {981, "Kobelt Manufacturing Co. Ltd"}, |
276 | | {992, "Blue Ocean IOT"}, |
277 | | {997, "Xenta Systems"}, |
278 | | {1004, "Ultraflex SpA"}, |
279 | | {1008, "Lintest SmartBoat"}, |
280 | | {1011, "Soundmax"}, |
281 | | {1020, "Team Italia Marine (Onyx Marine Automation s.r.l)"}, |
282 | | {1021, "Entratech"}, |
283 | | {1022, "ITC Inc."}, |
284 | | {1029, "The Marine Guardian LLC"}, |
285 | | {1047, "Sonic Corporation"}, |
286 | | {1051, "ProNav"}, |
287 | | {1053, "Vetus Maxwell INC."}, |
288 | | {1056, "Lithium Pros"}, |
289 | | {1059, "Boatrax"}, |
290 | | {1062, "Marol Co ltd"}, |
291 | | {1065, "CALYPSO Instruments"}, |
292 | | {1066, "Spot Zero Water"}, |
293 | | {1069, "Lithionics Battery LLC"}, |
294 | | {1070, "Quick-teck Electronics Ltd"}, |
295 | | {1075, "Uniden America"}, |
296 | | {1083, "Nauticoncept"}, |
297 | | {1084, "Shadow-Caster LED lighting LLC"}, |
298 | | {1085, "Wet Sounds, LLC"}, |
299 | | {1088, "E-T-A Circuit Breakers"}, |
300 | | {1092, "Scheiber"}, |
301 | | {1100, "Smart Yachts International Limited"}, |
302 | | {1109, "Dockmate"}, |
303 | | {1114, "Bobs Machine"}, |
304 | | {1118, "L3Harris ASV"}, |
305 | | {1119, "Balmar LLC"}, |
306 | | {1120, "Elettromedia spa"}, |
307 | | {1127, "Electromaax"}, |
308 | | {1140, "Across Oceans Systems Ltd."}, |
309 | | {1145, "Kiwi Yachting"}, |
310 | | {1150, "BSB Artificial Intelligence GmbH"}, |
311 | | {1151, "Orca Technologoes AS"}, |
312 | | {1154, "TBS Electronics BV"}, |
313 | | {1158, "Technoton Electroics"}, |
314 | | {1160, "MG Energy Systems B.V."}, |
315 | | {1169, "Sea Macine Robotics Inc."}, |
316 | | {1171, "Vista Manufacturing"}, |
317 | | {1183, "Zipwake"}, |
318 | | {1186, "Sailmon BV"}, |
319 | | {1192, "Airmoniq Pro Kft"}, |
320 | | {1194, "Sierra Marine"}, |
321 | | {1200, "Xinuo Information Technology (Xiamen)"}, |
322 | | {1218, "Septentrio"}, |
323 | | {1233, "NKE Marine Elecronics"}, |
324 | | {1238, "SuperTrack Aps"}, |
325 | | {1239, "Honda Electronics Co., LTD"}, |
326 | | {1245, "Raritan Engineering Company, Inc"}, |
327 | | {1249, "Integrated Power Solutions AG"}, |
328 | | {1260, "Interactive Technologies, Inc."}, |
329 | | {1283, "LTG-Tech"}, |
330 | | {1299, "Energy Solutions (UK) LTD."}, |
331 | | {1300, "WATT Fuel Cell Corp"}, |
332 | | {1302, "Pro Mainer"}, |
333 | | {1305, "Dragonfly Energy"}, |
334 | | {1306, "Koden Electronics Co., Ltd"}, |
335 | | {1311, "Humphree AB"}, |
336 | | {1316, "Hinkley Yachts"}, |
337 | | {1317, "Global Marine Management GmbH (GMM)"}, |
338 | | {1320, "Triskel Marine Ltd"}, |
339 | | {1330, "Warwick Control Technologies"}, |
340 | | {1331, "Dolphin Charger"}, |
341 | | {1337, "Barnacle Systems Inc"}, |
342 | | {1348, "Radian IoT, Inc."}, |
343 | | {1353, "Ocean LED Marine Ltd"}, |
344 | | {1359, "BluNav"}, |
345 | | {1361, "OVA (Nantong Saiyang Electronics Co., Ltd)"}, |
346 | | {1368, "RAD Propulsion"}, |
347 | | {1369, "Electric Yacht"}, |
348 | | {1372, "Elco Motor Yachts"}, |
349 | | {1384, "Tecnoseal Foundry S.r.l"}, |
350 | | {1385, "Pro Charging Systems, LLC"}, |
351 | | {1389, "EVEX Co., LTD"}, |
352 | | {1398, "Gobius Sensor Technology AB"}, |
353 | | {1403, "Arco Marine"}, |
354 | | {1408, "Lenco Marine Inc."}, |
355 | | {1413, "Naocontrol S.L."}, |
356 | | {1417, "Revatek"}, |
357 | | {1438, "Aeolionics"}, |
358 | | {1439, "PredictWind Ltd"}, |
359 | | {1440, "Egis Mobile Electric"}, |
360 | | {1445, "Starboard Yacht Group"}, |
361 | | {1446, "Roswell Marine"}, |
362 | | {1451, "ePropulsion (Guangdong ePropulsion Technology Ltd.)"}, |
363 | | {1452, "Micro-Air LLC"}, |
364 | | {1453, "Vital Battery"}, |
365 | | {1458, "Ride Controller LLC"}, |
366 | | {1460, "Tocaro Blue"}, |
367 | | {1461, "Vanquish Yachts"}, |
368 | | {1471, "FT Technologies"}, |
369 | | {1478, "Alps Alpine Co., Ltd."}, |
370 | | {1481, "E-Force Marine"}, |
371 | | {1482, "CMC Marine"}, |
372 | | {1483, "Nanjing Sandemarine Information Technology Co., Ltd."}, |
373 | | {1850, "Teleflex Marine (SeaStar Solutions)"}, |
374 | | {1851, "Raymarine"}, |
375 | | {1852, "Navionics"}, |
376 | | {1853, "Japan Radio Co"}, |
377 | | {1854, "Northstar Technologies"}, |
378 | | {1855, "Furuno"}, |
379 | | {1856, "Trimble"}, |
380 | | {1857, "Simrad"}, |
381 | | {1858, "Litton"}, |
382 | | {1859, "Kvasar AB"}, |
383 | | {1860, "MMP"}, |
384 | | {1861, "Vector Cantech"}, |
385 | | {1862, "Yamaha Marine"}, |
386 | | {1863, "Faria Instrument"}, |
387 | | {0, NULL} |
388 | | }; |
389 | | |
390 | 0 | #define SHIFT32 0x100000000l |
391 | | |
392 | | static int scale_int(int32_t var, const int64_t factor) |
393 | 0 | { |
394 | 0 | int64_t ret = (var * factor) >> 32; |
395 | |
|
396 | 0 | return (int)ret; |
397 | 0 | } |
398 | | |
399 | | static void print_data(struct gps_device_t *session) |
400 | 0 | { |
401 | 0 | unsigned char *buffer = session->lexer.outbuffer; |
402 | 0 | size_t len = session->lexer.outbuflen; |
403 | 0 | const PGN *pgn = (const PGN *)session->driver.nmea2000.workpgn; |
404 | 0 | size_t l1; |
405 | 0 | int l2 = 0; |
406 | 0 | int idx = 0; |
407 | 0 | char bu[128]; |
408 | |
|
409 | 0 | GPSD_LOG(LOG_PROG, &session->context->errout, |
410 | 0 | "NMEA2000: pgn %6d SA %u len %zu %s\n", |
411 | 0 | pgn->pgn, session->driver.nmea2000.source_addr, len, pgn->name); |
412 | |
|
413 | 0 | if (LOG_IO > libgps_debuglevel) { |
414 | 0 | return; |
415 | 0 | } |
416 | | |
417 | 0 | for (l1 = 0; l1 < len; l1++) { |
418 | 0 | if (0 == (l1 % 20) && |
419 | 0 | 0 != l1) { |
420 | 0 | GPSD_LOG(LOG_IO, &session->context->errout, |
421 | 0 | "NMEA2000: got data: %s\n", bu); |
422 | 0 | idx = 0; |
423 | 0 | bu[0] = '\0'; |
424 | 0 | } |
425 | | // FIXME: check buffer overrun |
426 | 0 | l2 = sprintf(&bu[idx], "x%02x ", (unsigned)buffer[l1]); |
427 | 0 | idx += l2; |
428 | 0 | } |
429 | 0 | GPSD_LOG(LOG_IO, &session->context->errout, |
430 | 0 | "NMEA2000: got data: %s\n", bu); |
431 | 0 | } |
432 | | |
433 | | static gps_mask_t get_mode(struct gps_device_t *session) |
434 | 0 | { |
435 | 0 | if (1 & session->driver.nmea2000.mode_valid) { |
436 | 0 | session->newdata.mode = session->driver.nmea2000.mode; |
437 | 0 | } else { |
438 | 0 | session->newdata.mode = MODE_NOT_SEEN; |
439 | 0 | } |
440 | |
|
441 | 0 | if (2 & session->driver.nmea2000.mode_valid) { |
442 | 0 | return MODE_SET | USED_IS; |
443 | 0 | } else { |
444 | 0 | return MODE_SET; |
445 | 0 | } |
446 | 0 | } |
447 | | |
448 | | |
449 | | static int decode_ais_header(struct gps_context_t *context, |
450 | | unsigned char *bu, |
451 | | size_t len, |
452 | | struct ais_t *ais, |
453 | | unsigned int mask) |
454 | 0 | { |
455 | 0 | if (4 < len) { |
456 | 0 | ais->type = (unsigned)( bu[0] & 0x3f); |
457 | 0 | ais->repeat = (unsigned)((bu[0] >> 6) & 0x03); |
458 | 0 | ais->mmsi = (unsigned) getleu32(bu, 1); |
459 | 0 | ais->mmsi &= mask; |
460 | 0 | GPSD_LOG(LOG_INF, &context->errout, |
461 | 0 | "NMEA2000 AIS message type %u, MMSI %09u:\n", |
462 | 0 | ais->type, ais->mmsi); |
463 | 0 | return 1; |
464 | 0 | } |
465 | | // else |
466 | 0 | ais->type = 0; |
467 | 0 | ais->repeat = 0; |
468 | 0 | ais->mmsi = 0; |
469 | 0 | GPSD_LOG(LOG_ERROR, &context->errout, |
470 | 0 | "NMEA2000 AIS message type %u, too short message.\n", |
471 | 0 | ais->type); |
472 | 0 | return 0; |
473 | 0 | } |
474 | | |
475 | | |
476 | | static void decode_ais_channel_info(unsigned char *bu, |
477 | | size_t len, |
478 | | unsigned int offset, |
479 | | struct gps_device_t *session) |
480 | 0 | { |
481 | 0 | unsigned int pos = offset / 8; |
482 | 0 | unsigned int bpos = offset % 8; |
483 | 0 | uint16_t x; |
484 | |
|
485 | 0 | if (pos >= (unsigned int)len) { |
486 | 0 | session->driver.aivdm.ais_channel = 'A'; |
487 | 0 | return; |
488 | 0 | } |
489 | 0 | x = getleu16(bu, pos); |
490 | 0 | x = (uint16_t)((x >> bpos) & 0x1f); |
491 | 0 | switch (x) { |
492 | 0 | case 1: |
493 | 0 | FALLTHROUGH |
494 | 0 | case 3: |
495 | 0 | session->driver.aivdm.ais_channel = 'B'; |
496 | 0 | break; |
497 | 0 | default: |
498 | 0 | session->driver.aivdm.ais_channel = 'A'; |
499 | 0 | break; |
500 | 0 | } |
501 | 0 | return; |
502 | 0 | } |
503 | | |
504 | | |
505 | | static int ais_turn_rate(int rate) |
506 | 0 | { |
507 | 0 | if (0 > rate) { |
508 | 0 | return -ais_turn_rate(-rate); |
509 | 0 | } |
510 | 0 | return (int)(4.733 * sqrt(rate * RAD_2_DEG * .0001 * 60.0)); |
511 | 0 | } |
512 | | |
513 | | |
514 | | static double ais_direction(unsigned int val, double scale) |
515 | 0 | { |
516 | 0 | if ((0xffff == val) && |
517 | 0 | (1.0 == scale)) { |
518 | 0 | return 511.0; |
519 | 0 | } |
520 | 0 | return val * RAD_2_DEG * 0.0001 * scale; |
521 | 0 | } |
522 | | |
523 | | |
524 | | /* |
525 | | * PGN 59392: ISO Acknowledgment |
526 | | */ |
527 | | static gps_mask_t hnd_059392(struct gps_device_t *session UNUSED) |
528 | 0 | { |
529 | 0 | return 0; |
530 | 0 | } |
531 | | |
532 | | |
533 | | /* |
534 | | * PGN 60928: ISO Address Claim |
535 | | */ |
536 | | static gps_mask_t hnd_060928(struct gps_device_t *session UNUSED) |
537 | 0 | { |
538 | 0 | return 0; |
539 | 0 | } |
540 | | |
541 | | |
542 | | /* |
543 | | * PGN 126208: NMEA Command/Request/Acknowledge |
544 | | */ |
545 | | static gps_mask_t hnd_126208(struct gps_device_t *session UNUSED) |
546 | 0 | { |
547 | 0 | return 0; |
548 | 0 | } |
549 | | |
550 | | |
551 | | /* |
552 | | * PGN 126464: ISO Transmit/Receive PGN List |
553 | | */ |
554 | | static gps_mask_t hnd_126464(struct gps_device_t *session UNUSED) |
555 | 0 | { |
556 | 0 | return 0; |
557 | 0 | } |
558 | | |
559 | | /* |
560 | | * PGN 126720: Maretron proprietary, used by Garmin, etc.: |
561 | | */ |
562 | | static gps_mask_t hnd_126720(struct gps_device_t *session UNUSED) |
563 | 0 | { |
564 | 0 | unsigned char *bu = session->lexer.outbuffer; |
565 | |
|
566 | 0 | unsigned word0 = getleu16(bu, 0); |
567 | 0 | unsigned mfg = word0 & 0x07ff; // 11 bits Manufacturer Code |
568 | | // 2 bits reserved |
569 | 0 | unsigned indus = (word0 >> 13) & 0x07; // 3 bits Industry Code |
570 | 0 | unsigned prop_id = bu[2]; // 8 bits Proprietary ID |
571 | 0 | unsigned cmd = bu[3]; // 8 bits Command |
572 | |
|
573 | 0 | GPSD_LOG(LOG_PROG, &session->context->errout, |
574 | 0 | "NMEA2000: pgn 126720 mfg %u indus %u prop %u cmd %u (%s/%s)\n", |
575 | 0 | mfg, indus, prop_id, cmd, |
576 | 0 | val2str(mfg, mfg_ids), |
577 | 0 | val2str(indus, indus_ids)); |
578 | 0 | return 0; |
579 | 0 | } |
580 | | |
581 | | static const struct vlist_t time_sources[] = { |
582 | | {0, "GPS"}, |
583 | | {1, "GLONASS"}, |
584 | | {2, "Radio Station"}, |
585 | | {3, "Local Cesium clock"}, |
586 | | {4, "Local Rubidium clock"}, |
587 | | {5, "Local Crystal clock"}, |
588 | | {0, NULL} |
589 | | }; |
590 | | |
591 | | /* |
592 | | * PGN: 126992 / 00370020 / 1F010 - 8 - System Time |
593 | | * |
594 | | * Field #1: SID |
595 | | * Bits: 8 |
596 | | * Signed: false |
597 | | * Field #2: Source |
598 | | * Bits: 4 |
599 | | * Type: Lookup table |
600 | | * Signed: false |
601 | | * Lookup: 0=GPS |
602 | | * Lookup: 1=GLONASS |
603 | | * Lookup: 2=Radio Station |
604 | | * Lookup: 3=Local Cesium clock |
605 | | * Lookup: 4=Local Rubidium clock |
606 | | * Lookup: 5=Local Crystal clock |
607 | | * Field #3: Reserved - Reserved |
608 | | * Bits: 4 |
609 | | * Type: Binary data |
610 | | * Signed: false |
611 | | * Field #4: Date - Days since January 1, 1970 |
612 | | * Bits: 16 |
613 | | * Units: days |
614 | | * Type: Date |
615 | | * Resolution: 1 |
616 | | * Signed: false |
617 | | * Field #5: Time - Seconds since midnight |
618 | | * Bits: 32 |
619 | | * Units: s |
620 | | * Type: Time |
621 | | * Resolution: 0.0001 |
622 | | * Signed: false |
623 | | * |
624 | | */ |
625 | | static gps_mask_t hnd_126992(struct gps_device_t *session) |
626 | 0 | { |
627 | 0 | unsigned char *bu = session->lexer.outbuffer; |
628 | |
|
629 | 0 | unsigned sid = bu[0]; |
630 | 0 | unsigned source = bu[1] & 0x0f; |
631 | 0 | uint64_t usecs = getleu32(bu, 4) * 100UL; // time of day in us |
632 | |
|
633 | 0 | USTOTS(&session->newdata.time, usecs); |
634 | 0 | session->newdata.time.tv_sec += (time_t)(getleu16(bu, 2) * 24 * 60 * 60); |
635 | | |
636 | | // casts for 32 bit time_t |
637 | 0 | GPSD_LOG(LOG_PROG, &session->context->errout, |
638 | 0 | "NMEA2000: pgn 126992 sid %u source %u (%s) time %lld %09ld\n", |
639 | 0 | sid, source, val2str(source, time_sources), |
640 | 0 | (long long)session->newdata.time.tv_sec, |
641 | 0 | (long)session->newdata.time.tv_nsec); |
642 | |
|
643 | 0 | return TIME_SET | get_mode(session); |
644 | 0 | } |
645 | | |
646 | | |
647 | | /* |
648 | | * PGN 126996: ISO Product Information |
649 | | */ |
650 | | static gps_mask_t hnd_126996(struct gps_device_t *session UNUSED) |
651 | 0 | { |
652 | 0 | return 0; |
653 | 0 | } |
654 | | |
655 | | |
656 | | /* |
657 | | * PGN 127245: NAV Rudder |
658 | | */ |
659 | | static gps_mask_t hnd_127245(struct gps_device_t *session UNUSED) |
660 | 0 | { |
661 | 0 | return 0; |
662 | 0 | } |
663 | | |
664 | | |
665 | | /* |
666 | | * PGN 127250: NAV Vessel Heading |
667 | | */ |
668 | | static gps_mask_t hnd_127250(struct gps_device_t *session UNUSED) |
669 | 0 | { |
670 | 0 | unsigned char *bu = session->lexer.outbuffer; |
671 | 0 | int aux; |
672 | |
|
673 | 0 | session->gpsdata.attitude.heading = getleu16(bu, 1) * RAD_2_DEG * 0.0001; |
674 | | // printf("ATT 0:%8.3f\n",session->gpsdata.attitude.heading); |
675 | 0 | aux = getles16(bu, 3); |
676 | 0 | if (0x07fff != aux) { |
677 | 0 | session->gpsdata.attitude.heading += aux * RAD_2_DEG * 0.0001; |
678 | 0 | } |
679 | | // printf("ATT 1:%8.3f %6x\n",session->gpsdata.attitude.heading, aux); |
680 | 0 | aux = getles16(bu, 5); |
681 | 0 | if (0x07fff != aux) { |
682 | 0 | session->gpsdata.attitude.heading += aux * RAD_2_DEG * 0.0001; |
683 | 0 | } |
684 | | // printf("ATT 2:%8.3f %6x\n",session->gpsdata.attitude.heading, aux); |
685 | |
|
686 | 0 | return ONLINE_SET | ATTITUDE_SET; |
687 | 0 | } |
688 | | |
689 | | |
690 | | /* |
691 | | * PGN 127258: GNSS Magnetic Variation |
692 | | * |
693 | | * 1 Sequence ID |
694 | | * 2 Variation Source |
695 | | * 3 Reserved Bits |
696 | | * 4 Age of Service (Date) |
697 | | * 5 Variation |
698 | | * 6 Reserved B |
699 | | */ |
700 | | static gps_mask_t hnd_127258(struct gps_device_t *session UNUSED) |
701 | 0 | { |
702 | | // FIXME? Get magnetic variation |
703 | 0 | return 0; |
704 | 0 | } |
705 | | |
706 | | |
707 | | static const struct vlist_t dc_types[] = { |
708 | | {0, "Battery"}, |
709 | | {1, "Alternator"}, |
710 | | {2, "Convertor"}, |
711 | | {3, "Solar cell"}, |
712 | | {4, "Wind generator"}, |
713 | | {0, NULL}, |
714 | | }; |
715 | | |
716 | | /* |
717 | | * PGN 127506: PWR DC Detailed Status |
718 | | */ |
719 | | static gps_mask_t hnd_127506(struct gps_device_t *session) |
720 | 0 | { |
721 | 0 | unsigned char *bu = session->lexer.outbuffer; |
722 | |
|
723 | 0 | unsigned sid = bu[0]; |
724 | 0 | unsigned instance = bu[1]; |
725 | 0 | unsigned dc_type = bu[2]; |
726 | 0 | unsigned charge = bu[3]; |
727 | 0 | unsigned health = bu[4]; |
728 | 0 | unsigned timer = getles16(bu, 5); |
729 | 0 | unsigned ripple = getles16(bu, 7); |
730 | 0 | unsigned cap = getles16(bu, 9); |
731 | |
|
732 | 0 | GPSD_LOG(LOG_PROG, &session->context->errout, |
733 | 0 | "NMEA2000: pgn 127506 sid %u instance %u DC type %u (%s) " |
734 | 0 | "charge %u health %u time %u ripple %u cap %u\n", |
735 | 0 | sid, instance, dc_type, val2str(dc_type, dc_types), |
736 | 0 | charge, health, timer, ripple, cap); |
737 | 0 | return 0; |
738 | 0 | } |
739 | | |
740 | | |
741 | | /* |
742 | | * PGN 127508: PWR Battery Status |
743 | | */ |
744 | | static gps_mask_t hnd_127508(struct gps_device_t *session UNUSED) |
745 | 0 | { |
746 | 0 | return 0; |
747 | 0 | } |
748 | | |
749 | | |
750 | | /* |
751 | | * PGN 127513: PWR Battery Configuration Status |
752 | | */ |
753 | | static gps_mask_t hnd_127513(struct gps_device_t *session UNUSED) |
754 | 0 | { |
755 | 0 | return 0; |
756 | 0 | } |
757 | | |
758 | | |
759 | | /* |
760 | | * PGN 128259: NAV Speed |
761 | | */ |
762 | | static gps_mask_t hnd_128259(struct gps_device_t *session UNUSED) |
763 | 0 | { |
764 | 0 | return 0; |
765 | 0 | } |
766 | | |
767 | | |
768 | | /* |
769 | | * PGN 128267: NAV Water Depth |
770 | | */ |
771 | | static gps_mask_t hnd_128267(struct gps_device_t *session) |
772 | 0 | { |
773 | 0 | unsigned char *bu = session->lexer.outbuffer; |
774 | |
|
775 | 0 | unsigned sid = bu[0]; |
776 | 0 | double offset= getleu16(bu, 5) / 1000.0; |
777 | 0 | unsigned range = bu[7]; |
778 | 0 | session->gpsdata.attitude.depth = getleu32(bu, 1) / 100.0 ; |
779 | |
|
780 | 0 | GPSD_LOG(LOG_PROG, &session->context->errout, |
781 | 0 | "NMEA2000: pgn 128267 sid %u depth %.2f offset %.3f range %u\n", |
782 | 0 | sid, session->gpsdata.attitude.depth, offset, range); |
783 | 0 | return ONLINE_SET | ATTITUDE_SET; |
784 | 0 | } |
785 | | |
786 | | |
787 | | /* |
788 | | * PGN 128275: NAV Distance Log |
789 | | */ |
790 | | static gps_mask_t hnd_128275(struct gps_device_t *session UNUSED) |
791 | 0 | { |
792 | 0 | return 0; |
793 | 0 | } |
794 | | |
795 | | |
796 | | /* |
797 | | * PGN 129283: NAV Cross Track Error |
798 | | */ |
799 | | static gps_mask_t hnd_129283(struct gps_device_t *session UNUSED) |
800 | 0 | { |
801 | 0 | return 0; |
802 | 0 | } |
803 | | |
804 | | |
805 | | /* |
806 | | * PGN 129284: NAV Navigation Data |
807 | | */ |
808 | | static gps_mask_t hnd_129284(struct gps_device_t *session UNUSED) |
809 | 0 | { |
810 | 0 | return 0; |
811 | 0 | } |
812 | | |
813 | | |
814 | | /* |
815 | | * PGN 129285: NAV Navigation - Route/WP Information |
816 | | */ |
817 | | static gps_mask_t hnd_129285(struct gps_device_t *session UNUSED) |
818 | 0 | { |
819 | 0 | return 0; |
820 | 0 | } |
821 | | |
822 | | |
823 | | /* |
824 | | * PGN 129025: GNSS Position Rapid Update |
825 | | */ |
826 | | static gps_mask_t hnd_129025(struct gps_device_t *session) |
827 | 0 | { |
828 | 0 | unsigned char *bu = session->lexer.outbuffer; |
829 | |
|
830 | 0 | session->newdata.latitude = getles32(bu, 0) * 1e-7; |
831 | 0 | session->newdata.longitude = getles32(bu, 4) * 1e-7; |
832 | |
|
833 | 0 | GPSD_LOG(LOG_PROG, &session->context->errout, |
834 | 0 | "NMEA2000: pgn 129025 lat %.4f lon %.4f\n", |
835 | 0 | session->newdata.latitude, |
836 | 0 | session->newdata.longitude); |
837 | 0 | return LATLON_SET | get_mode(session); |
838 | 0 | } |
839 | | |
840 | | |
841 | | static const struct vlist_t cog_refs[] = { |
842 | | {0, "True"}, |
843 | | {1, "Magnetic"}, |
844 | | {2, "Error"}, |
845 | | {0, NULL}, |
846 | | }; |
847 | | |
848 | | /* |
849 | | * PGN 129026: GNSS COG and SOG Rapid Update |
850 | | */ |
851 | | static gps_mask_t hnd_129026(struct gps_device_t *session) |
852 | 0 | { |
853 | 0 | unsigned char *bu = session->lexer.outbuffer; |
854 | |
|
855 | 0 | unsigned cog_ref = bu[1] & 0x03; |
856 | 0 | session->driver.nmea2000.sid[0] = bu[0]; |
857 | |
|
858 | 0 | session->newdata.track = getleu16(bu, 2) * 1e-4 * RAD_2_DEG; |
859 | 0 | session->newdata.speed = getleu16(bu, 4) * 1e-2; |
860 | |
|
861 | 0 | GPSD_LOG(LOG_PROG, &session->context->errout, |
862 | 0 | "NMEA2000: pgn 129026 sid %u ref %u (%s) COG %.3f SOG %.3f\n", |
863 | 0 | session->driver.nmea2000.sid[0], cog_ref, |
864 | 0 | val2str(cog_ref, cog_refs), |
865 | 0 | session->newdata.track, session->newdata.speed); |
866 | 0 | return SPEED_SET | TRACK_SET | get_mode(session); |
867 | 0 | } |
868 | | |
869 | | static const struct vlist_t integritys[] = { |
870 | | {0, "No integrity checking"}, |
871 | | {1, "Safe"}, |
872 | | {2, "Caution"}, |
873 | | {3, "Unsafe"}, |
874 | | {0, NULL} |
875 | | }; |
876 | | |
877 | | static const struct vlist_t gns_methods[] = { |
878 | | {0, "no GNSS"}, |
879 | | {1, "GNSS fix"}, |
880 | | {2, "DGNSS fix"}, |
881 | | {3, "Precise GNSS"}, |
882 | | {4, "RTK Fixed Integer"}, |
883 | | {5, "RTK float"}, |
884 | | {6, "Estimated (DR) mode"}, |
885 | | {7, "Manual Input"}, |
886 | | {8, "Simulate mode"}, |
887 | | {0, NULL} |
888 | | }; |
889 | | |
890 | | static const struct vlist_t gnss_types[] = { |
891 | | {0, "GPS"}, |
892 | | {1, "GLONASS"}, |
893 | | {2, "GPS+GLONASS"}, |
894 | | {3, "GPS+SBAS/WAAS"}, |
895 | | {4, "GPS+SBAS/WAAS+GLONASS"}, |
896 | | {5, "Chayka"}, |
897 | | {6, "integrated"}, |
898 | | {7, "surveyed"}, |
899 | | {8, "Galileo"}, |
900 | | {0, NULL} |
901 | | }; |
902 | | |
903 | | /* |
904 | | * PGN: 129029 / 00374005 / 1F805 - 51 - GNSS Position Data |
905 | | * |
906 | | * The last 3 fields repeat until the data is exhausted. |
907 | | * |
908 | | * Field #1: SID |
909 | | * Bits: 8 |
910 | | * Signed: false |
911 | | * Field #2: Date - Days since January 1, 1970 |
912 | | * Bits: 16 |
913 | | * Units: days |
914 | | * Type: Date |
915 | | * Resolution: 1 |
916 | | * Signed: false |
917 | | * Field #3: Time - Seconds since midnight |
918 | | * Bits: 32 |
919 | | * Units: s |
920 | | * Type: Time |
921 | | * Resolution: 0.0001 |
922 | | * Signed: false |
923 | | * Field #4: Latitude |
924 | | * Bits: 64 |
925 | | * Units: deg |
926 | | * Type: Latitude |
927 | | * Resolution: 0.0000000000000001 |
928 | | * Signed: true |
929 | | * Field #5: Longitude |
930 | | * Bits: 64 |
931 | | * Units: deg |
932 | | * Type: Longitude |
933 | | * Resolution: 0.0000000000000001 |
934 | | * Signed: true |
935 | | * Field #6: Altitude - Altitude referenced to WGS-84 |
936 | | * Bits: 64 |
937 | | * Units: m |
938 | | * Resolution: 1e-06 |
939 | | * Signed: true |
940 | | * Field #7: GNSS type |
941 | | * Bits: 4 |
942 | | * Type: Lookup table |
943 | | * Signed: false |
944 | | * Lookup: 0=GPS |
945 | | * Lookup: 1=GLONASS |
946 | | * Lookup: 2=GPS+GLONASS |
947 | | * Lookup: 3=GPS+SBAS/WAAS |
948 | | * Lookup: 4=GPS+SBAS/WAAS+GLONASS |
949 | | * Lookup: 5=Chayka |
950 | | * Lookup: 6=integrated |
951 | | * Lookup: 7=surveyed |
952 | | * Lookup: 8=Galileo |
953 | | * Field #8: Method |
954 | | * Bits: 4 |
955 | | * Type: Lookup table |
956 | | * Signed: false |
957 | | * Lookup: 0=no GNSS |
958 | | * Lookup: 1=GNSS fix |
959 | | * Lookup: 2=DGNSS fix |
960 | | * Lookup: 3=Precise GNSS |
961 | | * Lookup: 4=RTK Fixed Integer |
962 | | * Lookup: 5=RTK float |
963 | | * Lookup: 6=Estimated (DR) mode |
964 | | * Lookup: 7=Manual Input |
965 | | * Lookup: 8=Simulate mode |
966 | | * Field #9: Integrity |
967 | | * Bits: 2 |
968 | | * Type: Lookup table |
969 | | * Signed: false |
970 | | * Lookup: 0=No integrity checking |
971 | | * Lookup: 1=Safe |
972 | | * Lookup: 2=Caution |
973 | | * Field #10: Reserved - Reserved |
974 | | * Bits: 6 |
975 | | * Type: Binary data |
976 | | * Signed: false |
977 | | * Field #11: Number of SVs - Number of satellites used in solution |
978 | | * Bits: 8 |
979 | | * Signed: false |
980 | | * Field #12: HDOP - Horizontal dilution of precision |
981 | | * Bits: 16 |
982 | | * Resolution: 0.01 |
983 | | * Signed: true |
984 | | * Field #13: PDOP - Probable dilution of precision |
985 | | * Bits: 16 |
986 | | * Resolution: 0.01 |
987 | | * Signed: true |
988 | | * Field #14: Geoidal Separation - Geoidal Separation |
989 | | * Bits: 32 |
990 | | * Units: m |
991 | | * Resolution: 0.01 |
992 | | * Signed: true |
993 | | * Field #15: Reference Stations - Number of reference stations |
994 | | * Bits: 8 |
995 | | * Signed: false |
996 | | * Field #16: Reference Station Type |
997 | | * Bits: 4 |
998 | | * Type: Lookup table |
999 | | * Signed: false |
1000 | | * Lookup: 0=GPS |
1001 | | * Lookup: 1=GLONASS |
1002 | | * Lookup: 2=GPS+GLONASS |
1003 | | * Lookup: 3=GPS+SBAS/WAAS |
1004 | | * Lookup: 4=GPS+SBAS/WAAS+GLONASS |
1005 | | * Lookup: 5=Chayka |
1006 | | * Lookup: 6=integrated |
1007 | | * Lookup: 7=surveyed |
1008 | | * Lookup: 8=Galileo |
1009 | | * Field #17: Reference Station ID |
1010 | | * Bits: 12 |
1011 | | * Units: |
1012 | | * Signed: false |
1013 | | * Field #18: Age of DGNSS Corrections |
1014 | | * Bits: 16 |
1015 | | * Units: s |
1016 | | * Resolution: 0.01 |
1017 | | * Signed: false |
1018 | | * |
1019 | | */ |
1020 | | static gps_mask_t hnd_129029(struct gps_device_t *session) |
1021 | 0 | { |
1022 | 0 | unsigned char *bu = session->lexer.outbuffer; |
1023 | 0 | gps_mask_t mask = 0; |
1024 | 0 | uint64_t usecs; // time of day in us |
1025 | 0 | unsigned gns_method = (bu[31] >> 4) & 0x0f; |
1026 | 0 | unsigned gnss_type = bu[31] & 0x0f; |
1027 | 0 | unsigned integrity = bu[32] & 0x03; |
1028 | 0 | unsigned refs = bu[40]; |
1029 | |
|
1030 | 0 | session->driver.nmea2000.sid[3] = bu[0]; |
1031 | | |
1032 | | // field 3 is time of day in 0.1 ms |
1033 | 0 | usecs = getleu32(bu, 3) * (uint64_t)100; |
1034 | 0 | USTOTS(&session->newdata.time, usecs); |
1035 | | // add in the date from field 2 |
1036 | 0 | session->newdata.time.tv_sec += (time_t)(getleu16(bu,1) * 24 * 60 * 60); |
1037 | 0 | mask |= TIME_SET; |
1038 | |
|
1039 | 0 | session->newdata.latitude = getles64(bu, 7) * 1e-16; |
1040 | 0 | session->newdata.longitude = getles64(bu, 15) * 1e-16; |
1041 | 0 | mask |= LATLON_SET; |
1042 | |
|
1043 | 0 | session->newdata.altHAE = getles64(bu, 23) * 1e-6; |
1044 | 0 | mask |= ALTITUDE_SET; |
1045 | |
|
1046 | 0 | switch (gns_method) { |
1047 | 0 | case 0: |
1048 | 0 | session->newdata.status = STATUS_UNK; |
1049 | 0 | break; |
1050 | 0 | case 1: |
1051 | 0 | session->newdata.status = STATUS_GPS; |
1052 | 0 | break; |
1053 | 0 | case 2: |
1054 | 0 | session->newdata.status = STATUS_DGPS; |
1055 | 0 | break; |
1056 | 0 | case 3: |
1057 | 0 | session->newdata.status = STATUS_PPS_FIX; |
1058 | 0 | break; |
1059 | 0 | case 4: |
1060 | 0 | session->newdata.status = STATUS_RTK_FIX; |
1061 | 0 | break; |
1062 | 0 | case 5: |
1063 | 0 | session->newdata.status = STATUS_RTK_FLT; |
1064 | 0 | break; |
1065 | 0 | case 6: |
1066 | 0 | session->newdata.status = STATUS_DR; |
1067 | 0 | break; |
1068 | 0 | case 7: |
1069 | 0 | session->newdata.status = STATUS_TIME; |
1070 | 0 | break; |
1071 | 0 | case 8: |
1072 | 0 | session->newdata.status = STATUS_SIM; |
1073 | 0 | break; |
1074 | 0 | default: |
1075 | 0 | session->newdata.status = STATUS_UNK; |
1076 | 0 | break; |
1077 | 0 | } |
1078 | 0 | mask |= STATUS_SET; |
1079 | |
|
1080 | 0 | session->newdata.geoid_sep = getles32(bu, 38) / 100.0; |
1081 | |
|
1082 | 0 | session->gpsdata.satellites_used = (int)bu[33]; |
1083 | |
|
1084 | 0 | session->gpsdata.dop.hdop = getleu16(bu, 34) * 1e-2; |
1085 | 0 | session->gpsdata.dop.pdop = getleu16(bu, 36) * 1e-2; |
1086 | 0 | mask |= DOP_SET; |
1087 | |
|
1088 | 0 | session->newdata.geoid_sep = getles32(bu, 38) / 100; |
1089 | |
|
1090 | 0 | GPSD_LOG(LOG_PROG, &session->context->errout, |
1091 | 0 | "NMEA2000: pgn 129029 SA %u sid %u lat %.2f lon %.2f HAE %.2f " |
1092 | 0 | "type %u(%s) method %u(%s) integrity %u(%s) " |
1093 | 0 | "hdop:%5.2f pdop:%5.2f sep %.2f refs %u\n", |
1094 | 0 | session->driver.nmea2000.source_addr, |
1095 | 0 | session->driver.nmea2000.sid[3], |
1096 | 0 | session->newdata.latitude, |
1097 | 0 | session->newdata.longitude, |
1098 | 0 | session->newdata.altHAE, |
1099 | 0 | gnss_type, val2str(gnss_type, gnss_types), |
1100 | 0 | gns_method, val2str(gns_method, gns_methods), |
1101 | 0 | integrity, val2str(integrity, integritys), |
1102 | 0 | session->gpsdata.dop.hdop, |
1103 | 0 | session->gpsdata.dop.pdop, |
1104 | 0 | session->newdata.geoid_sep, refs); |
1105 | 0 | return mask | get_mode(session); |
1106 | 0 | } |
1107 | | |
1108 | | |
1109 | | /* |
1110 | | * PGN 129038: AIS Class A Position Report |
1111 | | */ |
1112 | | static gps_mask_t hnd_129038(struct gps_device_t *session) |
1113 | 0 | { |
1114 | 0 | unsigned char *bu = session->lexer.outbuffer; |
1115 | 0 | size_t len = session->lexer.outbuflen; |
1116 | 0 | struct ais_t *ais = &session->gpsdata.ais; |
1117 | |
|
1118 | 0 | if (0 != decode_ais_header(session->context, bu, len, ais, 0xffffffffU)) { |
1119 | 0 | ais->type1.lon = (int)scale_int(getles32(bu, 5), |
1120 | 0 | (int64_t)(SHIFT32 *.06L)); |
1121 | 0 | ais->type1.lat = (int)scale_int(getles32(bu, 9), |
1122 | 0 | (int64_t)(SHIFT32 *.06L)); |
1123 | 0 | ais->type1.accuracy = (bool) ((bu[13] >> 0) & 0x01); |
1124 | 0 | ais->type1.raim = (bool) ((bu[13] >> 1) & 0x01); |
1125 | 0 | ais->type1.second = (unsigned int) ((bu[13] >> 2) & 0x3f); |
1126 | 0 | ais->type1.course = (unsigned int)ais_direction( |
1127 | 0 | (unsigned int)getleu16(bu, 14), 10.0); |
1128 | 0 | ais->type1.speed = (unsigned int)(getleu16(bu, 16) * |
1129 | 0 | MPS_TO_KNOTS * 0.01 / 0.1); |
1130 | 0 | ais->type1.radio = (unsigned int) (getleu32(bu, 18) & 0x7ffff); |
1131 | 0 | ais->type1.heading = |
1132 | 0 | (unsigned int)ais_direction((unsigned int)getleu16(bu, 21), 1.0); |
1133 | 0 | ais->type1.turn = ais_turn_rate((int)getles16(bu, 23)); |
1134 | 0 | ais->type1.status = (unsigned int) ((bu[25] >> 0) & 0x0f); |
1135 | 0 | ais->type1.maneuver = 0; // Not transmitted ???? |
1136 | 0 | decode_ais_channel_info(bu, len, 163, session); |
1137 | |
|
1138 | 0 | return ONLINE_SET | AIS_SET; |
1139 | 0 | } |
1140 | 0 | return 0; |
1141 | 0 | } |
1142 | | |
1143 | | |
1144 | | /* |
1145 | | * PGN 129039: AIS Class B Position Report |
1146 | | */ |
1147 | | static gps_mask_t hnd_129039(struct gps_device_t *session) |
1148 | 0 | { |
1149 | 0 | unsigned char *bu = session->lexer.outbuffer; |
1150 | 0 | size_t len = session->lexer.outbuflen; |
1151 | 0 | struct ais_t *ais = &session->gpsdata.ais; |
1152 | |
|
1153 | 0 | if (0 != decode_ais_header(session->context, bu, len, ais, 0xffffffffU)) { |
1154 | 0 | ais->type18.lon = (int)scale_int(getles32(bu, 5), |
1155 | 0 | (int64_t)(SHIFT32 *.06L)); |
1156 | 0 | ais->type18.lat = (int)scale_int(getles32(bu, 9), |
1157 | 0 | (int64_t)(SHIFT32 *.06L)); |
1158 | 0 | ais->type18.accuracy = (bool) ((bu[13] >> 0) & 0x01); |
1159 | 0 | ais->type18.raim = (bool) ((bu[13] >> 1) & 0x01); |
1160 | 0 | ais->type18.second = (unsigned int) ((bu[13] >> 2) & 0x3f); |
1161 | 0 | ais->type18.course = |
1162 | 0 | (unsigned int)ais_direction((unsigned int) getleu16(bu, 14), 10.0); |
1163 | 0 | ais->type18.speed = (unsigned int)(getleu16(bu, 16) * |
1164 | 0 | MPS_TO_KNOTS * 0.01 / 0.1); |
1165 | 0 | ais->type18.radio = (unsigned int) (getleu32(bu, 18) & 0x7ffff); |
1166 | 0 | ais->type18.heading = |
1167 | 0 | (unsigned int)ais_direction((unsigned int) getleu16(bu, 21), 1.0); |
1168 | 0 | ais->type18.reserved = 0; |
1169 | 0 | ais->type18.regional = (unsigned int) ((bu[24] >> 0) & 0x03); |
1170 | 0 | ais->type18.cs = (bool) ((bu[24] >> 2) & 0x01); |
1171 | 0 | ais->type18.display = (bool) ((bu[24] >> 3) & 0x01); |
1172 | 0 | ais->type18.dsc = (bool) ((bu[24] >> 4) & 0x01); |
1173 | 0 | ais->type18.band = (bool) ((bu[24] >> 5) & 0x01); |
1174 | 0 | ais->type18.msg22 = (bool) ((bu[24] >> 6) & 0x01); |
1175 | 0 | ais->type18.assigned = (bool) ((bu[24] >> 7) & 0x01); |
1176 | 0 | decode_ais_channel_info(bu, len, 163, session); |
1177 | |
|
1178 | 0 | return ONLINE_SET | AIS_SET; |
1179 | 0 | } |
1180 | 0 | return 0; |
1181 | 0 | } |
1182 | | |
1183 | | |
1184 | | /* |
1185 | | * PGN 129040: AIS Class B Extended Position Report |
1186 | | * |
1187 | | * No test case for this message at the moment |
1188 | | */ |
1189 | | static gps_mask_t hnd_129040(struct gps_device_t *session) |
1190 | 0 | { |
1191 | 0 | unsigned char *bu = session->lexer.outbuffer; |
1192 | 0 | size_t len = session->lexer.outbuflen; |
1193 | 0 | struct ais_t *ais = &session->gpsdata.ais; |
1194 | |
|
1195 | 0 | if (0 != decode_ais_header(session->context, bu, len, ais, 0xffffffffU)) { |
1196 | 0 | uint16_t length, beam, to_bow, to_starboard; |
1197 | |
|
1198 | 0 | ais->type19.lon = (int)scale_int(getles32(bu, 5), |
1199 | 0 | (int64_t)(SHIFT32 *.06L)); |
1200 | 0 | ais->type19.lat = (int)scale_int(getles32(bu, 9), |
1201 | 0 | (int64_t)(SHIFT32 *.06L)); |
1202 | 0 | ais->type19.accuracy = (bool) ((bu[13] >> 0) & 0x01); |
1203 | 0 | ais->type19.raim = (bool) ((bu[13] >> 1) & 0x01); |
1204 | 0 | ais->type19.second = (unsigned int) ((bu[13] >> 2) & 0x3f); |
1205 | 0 | ais->type19.course = |
1206 | 0 | (unsigned int)ais_direction((unsigned int)getleu16(bu, 14), 10.0); |
1207 | 0 | ais->type19.speed = |
1208 | 0 | (unsigned int)(getleu16(bu, 16) * MPS_TO_KNOTS * 0.01 / 0.1); |
1209 | 0 | ais->type19.reserved = (unsigned int) ((bu[18] >> 0) & 0xff); |
1210 | 0 | ais->type19.regional = (unsigned int) ((bu[19] >> 0) & 0x0f); |
1211 | 0 | ais->type19.shiptype = (unsigned int) ((bu[20] >> 0) & 0xff); |
1212 | 0 | ais->type19.heading = |
1213 | 0 | (unsigned int) ais_direction((unsigned int) getleu16(bu, 21), 1.0); |
1214 | 0 | length = getleu16(bu, 24); |
1215 | 0 | beam = getleu16(bu, 26); |
1216 | 0 | to_starboard = getleu16(bu, 28); |
1217 | 0 | to_bow = getleu16(bu, 30); |
1218 | 0 | if ((0xffff == length) || |
1219 | 0 | (0xffff == to_bow)) { |
1220 | 0 | length = 0; |
1221 | 0 | to_bow = 0; |
1222 | 0 | } |
1223 | 0 | if ((0xffff == beam) || |
1224 | 0 | (0xffff == to_starboard)) { |
1225 | 0 | beam = 0; |
1226 | 0 | to_starboard = 0; |
1227 | 0 | } |
1228 | 0 | ais->type19.to_bow = (unsigned int) (to_bow / 10); |
1229 | 0 | ais->type19.to_stern = (unsigned int) ((length-to_bow) / 10); |
1230 | 0 | ais->type19.to_port = (unsigned int) ((beam-to_starboard) / 10); |
1231 | 0 | ais->type19.to_starboard = (unsigned int) (to_starboard / 10); |
1232 | 0 | ais->type19.epfd = (unsigned int) ((bu[23] >> 4) & 0x0f); |
1233 | 0 | ais->type19.dte = (unsigned int) ((bu[52] >> 0) & 0x01); |
1234 | 0 | ais->type19.assigned = (bool) ((bu[52] >> 1) & 0x01); |
1235 | 0 | strlcpy(ais->type19.shipname, (char *)&bu[32], |
1236 | 0 | sizeof(ais->type19.shipname)); |
1237 | 0 | decode_ais_channel_info(bu, len, 422, session); |
1238 | |
|
1239 | 0 | return ONLINE_SET | AIS_SET; |
1240 | 0 | } |
1241 | 0 | return 0; |
1242 | 0 | } |
1243 | | |
1244 | | |
1245 | | static const int mode_tab[] = {MODE_NO_FIX, MODE_2D, MODE_3D, MODE_NO_FIX, |
1246 | | MODE_NO_FIX, MODE_NO_FIX, MODE_NO_FIX, |
1247 | | MODE_NO_FIX}; |
1248 | | |
1249 | | static const struct vlist_t gnss_modes[] = { |
1250 | | {0, "1D"}, |
1251 | | {1, "2D"}, |
1252 | | {2, "3D"}, |
1253 | | {3, "Auto"}, |
1254 | | {0, NULL}, |
1255 | | }; |
1256 | | |
1257 | | /* |
1258 | | * PGN 129539: GNSS DOPs |
1259 | | */ |
1260 | | static gps_mask_t hnd_129539(struct gps_device_t *session) |
1261 | 0 | { |
1262 | 0 | unsigned char *bu = session->lexer.outbuffer; |
1263 | 0 | gps_mask_t mask = 0; |
1264 | 0 | unsigned int req_mode; |
1265 | 0 | unsigned int act_mode; |
1266 | |
|
1267 | 0 | session->driver.nmea2000.sid[1] = bu[0]; |
1268 | |
|
1269 | 0 | session->driver.nmea2000.mode_valid |= 1; |
1270 | |
|
1271 | 0 | req_mode = (unsigned int)((bu[1] >> 0) & 0x07); |
1272 | 0 | act_mode = (unsigned int)((bu[1] >> 3) & 0x07); |
1273 | | |
1274 | | /* This is a workaround for some GARMIN plotter, |
1275 | | * actual mode auto makes no sense for me! */ |
1276 | 0 | if ((3 == act_mode) && |
1277 | 0 | (3 != req_mode)) { |
1278 | 0 | act_mode = req_mode; |
1279 | 0 | } |
1280 | |
|
1281 | 0 | session->driver.nmea2000.mode = mode_tab[act_mode]; |
1282 | |
|
1283 | 0 | session->gpsdata.dop.hdop = getleu16(bu, 2) * 1e-2; |
1284 | 0 | session->gpsdata.dop.vdop = getleu16(bu, 4) * 1e-2; |
1285 | 0 | session->gpsdata.dop.tdop = getleu16(bu, 6) * 1e-2; |
1286 | 0 | mask |= DOP_SET; |
1287 | |
|
1288 | 0 | GPSD_LOG(LOG_PROG, &session->context->errout, |
1289 | 0 | "NMEA2000: pgn 129539 SA %u sid %u req %u(%s) act %u(%s) " |
1290 | 0 | "hdop %5.2f vdop %5.2f tdop %5.2f\n", |
1291 | 0 | session->driver.nmea2000.source_addr, |
1292 | 0 | session->driver.nmea2000.sid[1], |
1293 | 0 | req_mode, val2str(req_mode, gnss_modes), |
1294 | 0 | act_mode, val2str(act_mode, gnss_modes), |
1295 | 0 | session->gpsdata.dop.hdop, |
1296 | 0 | session->gpsdata.dop.vdop, |
1297 | 0 | session->gpsdata.dop.tdop); |
1298 | |
|
1299 | 0 | return mask | get_mode(session); |
1300 | 0 | } |
1301 | | |
1302 | | static const struct vlist_t range_modes[] = { |
1303 | | {0, "Range residuals used"}, |
1304 | | {1, "Range residuals calculated"}, |
1305 | | {0, NULL} |
1306 | | }; |
1307 | | |
1308 | | static const struct vlist_t svts[] = { |
1309 | | {0, "Not tracked"}, |
1310 | | {1, "Tracked"}, |
1311 | | {2, "Used"}, |
1312 | | {3, "Not tracked+Diff"}, |
1313 | | {4, "Tracked+Diff"}, |
1314 | | {5, "Used+Diff"}, |
1315 | | {0, NULL} |
1316 | | }; |
1317 | | |
1318 | | /* |
1319 | | * PGN 129540: GNSS Satellites in View |
1320 | | */ |
1321 | | static gps_mask_t hnd_129540(struct gps_device_t *session) |
1322 | 0 | { |
1323 | 0 | unsigned char *bu = session->lexer.outbuffer; |
1324 | 0 | size_t len = session->lexer.outbuflen; |
1325 | 0 | int l1; |
1326 | 0 | size_t expected_len; |
1327 | 0 | unsigned range_mode = bu[1] & 0x03; |
1328 | |
|
1329 | 0 | session->driver.nmea2000.sid[2] = bu[0]; |
1330 | 0 | session->gpsdata.satellites_visible = (int)bu[2]; |
1331 | 0 | if (MAXCHANNELS <= session->gpsdata.satellites_visible) { |
1332 | | // Handle a CVE for overrunning skyview[] |
1333 | 0 | GPSD_LOG(LOG_WARN, &session->context->errout, |
1334 | 0 | "NMEA2000: pgn 129540 SA %u Too many sats %d\n", |
1335 | 0 | session->driver.nmea2000.source_addr, |
1336 | 0 | session->gpsdata.satellites_visible); |
1337 | 0 | session->gpsdata.satellites_visible = MAXCHANNELS; |
1338 | 0 | } |
1339 | 0 | expected_len = 3 + (12 * session->gpsdata.satellites_visible); |
1340 | 0 | if (len != expected_len) { |
1341 | 0 | GPSD_LOG(LOG_WARN, &session->context->errout, |
1342 | 0 | "NMEA2000: pgn 129540 SA %u wrong length %zu s/b %zu\n", |
1343 | 0 | session->driver.nmea2000.source_addr, |
1344 | 0 | len, expected_len); |
1345 | 0 | return 0; |
1346 | 0 | } |
1347 | | |
1348 | 0 | memset(session->gpsdata.skyview, '\0', sizeof(session->gpsdata.skyview)); |
1349 | 0 | for (l1 = 0; l1 < session->gpsdata.satellites_visible; l1++) { |
1350 | 0 | int offset = 3 + (12 * l1); |
1351 | 0 | double elev = getles16(bu, offset + 1) * 1e-4 * RAD_2_DEG; |
1352 | 0 | double azi = getleu16(bu, offset + 3) * 1e-4 * RAD_2_DEG; |
1353 | 0 | double snr = getles16(bu, offset + 5) * 1e-2; |
1354 | |
|
1355 | 0 | unsigned svt = bu[offset + 11] & 0x0f; |
1356 | |
|
1357 | 0 | session->gpsdata.skyview[l1].elevation = elev; |
1358 | 0 | session->gpsdata.skyview[l1].azimuth = azi; |
1359 | 0 | session->gpsdata.skyview[l1].ss = snr; |
1360 | 0 | session->gpsdata.skyview[l1].PRN = (int16_t)bu[offset]; |
1361 | 0 | session->gpsdata.skyview[l1].used = false; |
1362 | 0 | if ((2 == svt) || |
1363 | 0 | (5 == svt)) { |
1364 | 0 | session->gpsdata.skyview[l1].used = true; |
1365 | 0 | } |
1366 | 0 | GPSD_LOG(LOG_IO, &session->context->errout, |
1367 | 0 | "NMEA2000: pgn 129540 PRN %d svt %u(%s)\n", |
1368 | 0 | session->gpsdata.skyview[l1].PRN, |
1369 | 0 | svt, val2str(svt, svts)); |
1370 | 0 | } |
1371 | 0 | session->driver.nmea2000.mode_valid |= 2; |
1372 | 0 | GPSD_LOG(LOG_PROG, &session->context->errout, |
1373 | 0 | "NMEA2000: pgn 129540 SA %u sid %u mode %u(%s) seen %u\n", |
1374 | 0 | session->driver.nmea2000.source_addr, |
1375 | 0 | session->driver.nmea2000.sid[2], |
1376 | 0 | range_mode, val2str(range_mode, range_modes), |
1377 | 0 | session->gpsdata.satellites_visible); |
1378 | 0 | return SATELLITE_SET | USED_IS; |
1379 | 0 | } |
1380 | | |
1381 | | |
1382 | | /* |
1383 | | * PGN 129793: AIS UTC and Date Report |
1384 | | */ |
1385 | | static gps_mask_t hnd_129793(struct gps_device_t *session) |
1386 | 0 | { |
1387 | 0 | unsigned char *bu = session->lexer.outbuffer; |
1388 | 0 | size_t len = session->lexer.outbuflen; |
1389 | 0 | struct ais_t *ais = &session->gpsdata.ais; |
1390 | |
|
1391 | 0 | if (0 != decode_ais_header(session->context, bu, len, ais, 0xffffffffU)) { |
1392 | 0 | uint32_t time; |
1393 | 0 | uint32_t date; |
1394 | 0 | time_t date1; |
1395 | 0 | struct tm date2; |
1396 | |
|
1397 | 0 | ais->type4.lon = (int)scale_int(getles32(bu, 5), |
1398 | 0 | (int64_t)(SHIFT32 *.06L)); |
1399 | 0 | ais->type4.lat = (int)scale_int(getles32(bu, 9), |
1400 | 0 | (int64_t)(SHIFT32 *.06L)); |
1401 | 0 | ais->type4.accuracy = (bool) ((bu[13] >> 0) & 0x01); |
1402 | 0 | ais->type4.raim = (bool) ((bu[13] >> 1) & 0x01); |
1403 | |
|
1404 | 0 | time = getleu32(bu, 14); |
1405 | 0 | if (0xffffffff != time) { |
1406 | 0 | time = time / 10000; |
1407 | 0 | ais->type4.second = time % 60; time = time / 60; |
1408 | 0 | ais->type4.minute = time % 60; time = time / 60; |
1409 | 0 | ais->type4.hour = time % 24; |
1410 | 0 | } else { |
1411 | 0 | ais->type4.second = AIS_SECOND_NOT_AVAILABLE; |
1412 | 0 | ais->type4.minute = AIS_MINUTE_NOT_AVAILABLE; |
1413 | 0 | ais->type4.hour = AIS_HOUR_NOT_AVAILABLE; |
1414 | 0 | } |
1415 | |
|
1416 | 0 | ais->type4.radio = (unsigned int) (getleu32(bu, 18) & 0x7ffff); |
1417 | |
|
1418 | 0 | date = getleu16(bu, 21); |
1419 | 0 | if (0xffff != date) { |
1420 | 0 | date1 = (time_t)date * (24L *60L *60L); |
1421 | 0 | (void) gmtime_r(&date1, &date2); |
1422 | 0 | ais->type4.year = (unsigned int) (date2.tm_year + 1900); |
1423 | 0 | ais->type4.month = (unsigned int) (date2.tm_mon + 1); |
1424 | 0 | ais->type4.day = (unsigned int) (date2.tm_mday); |
1425 | 0 | } else { |
1426 | 0 | ais->type4.day = AIS_DAY_NOT_AVAILABLE; |
1427 | 0 | ais->type4.month = AIS_MONTH_NOT_AVAILABLE; |
1428 | 0 | ais->type4.year = AIS_YEAR_NOT_AVAILABLE; |
1429 | 0 | } |
1430 | |
|
1431 | 0 | ais->type4.epfd = (unsigned int) ((bu[23] >> 4) & 0x0f); |
1432 | |
|
1433 | 0 | decode_ais_channel_info(bu, len, 163, session); |
1434 | |
|
1435 | 0 | return ONLINE_SET | AIS_SET; |
1436 | 0 | } |
1437 | 0 | return 0; |
1438 | 0 | } |
1439 | | |
1440 | | |
1441 | | /* |
1442 | | * PGN 129794: AIS Class A Static and Voyage Related Data |
1443 | | */ |
1444 | | static gps_mask_t hnd_129794(struct gps_device_t *session) |
1445 | 0 | { |
1446 | 0 | unsigned char *bu = session->lexer.outbuffer; |
1447 | 0 | size_t len = session->lexer.outbuflen; |
1448 | 0 | struct ais_t *ais = &session->gpsdata.ais; |
1449 | |
|
1450 | 0 | if (0 != decode_ais_header(session->context, bu, len, ais, 0xffffffffU)) { |
1451 | 0 | uint16_t length, beam, to_bow, to_starboard, date; |
1452 | 0 | int l; |
1453 | 0 | uint32_t time; |
1454 | 0 | time_t date1; |
1455 | 0 | struct tm date2; |
1456 | 0 | int cpy_stop; |
1457 | |
|
1458 | 0 | ais->type5.ais_version = (unsigned int) ((bu[73] >> 0) & 0x03); |
1459 | 0 | ais->type5.imo = (unsigned int) getleu32(bu, 5); |
1460 | 0 | if (0xffffffffU == ais->type5.imo) { |
1461 | 0 | ais->type5.imo = 0; |
1462 | 0 | } |
1463 | 0 | ais->type5.shiptype = (unsigned int) ((bu[36] >> 0) & 0xff); |
1464 | 0 | length = getleu16(bu, 37); |
1465 | 0 | beam = getleu16(bu, 39); |
1466 | 0 | to_starboard = getleu16(bu, 41); |
1467 | 0 | to_bow = getleu16(bu, 43); |
1468 | 0 | if ((0xffff == length) || |
1469 | 0 | (0xffff == to_bow)) { |
1470 | 0 | length = 0; |
1471 | 0 | to_bow = 0; |
1472 | 0 | } |
1473 | 0 | if ((0xffff == beam) || |
1474 | 0 | (0xffff == to_starboard)) { |
1475 | 0 | beam = 0; |
1476 | 0 | to_starboard = 0; |
1477 | 0 | } |
1478 | 0 | ais->type5.to_bow = (unsigned int) (to_bow/10); |
1479 | 0 | ais->type5.to_stern = (unsigned int) ((length-to_bow) / 10); |
1480 | 0 | ais->type5.to_port = (unsigned int) ((beam-to_starboard) / 10); |
1481 | 0 | ais->type5.to_starboard = (unsigned int) (to_starboard / 10); |
1482 | 0 | ais->type5.epfd = (unsigned int) ((bu[73] >> 2) & 0x0f); |
1483 | 0 | date = getleu16(bu, 45); |
1484 | 0 | time = getleu32(bu, 47); |
1485 | 0 | date1 = (time_t) (date * 24 * 60 * 60); |
1486 | 0 | (void) gmtime_r(&date1, &date2); |
1487 | 0 | ais->type5.month = (unsigned int) (date2.tm_mon + 1); |
1488 | 0 | ais->type5.day = (unsigned int) (date2.tm_mday); |
1489 | 0 | ais->type5.minute = (unsigned int) (time/(10000 * 60)); |
1490 | 0 | ais->type5.hour = (unsigned int) (ais->type5.minute / 60); |
1491 | 0 | ais->type5.minute = |
1492 | 0 | (unsigned int)(ais->type5.minute-(ais->type5.hour * 60)); |
1493 | |
|
1494 | 0 | ais->type5.draught = (unsigned int) (getleu16(bu, 51) / 10); |
1495 | 0 | ais->type5.dte = (unsigned int) ((bu[73] >> 6) & 0x01); |
1496 | |
|
1497 | 0 | for (l = 0, cpy_stop = 0; l < 7; l++) { |
1498 | 0 | char next; |
1499 | |
|
1500 | 0 | next = (char) bu[9+l]; |
1501 | 0 | if ((' ' > next) || |
1502 | 0 | (0x7e < next)) { |
1503 | 0 | cpy_stop = 1; |
1504 | 0 | } |
1505 | 0 | if (0 == cpy_stop) { |
1506 | 0 | ais->type5.callsign[l] = next; |
1507 | 0 | } else { |
1508 | 0 | ais->type5.callsign[l] = 0; |
1509 | 0 | } |
1510 | 0 | } |
1511 | 0 | ais->type5.callsign[7] = (char) 0; |
1512 | |
|
1513 | 0 | for (l = 0, cpy_stop = 0; l < AIS_SHIPNAME_MAXLEN; l++) { |
1514 | 0 | char next; |
1515 | |
|
1516 | 0 | next = (char) bu[16+l]; |
1517 | 0 | if ((next < ' ') || |
1518 | 0 | (next > 0x7e)) { |
1519 | 0 | cpy_stop = 1; |
1520 | 0 | } |
1521 | 0 | if (cpy_stop == 0) { |
1522 | 0 | ais->type5.shipname[l] = next; |
1523 | 0 | } else { |
1524 | 0 | ais->type5.shipname[l] = 0; |
1525 | 0 | } |
1526 | 0 | } |
1527 | 0 | ais->type5.shipname[AIS_SHIPNAME_MAXLEN] = (char) 0; |
1528 | |
|
1529 | 0 | for (l = 0, cpy_stop = 0; l < 20; l++) { |
1530 | 0 | char next; |
1531 | |
|
1532 | 0 | next = (char) bu[53+l]; |
1533 | 0 | if ((next < ' ') || |
1534 | 0 | (next > 0x7e)) { |
1535 | 0 | cpy_stop = 1; |
1536 | 0 | } |
1537 | 0 | if (cpy_stop == 0) { |
1538 | 0 | ais->type5.destination[l] = next; |
1539 | 0 | } else { |
1540 | 0 | ais->type5.destination[l] = 0; |
1541 | 0 | } |
1542 | 0 | } |
1543 | 0 | ais->type5.destination[20] = (char) 0; |
1544 | | #if NMEA2000_DEBUG_AIS |
1545 | | printf("AIS: MMSI: %09u\n", |
1546 | | ais->mmsi); |
1547 | | printf("AIS: name: %-20.20s i:%8u c:%-8.8s b:%6u s:%6u p:%6u" |
1548 | | "s:%6u dr:%4.1f\n", |
1549 | | ais->type5.shipname, |
1550 | | ais->type5.imo, |
1551 | | ais->type5.callsign, |
1552 | | ais->type5.to_bow, |
1553 | | ais->type5.to_stern, |
1554 | | ais->type5.to_port, |
1555 | | ais->type5.to_starboard, |
1556 | | ais->type5.draught / 10.0); |
1557 | | printf("AIS: arrival:%-20.20s at %02u-%02u-%04d %02u:%0u\n", |
1558 | | ais->type5.destination, |
1559 | | ais->type5.day, |
1560 | | ais->type5.month, |
1561 | | date2.tm_year + 1900, |
1562 | | ais->type5.hour, |
1563 | | ais->type5.minute); |
1564 | | #endif // end of #if NMEA2000_DEBUG_AIS |
1565 | 0 | decode_ais_channel_info(bu, len, 592, session); |
1566 | 0 | return ONLINE_SET | AIS_SET; |
1567 | 0 | } |
1568 | 0 | return 0; |
1569 | 0 | } |
1570 | | |
1571 | | |
1572 | | /* |
1573 | | * PGN 129798: AIS SAR Aircraft Position Report |
1574 | | * |
1575 | | * No test case for this message at the moment |
1576 | | */ |
1577 | | static gps_mask_t hnd_129798(struct gps_device_t *session) |
1578 | 0 | { |
1579 | 0 | unsigned char *bu = session->lexer.outbuffer; |
1580 | 0 | size_t len = session->lexer.outbuflen; |
1581 | 0 | struct ais_t *ais = &session->gpsdata.ais; |
1582 | |
|
1583 | 0 | if (0 != decode_ais_header(session->context, bu, len, ais, 0xffffffffU)) { |
1584 | 0 | ais->type9.lon = (int)scale_int(getles32(bu, 5), |
1585 | 0 | (int64_t)(SHIFT32 *.06L)); |
1586 | 0 | ais->type9.lat = (int)scale_int(getles32(bu, 9), |
1587 | 0 | (int64_t)(SHIFT32 *.06L)); |
1588 | 0 | ais->type9.accuracy = (bool) ((bu[13] >> 0) & 0x01); |
1589 | 0 | ais->type9.raim = (bool) ((bu[13] >> 1) & 0x01); |
1590 | 0 | ais->type9.second = (unsigned int) ((bu[13] >> 2) & 0x3f); |
1591 | 0 | ais->type9.course = |
1592 | 0 | (unsigned int)ais_direction((unsigned int)getleu16(bu, 14), 10.0); |
1593 | 0 | ais->type9.speed = |
1594 | 0 | (unsigned int)(getleu16(bu, 16) * MPS_TO_KNOTS * 0.01 / 0.1); |
1595 | 0 | ais->type9.radio = (unsigned int) (getleu32(bu, 18) & 0x7ffff); |
1596 | 0 | ais->type9.alt = (unsigned int) (getleu64(bu, 21)/1000000); |
1597 | 0 | ais->type9.regional = (unsigned int) ((bu[29] >> 0) & 0xff); |
1598 | 0 | ais->type9.dte = (unsigned int) ((bu[30] >> 0) & 0x01); |
1599 | | // ais->type9.spare = (bu[30] >> 1) & 0x7f; |
1600 | 0 | ais->type9.assigned = 0; // Not transmitted ???? |
1601 | 0 | decode_ais_channel_info(bu, len, 163, session); |
1602 | |
|
1603 | 0 | return ONLINE_SET | AIS_SET; |
1604 | 0 | } |
1605 | 0 | return 0; |
1606 | 0 | } |
1607 | | |
1608 | | |
1609 | | /* |
1610 | | * PGN 129802: AIS Safety Related Broadcast Message |
1611 | | * |
1612 | | * No test case for this message at the moment |
1613 | | */ |
1614 | | static gps_mask_t hnd_129802(struct gps_device_t *session) |
1615 | 0 | { |
1616 | 0 | unsigned char *bu = session->lexer.outbuffer; |
1617 | 0 | size_t len = session->lexer.outbuflen; |
1618 | 0 | struct ais_t *ais = &session->gpsdata.ais; |
1619 | |
|
1620 | 0 | if (0 != decode_ais_header(session->context, bu, len, ais, 0x3fffffff)) { |
1621 | | // ais->type14.channel = (bu[ 5] >> 0) & 0x1f; |
1622 | 0 | strlcpy(ais->type14.text, (char *)&bu[6], sizeof(ais->type14.text)); |
1623 | 0 | decode_ais_channel_info(bu, len, 40, session); |
1624 | |
|
1625 | 0 | return ONLINE_SET | AIS_SET; |
1626 | 0 | } |
1627 | 0 | return 0; |
1628 | 0 | } |
1629 | | |
1630 | | |
1631 | | /* |
1632 | | * PGN 129809: AIS Class B CS Static Data Report, Part A |
1633 | | */ |
1634 | | static gps_mask_t hnd_129809(struct gps_device_t *session) |
1635 | 0 | { |
1636 | 0 | unsigned char *bu = session->lexer.outbuffer; |
1637 | 0 | size_t len = session->lexer.outbuflen; |
1638 | 0 | struct ais_t *ais = &session->gpsdata.ais; |
1639 | |
|
1640 | 0 | if (0 != decode_ais_header(session->context, bu, len, ais, 0xffffffffU)) { |
1641 | 0 | int index = session->driver.aivdm.context[0].type24_queue.index; |
1642 | 0 | struct ais_type24a_t *saveptr = |
1643 | 0 | &session->driver.aivdm.context[0].type24_queue.ships[index]; |
1644 | |
|
1645 | 0 | GPSD_LOG(LOG_PROG, &session->context->errout, |
1646 | 0 | "NMEA2000: AIS message 24A from %09u stashed.\n", |
1647 | 0 | ais->mmsi); |
1648 | |
|
1649 | 0 | strlcpy(ais->type24.shipname, (char *)&bu[5], |
1650 | 0 | sizeof(ais->type24.shipname)); |
1651 | 0 | strlcpy(saveptr->shipname, (char *)&bu[5], sizeof(saveptr->shipname)); |
1652 | |
|
1653 | 0 | saveptr->mmsi = ais->mmsi; |
1654 | |
|
1655 | 0 | index += 1; |
1656 | 0 | index %= MAX_TYPE24_INTERLEAVE; |
1657 | 0 | session->driver.aivdm.context[0].type24_queue.index = index; |
1658 | |
|
1659 | 0 | decode_ais_channel_info(bu, len, 200, session); |
1660 | |
|
1661 | 0 | ais->type24.part = part_a; |
1662 | 0 | return ONLINE_SET | AIS_SET; |
1663 | 0 | } |
1664 | 0 | return 0; |
1665 | 0 | } |
1666 | | |
1667 | | |
1668 | | /* |
1669 | | * PGN 129810: AIS Class B CS Static Data Report, Part B |
1670 | | */ |
1671 | | static gps_mask_t hnd_129810(struct gps_device_t *session) |
1672 | 0 | { |
1673 | 0 | unsigned char *bu = session->lexer.outbuffer; |
1674 | 0 | size_t len = session->lexer.outbuflen; |
1675 | 0 | struct ais_t *ais = &session->gpsdata.ais; |
1676 | |
|
1677 | 0 | if (0 != decode_ais_header(session->context, bu, len, ais, 0xffffffffU)) { |
1678 | 0 | int i; |
1679 | |
|
1680 | 0 | ais->type24.shiptype = (unsigned int) ((bu[ 5] >> 0) & 0xff); |
1681 | |
|
1682 | 0 | strlcpy(ais->type24.vendorid, (char *)&bu[6], |
1683 | 0 | sizeof(ais->type24.vendorid)); |
1684 | 0 | strlcpy(ais->type24.callsign, (char *)&bu[13], |
1685 | 0 | sizeof(ais->type24.callsign)); |
1686 | |
|
1687 | 0 | ais->type24.model = 0; |
1688 | 0 | ais->type24.serial = 0; |
1689 | |
|
1690 | 0 | if (AIS_AUXILIARY_MMSI(ais->mmsi)) { |
1691 | 0 | ais->type24.mothership_mmsi = (unsigned int)getleu32(bu, 28); |
1692 | 0 | } else { |
1693 | 0 | uint16_t length, beam, to_bow, to_starboard; |
1694 | |
|
1695 | 0 | length = getleu16(bu, 20); |
1696 | 0 | beam = getleu16(bu, 22); |
1697 | 0 | to_starboard = getleu16(bu, 24); |
1698 | 0 | to_bow = getleu16(bu, 26); |
1699 | 0 | if ((length == 0xffff) || (to_bow == 0xffff)) { |
1700 | 0 | length = 0; |
1701 | 0 | to_bow = 0; |
1702 | 0 | } |
1703 | 0 | if ((beam == 0xffff) || (to_starboard == 0xffff)) { |
1704 | 0 | beam = 0; |
1705 | 0 | to_starboard = 0; |
1706 | 0 | } |
1707 | 0 | ais->type24.dim.to_bow = (unsigned int) (to_bow/10); |
1708 | 0 | ais->type24.dim.to_stern = (unsigned int) ((length-to_bow)/10); |
1709 | 0 | ais->type24.dim.to_port = (unsigned int) ((beam-to_starboard)/10); |
1710 | 0 | ais->type24.dim.to_starboard = (unsigned int) (to_starboard/10); |
1711 | 0 | } |
1712 | |
|
1713 | 0 | for (i = 0; i < MAX_TYPE24_INTERLEAVE; i++) { |
1714 | 0 | if (session->driver.aivdm.context[0].type24_queue.ships[i].mmsi == |
1715 | 0 | ais->mmsi) { |
1716 | 0 | strlcpy(ais->type24.shipname, |
1717 | 0 | (char *)session->driver.aivdm.context[0].type24_queue.ships[i].shipname, |
1718 | 0 | sizeof(ais->type24.shipname)); |
1719 | |
|
1720 | 0 | GPSD_LOG(LOG_PROG, &session->context->errout, |
1721 | 0 | "NMEA2000: AIS 24B from %09u matches a 24A.\n", |
1722 | 0 | ais->mmsi); |
1723 | | // prevent false match if a 24B is repeated |
1724 | 0 | session->driver.aivdm.context[0].type24_queue.ships[i].mmsi = 0; |
1725 | | #if NMEA2000_DEBUG_AIS |
1726 | | printf("AIS: MMSI: %09u\n", ais->mmsi); |
1727 | | printf("AIS: name: %-20.20s v:%-8.8s c:%-8.8s b:%6u " |
1728 | | "s:%6u p:%6u s:%6u\n", |
1729 | | ais->type24.shipname, |
1730 | | ais->type24.vendorid, |
1731 | | ais->type24.callsign, |
1732 | | ais->type24.dim.to_bow, |
1733 | | ais->type24.dim.to_stern, |
1734 | | ais->type24.dim.to_port, |
1735 | | ais->type24.dim.to_starboard); |
1736 | | #endif // of #if NMEA2000_DEBUG_AIS |
1737 | |
|
1738 | 0 | decode_ais_channel_info(bu, len, 264, session); |
1739 | 0 | ais->type24.part = both; |
1740 | 0 | return ONLINE_SET | AIS_SET; |
1741 | 0 | } |
1742 | 0 | } |
1743 | | #if NMEA2000_DEBUG_AIS |
1744 | | printf("AIS: MMSI : %09u\n", ais->mmsi); |
1745 | | printf("AIS: vendor: %-8.8s c:%-8.8s b:%6u s:%6u p:%6u s:%6u\n", |
1746 | | ais->type24.vendorid, |
1747 | | ais->type24.callsign, |
1748 | | ais->type24.dim.to_bow, |
1749 | | ais->type24.dim.to_stern, |
1750 | | ais->type24.dim.to_port, |
1751 | | ais->type24.dim.to_starboard); |
1752 | | #endif // of #if NMEA2000_DEBUG_AIS |
1753 | 0 | decode_ais_channel_info(bu, len, 264, session); |
1754 | 0 | ais->type24.part = part_b; |
1755 | 0 | return ONLINE_SET | AIS_SET; |
1756 | 0 | } |
1757 | 0 | return 0; |
1758 | 0 | } |
1759 | | |
1760 | | |
1761 | | /* |
1762 | | * PGN 130306: NAV Wind Data |
1763 | | */ |
1764 | | static gps_mask_t hnd_130306(struct gps_device_t *session UNUSED) |
1765 | 0 | { |
1766 | 0 | return 0; |
1767 | 0 | } |
1768 | | |
1769 | | |
1770 | | /* |
1771 | | * PGN 130310: NAV Water Temp., Outside Air Temp., Atmospheric Pressure |
1772 | | */ |
1773 | | static gps_mask_t hnd_130310(struct gps_device_t *session UNUSED) |
1774 | 0 | { |
1775 | 0 | return 0; |
1776 | 0 | } |
1777 | | |
1778 | | |
1779 | | /* |
1780 | | * PGN 130311: NAV Environmental Parameters |
1781 | | */ |
1782 | | static gps_mask_t hnd_130311(struct gps_device_t *session UNUSED) |
1783 | 0 | { |
1784 | 0 | return 0; |
1785 | 0 | } |
1786 | | |
1787 | | |
1788 | | // keep list sorted! |
1789 | | static const PGN pgnlst[] = {{ 59392, 0, 0, hnd_059392, "ISO Acknowledgment"}, |
1790 | | { 60928, 0, 0, hnd_060928, "ISO Address Claim"}, |
1791 | | {126208, 0, 0, hnd_126208, |
1792 | | "NMEA Command/Request/Acknowledge"}, |
1793 | | {126464, 1, 0, hnd_126464, |
1794 | | "ISO Transmit/Receive PGN List"}, |
1795 | | {126720, 1, 0, hnd_126720, |
1796 | | "Maretron proprietary"}, |
1797 | | {126992, 0, 0, hnd_126992, "GNSS System Time"}, |
1798 | | {126996, 1, 0, hnd_126996, |
1799 | | "ISO Product Information"}, |
1800 | | {127245, 0, 4, hnd_127245, "NAV Rudder"}, |
1801 | | {127250, 0, 4, hnd_127250, "NAV Vessel Heading"}, |
1802 | | {127258, 0, 0, hnd_127258, |
1803 | | "GNSS Magnetic Variation"}, |
1804 | | {127258, 0, 0, hnd_127258, "NAV Vessel Heading"}, |
1805 | | {127506, 1, 3, hnd_127506, |
1806 | | "PWR DC Detailed Status"}, |
1807 | | {127508, 1, 3, hnd_127508, "PWR Battery Status"}, |
1808 | | {127513, 1, 3, hnd_127513, |
1809 | | "PWR Battery Configuration Status"}, |
1810 | | {128259, 0, 4, hnd_128259, "NAV Speed"}, |
1811 | | {128267, 0, 4, hnd_128267, "NAV Water Depth"}, |
1812 | | {128275, 1, 4, hnd_128275, "NAV Distance Log"}, |
1813 | | {129025, 0, 1, hnd_129025, |
1814 | | "GNSS Position Rapid Update"}, |
1815 | | {129026, 0, 1, hnd_129026, |
1816 | | "GNSS COG and SOG Rapid Update"}, |
1817 | | {129029, 1, 1, hnd_129029, |
1818 | | "GNSS Position Data"}, |
1819 | | {129038, 1, 2, hnd_129038, |
1820 | | "AIS Class A Position Report"}, |
1821 | | {129039, 1, 2, hnd_129039, |
1822 | | "AIS Class B Position Report"}, |
1823 | | {129040, 1, 2, hnd_129040, |
1824 | | "AIS Class B Extended Position Report"}, |
1825 | | {129283, 0, 0, hnd_129283, |
1826 | | "NAV Cross Track Error"}, |
1827 | | {129284, 1, 0, hnd_129284, "NAV Navigation Data"}, |
1828 | | {129285, 1, 0, hnd_129285, |
1829 | | "NAV Navigation - Route/WP Information"}, |
1830 | | {129539, 0, 1, hnd_129539, "GNSS DOPs"}, |
1831 | | {129540, 1, 1, hnd_129540, |
1832 | | "GNSS Satellites in View"}, |
1833 | | {129793, 1, 2, hnd_129793, |
1834 | | "AIS UTC and Date report"}, |
1835 | | {129794, 1, 2, hnd_129794, |
1836 | | "AIS Class A Static and Voyage Related Data"}, |
1837 | | {129798, 1, 2, hnd_129798, |
1838 | | "AIS SAR Aircraft Position Report"}, |
1839 | | {129802, 1, 2, hnd_129802, |
1840 | | "AIS Safety Related Broadcast Message"}, |
1841 | | {129809, 1, 2, hnd_129809, |
1842 | | "AIS Class B CS Static Data Report, Part A"}, |
1843 | | {129810, 1, 2, hnd_129810, |
1844 | | "AIS Class B CS Static Data Report, Part B"}, |
1845 | | {130306, 0, 4, hnd_130306, "NAV Wind Data"}, |
1846 | | {130310, 0, 4, hnd_130310, |
1847 | | "NAV Water Temp., Outside Air Temp., " |
1848 | | "Atmospheric Pressure"}, |
1849 | | {130311, 0, 4, hnd_130311, |
1850 | | "NAV Environmental Parameters"}, |
1851 | | {0 , 0, 0, NULL, "**error**"}, |
1852 | | }; |
1853 | | |
1854 | | |
1855 | | static const PGN *search_pgnlist(unsigned int pgn) |
1856 | 0 | { |
1857 | 0 | int l1 = 0; |
1858 | | |
1859 | | // since list is sorted, we can early out |
1860 | 0 | for (l1 = 0; pgn > pgnlst[l1].pgn; l1++) { |
1861 | 0 | if (0 == pgnlst[l1].pgn) { |
1862 | 0 | break; |
1863 | 0 | } |
1864 | 0 | } |
1865 | 0 | if (pgnlst[l1].pgn == pgn) { |
1866 | 0 | return &pgnlst[l1]; |
1867 | 0 | } |
1868 | 0 | return NULL; |
1869 | 0 | } |
1870 | | |
1871 | | static void find_pgn(struct can_frame *frame, struct gps_device_t *session) |
1872 | 0 | { |
1873 | 0 | unsigned can_net; |
1874 | 0 | unsigned source_prio; |
1875 | 0 | unsigned daddr; |
1876 | 0 | unsigned source_pgn; |
1877 | 0 | unsigned source_addr; |
1878 | |
|
1879 | 0 | session->driver.nmea2000.workpgn = NULL; |
1880 | 0 | can_net = session->driver.nmea2000.can_net; |
1881 | |
|
1882 | 0 | GPSD_LOG(LOG_RAW, &session->context->errout, |
1883 | 0 | "NMEA2000 find_pgn() can_id x%x can_net %u\n", |
1884 | 0 | frame->can_id, can_net); |
1885 | |
|
1886 | 0 | if (NMEA2000_NETS <= can_net) { |
1887 | 0 | GPSD_LOG(LOG_ERROR, &session->context->errout, |
1888 | 0 | "NMEA2000 find_pgn: Invalid can network %u.\n", can_net); |
1889 | 0 | return; |
1890 | 0 | } |
1891 | | |
1892 | 0 | if (frame->can_id & CAN_ERR_FLAG) { |
1893 | 0 | GPSD_LOG(LOG_ERROR, &session->context->errout, |
1894 | 0 | "NMEA2000 CAN_ERR_FLAG set x%x.\n", frame->can_id); |
1895 | 0 | return; |
1896 | 0 | } |
1897 | | |
1898 | 0 | if (!(frame->can_id & CAN_EFF_FLAG)) { |
1899 | | // we got RTR or 2.0A CAN frame, not used. SHould have been filtered. |
1900 | 0 | GPSD_LOG(LOG_WARN, &session->context->errout, |
1901 | 0 | "NMEA2000 CAN_EFF_FLAG not set x%x.\n", frame->can_id); |
1902 | 0 | return; |
1903 | 0 | } |
1904 | 0 | #if LOG_FILE |
1905 | 0 | if (NULL != logFile) { |
1906 | 0 | struct timespec msgTime; |
1907 | |
|
1908 | 0 | clock_gettime(CLOCK_REALTIME, &msgTime); |
1909 | 0 | (void)fprintf(logFile, |
1910 | 0 | "(%010lld.%06ld) can0 %08x#", |
1911 | 0 | (long long)msgTime.tv_sec, |
1912 | 0 | msgTime.tv_nsec / 1000, |
1913 | 0 | frame->can_id & 0x1ffffff); |
1914 | 0 | if (0 < (frame->can_dlc & 0x0f)) { |
1915 | 0 | int l1; |
1916 | 0 | for (l1 = 0; l1 < (frame->can_dlc & 0x0f); l1++) { |
1917 | 0 | (void)fprintf(logFile, "%02x", frame->data[l1]); |
1918 | 0 | } |
1919 | 0 | } |
1920 | 0 | (void)fprintf(logFile, "\n"); |
1921 | 0 | } |
1922 | 0 | #endif // of if LOG_FILE |
1923 | 0 | source_addr = frame->can_id & 0x0ff; |
1924 | 0 | if (NMEA2000_ADDRS <= source_addr) { |
1925 | 0 | GPSD_LOG(LOG_PROG, &session->context->errout, |
1926 | 0 | "NMEA2000 ignoring SA %u.\n", source_addr); |
1927 | 0 | return; |
1928 | 0 | } |
1929 | 0 | session->driver.nmea2000.can_msgcnt += 1; |
1930 | 0 | source_pgn = (frame->can_id >> 8) & 0x1ffff; |
1931 | 0 | source_prio = (frame->can_id >> 26) & 0x7; |
1932 | |
|
1933 | 0 | if (240 > ((source_pgn & 0x0ff00) >> 8)) { |
1934 | 0 | daddr = source_pgn & 0x000ff; |
1935 | 0 | source_pgn = source_pgn & 0x1ff00; |
1936 | 0 | } else { |
1937 | 0 | daddr = 0xff; |
1938 | 0 | } |
1939 | 0 | GPSD_LOG(LOG_DATA, &session->context->errout, |
1940 | 0 | "NMEA2000: source_prio %u SA %u daddr %u\n", |
1941 | 0 | source_prio, source_addr, daddr); |
1942 | |
|
1943 | 0 | if (!session->driver.nmea2000.source_addr) { |
1944 | 0 | unsigned int l1, l2; |
1945 | |
|
1946 | 0 | for (l1 = 0; l1 < NMEA2000_NETS; l1++) { |
1947 | 0 | for (l2 = 0; l2 < NMEA2000_ADDRS; l2++) { |
1948 | 0 | if (session == nmea2000_units[l1][l2]) { |
1949 | 0 | session->driver.nmea2000.source_addr = l2; |
1950 | 0 | session->driver.nmea2000.sa_valid = true; |
1951 | 0 | session->driver.nmea2000.can_net = l1; |
1952 | 0 | can_net = l1; |
1953 | 0 | } |
1954 | 0 | } |
1955 | 0 | } |
1956 | |
|
1957 | 0 | session->driver.nmea2000.source_addr = source_addr; |
1958 | 0 | session->driver.nmea2000.sa_valid = true; |
1959 | 0 | nmea2000_units[can_net][source_addr] = session; |
1960 | 0 | } |
1961 | |
|
1962 | 0 | if (source_addr == session->driver.nmea2000.source_addr) { |
1963 | | // current source_addr. Current net??? |
1964 | 0 | const PGN *work = search_pgnlist(source_pgn); |
1965 | |
|
1966 | 0 | if (NULL == work) { |
1967 | 0 | GPSD_LOG(LOG_WARN, &session->context->errout, |
1968 | 0 | "NMEA2000: PGN not found %08d %08x \n", |
1969 | 0 | source_pgn, source_pgn); |
1970 | 0 | } else if (0 == work->fast) { |
1971 | | // not FAST, one packet is one complete message |
1972 | |
|
1973 | 0 | GPSD_LOG(LOG_DATA, &session->context->errout, |
1974 | 0 | "NMEA2000: pgn %6d:%s \n", work->pgn, work->name); |
1975 | 0 | session->driver.nmea2000.workpgn = (const void *)work; |
1976 | 0 | session->lexer.outbuflen = frame->can_dlc & 0x0f; // max 15 |
1977 | 0 | memcpy(session->lexer.outbuffer, frame->data, |
1978 | 0 | session->lexer.outbuflen); |
1979 | 0 | } else if (0 == (frame->data[0] & 0x1f)) { |
1980 | | // FAST, first packet of multi packet message |
1981 | | |
1982 | | // max frame data 223 |
1983 | 0 | session->driver.nmea2000.fast_packet_len = frame->data[1]; |
1984 | 0 | session->driver.nmea2000.idx = frame->data[0]; |
1985 | 0 | GPSD_LOG(LOG_IO, &session->context->errout, |
1986 | 0 | "NMEA2000: Set idx %u SA %u flen %2x %6d\n", |
1987 | 0 | frame->data[0], |
1988 | 0 | session->driver.nmea2000.source_addr, |
1989 | 0 | frame->data[1], |
1990 | 0 | source_pgn); |
1991 | 0 | session->lexer.inbuflen = 6; |
1992 | 0 | session->driver.nmea2000.idx += 1; |
1993 | 0 | memcpy(session->lexer.inbuffer, &frame->data[2], 6); |
1994 | 0 | GPSD_LOG(LOG_DATA, &session->context->errout, |
1995 | 0 | "NMEA2000: pgn %6d:%s \n", work->pgn, work->name); |
1996 | 0 | } else if (frame->data[0] == session->driver.nmea2000.idx) { |
1997 | | /* FAST, the expected next packet of multi packet message. |
1998 | | * we assume FAST packets come in sequence order. |
1999 | | * Not always true. |
2000 | | * See: https://canboat.github.io/canboat/canboat.html |
2001 | | * Secton: packet framing. */ |
2002 | 0 | unsigned l2; |
2003 | | |
2004 | | // FIXME: check inbuflen and fast_packet_len |
2005 | 0 | l2 = session->driver.nmea2000.fast_packet_len - |
2006 | 0 | session->lexer.inbuflen; |
2007 | 0 | if (223 < l2) { |
2008 | | // WTF?? |
2009 | 0 | l2 = 0; |
2010 | 0 | } else if (7 < l2) { |
2011 | | // max 7 per packet |
2012 | 0 | l2 = 7; |
2013 | 0 | } |
2014 | | // take up to 7 bytes, of 8. 1st byte is idx. |
2015 | 0 | memcpy(&session->lexer.inbuffer[session->lexer.inbuflen], |
2016 | 0 | &frame->data[1], l2); |
2017 | 0 | session->lexer.inbuflen += l2; |
2018 | |
|
2019 | 0 | if (session->lexer.inbuflen == |
2020 | 0 | session->driver.nmea2000.fast_packet_len) { |
2021 | | // Got a complete message |
2022 | 0 | GPSD_LOG(LOG_IO, &session->context->errout, |
2023 | 0 | "NMEA2000: Fast done idx %2x/%2x SA %u " |
2024 | 0 | "flen %2x %6d\n", |
2025 | 0 | session->driver.nmea2000.idx, |
2026 | 0 | frame->data[0], |
2027 | 0 | session->driver.nmea2000.source_addr, |
2028 | 0 | (unsigned)session->driver.nmea2000.fast_packet_len, |
2029 | 0 | source_pgn); |
2030 | 0 | session->driver.nmea2000.workpgn = (const void *)work; |
2031 | 0 | session->lexer.outbuflen = |
2032 | 0 | session->driver.nmea2000.fast_packet_len; |
2033 | 0 | memcpy(session->lexer.outbuffer, session->lexer.inbuffer, |
2034 | 0 | session->lexer.outbuflen); |
2035 | 0 | session->driver.nmea2000.fast_packet_len = 0; |
2036 | 0 | } else { |
2037 | | // More to come. |
2038 | 0 | session->driver.nmea2000.idx += 1; |
2039 | 0 | } |
2040 | 0 | } else { |
2041 | | /* error? or packets out of order? |
2042 | | * reset FAST expected?? */ |
2043 | 0 | GPSD_LOG(LOG_WARN, &session->context->errout, |
2044 | 0 | "NMEA2000: Fast error idx%2x/%2x SA %u flen %2x %6d\n", |
2045 | 0 | session->driver.nmea2000.idx, |
2046 | 0 | frame->data[0], |
2047 | 0 | session->driver.nmea2000.source_addr, |
2048 | 0 | (unsigned)session->driver.nmea2000.fast_packet_len, |
2049 | 0 | source_pgn); |
2050 | 0 | } |
2051 | 0 | } else if (NULL == nmea2000_units[can_net][source_addr]) { |
2052 | | // unknown net/SA, add it as a new device. |
2053 | 0 | char buffer[GPS_PATH_MAX]; |
2054 | |
|
2055 | 0 | (void)snprintf(buffer, sizeof(buffer), "nmea2000://%s:%u", |
2056 | 0 | can_interface_name[can_net], |
2057 | 0 | source_addr); |
2058 | 0 | if (NULL != gpsd_add_device) { |
2059 | 0 | if (gpsd_add_device(buffer, true)) { |
2060 | 0 | GPSD_LOG(LOG_INF, &session->context->errout, |
2061 | 0 | "NMEA2000: gpsd_add_device(%s)\n", buffer); |
2062 | 0 | } else { |
2063 | 0 | GPSD_LOG(LOG_ERROR, &session->context->errout, |
2064 | 0 | "NMEA2000: gpsd_add_device(%s) failed\n", |
2065 | 0 | buffer); |
2066 | 0 | } |
2067 | 0 | } |
2068 | 0 | } // else, known net/SA that is not this net/SA. Ignore it. |
2069 | 0 | } |
2070 | | |
2071 | | |
2072 | | static ssize_t nmea2000_get(struct gps_device_t *session) |
2073 | 0 | { |
2074 | 0 | struct can_frame frame; |
2075 | 0 | ssize_t status; |
2076 | |
|
2077 | 0 | errno = 0; |
2078 | 0 | session->lexer.outbuflen = 0; |
2079 | | // FIXME: read() into a struct is not guaranteed in C |
2080 | | // sizeof(frame) === 16 |
2081 | 0 | status = read(session->gpsdata.gps_fd, &frame, sizeof(frame)); |
2082 | 0 | if ((ssize_t)sizeof(frame) == status) { |
2083 | 0 | session->lexer.type = NMEA2000_PACKET; |
2084 | 0 | find_pgn(&frame, session); |
2085 | |
|
2086 | 0 | return frame.can_dlc & 0x0f; |
2087 | 0 | } |
2088 | 0 | if (-1 == status && |
2089 | 0 | EAGAIN == errno && |
2090 | 0 | LOG_PROG > session->context->errout.debug) { |
2091 | | /* nothing to read, try again later |
2092 | | * do not log at low log levels */ |
2093 | 0 | return 0; |
2094 | 0 | } |
2095 | | // long cast for 32-bit. |
2096 | 0 | GPSD_LOG(LOG_WARN, &session->context->errout, |
2097 | 0 | "NMEA2000 nmea2000_get() status %ld %s(%d) \n", |
2098 | 0 | (long)status, strerror(errno), errno); |
2099 | 0 | return 0; |
2100 | 0 | } |
2101 | | |
2102 | | static gps_mask_t nmea2000_parse_input(struct gps_device_t *session) |
2103 | 0 | { |
2104 | 0 | gps_mask_t mask = 0; |
2105 | 0 | const PGN *work; |
2106 | 0 | char buf[128]; |
2107 | |
|
2108 | 0 | GPSD_LOG(LOG_RAW, &session->context->errout, |
2109 | 0 | "NMEA2000 nmea2000_parse_input(%s)\n", |
2110 | 0 | gps_hexdump(buf, sizeof(buf), |
2111 | 0 | session->lexer.outbuffer, |
2112 | 0 | session->lexer.outbuflen)); |
2113 | 0 | work = (const PGN *)session->driver.nmea2000.workpgn; |
2114 | |
|
2115 | 0 | if (NULL != work) { |
2116 | 0 | print_data(session); |
2117 | 0 | mask = (work->func)(session); |
2118 | 0 | session->driver.nmea2000.workpgn = NULL; |
2119 | 0 | } |
2120 | 0 | session->lexer.outbuflen = 0; |
2121 | |
|
2122 | 0 | return mask; |
2123 | 0 | } |
2124 | | |
2125 | | |
2126 | | int nmea2000_open(struct gps_device_t *session) |
2127 | 0 | { |
2128 | 0 | char interface_name[GPS_PATH_MAX]; |
2129 | 0 | socket_t sock; |
2130 | 0 | int status; |
2131 | 0 | int source_addr = -1; |
2132 | 0 | int can_net = -1; |
2133 | 0 | unsigned int l; |
2134 | 0 | struct ifreq ifr; |
2135 | 0 | struct sockaddr_can addr; |
2136 | 0 | char *sa_ptr = NULL; |
2137 | 0 | can_err_mask_t err_mask; |
2138 | 0 | int rcvbuf_size = 1000000; // requested receiver buffer size |
2139 | 0 | int curr_rcvbuf_size; |
2140 | 0 | socklen_t curr_rcvbuf_size_len = sizeof(curr_rcvbuf_size); |
2141 | 0 | struct can_filter can_filter; |
2142 | 0 | size_t interface_name_len; |
2143 | | |
2144 | | // FIXME: if this was a live socket, then we left orphan fd. |
2145 | 0 | INVALIDATE_SOCKET(session->gpsdata.gps_fd); |
2146 | |
|
2147 | 0 | GPSD_LOG(LOG_PROG, &session->context->errout, |
2148 | 0 | "NMEA2000 nmea2000_open(%s)\n", |
2149 | 0 | session->gpsdata.dev.path); |
2150 | |
|
2151 | 0 | session->driver.nmea2000.can_net = 0; |
2152 | | |
2153 | | // skip the leading "nmea2000://" |
2154 | 0 | (void)strlcpy(interface_name, session->gpsdata.dev.path + 11, |
2155 | 0 | sizeof(interface_name)); |
2156 | |
|
2157 | 0 | interface_name_len = strnlen(interface_name, sizeof(interface_name)); |
2158 | 0 | for (l = 0; l < interface_name_len; l++) { |
2159 | 0 | if (':' == interface_name[l]) { |
2160 | 0 | sa_ptr = &interface_name[l + 1]; |
2161 | 0 | interface_name[l] = 0; |
2162 | 0 | continue; |
2163 | 0 | } |
2164 | 0 | if (NULL != sa_ptr) { |
2165 | 0 | if (0 == isdigit(interface_name[l])) { |
2166 | 0 | GPSD_LOG(LOG_ERROR, &session->context->errout, |
2167 | 0 | "NMEA2000 open: Invalid char x%x in source addr.\n", |
2168 | 0 | interface_name[l]); |
2169 | 0 | return -1; |
2170 | 0 | } |
2171 | 0 | } |
2172 | 0 | } |
2173 | | |
2174 | 0 | if (NULL != sa_ptr) { |
2175 | 0 | source_addr = atoi(sa_ptr); |
2176 | 0 | if ((0 > source_addr) || |
2177 | 0 | (NMEA2000_ADDRS <= source_addr)) { |
2178 | 0 | GPSD_LOG(LOG_ERROR, &session->context->errout, |
2179 | 0 | "NMEA2000 open: SA %u out of range.\n", |
2180 | 0 | source_addr); |
2181 | 0 | return -1; |
2182 | 0 | } |
2183 | 0 | for (l = 0; l < NMEA2000_NETS; l++) { |
2184 | 0 | if (0 == strncmp(can_interface_name[l], |
2185 | 0 | interface_name, |
2186 | 0 | MIN(sizeof(interface_name), |
2187 | 0 | sizeof(can_interface_name[l])))) { |
2188 | 0 | can_net = l; |
2189 | 0 | break; |
2190 | 0 | } |
2191 | 0 | } |
2192 | 0 | if (0 > can_net) { |
2193 | 0 | for (l = 0; l < NMEA2000_NETS; l++) { |
2194 | 0 | if (0 == can_interface_name[l][0]) { |
2195 | 0 | can_net = l; |
2196 | 0 | break; |
2197 | 0 | } |
2198 | 0 | } |
2199 | 0 | } |
2200 | 0 | if (0 > can_net) { |
2201 | 0 | GPSD_LOG(LOG_ERROR, &session->context->errout, |
2202 | 0 | "NMEA2000 open: CAN device not open: %s .\n", |
2203 | 0 | interface_name); |
2204 | 0 | return -1; |
2205 | 0 | } |
2206 | 0 | } else { |
2207 | 0 | for (l = 0; l < NMEA2000_NETS; l++) { |
2208 | 0 | if (0 == strncmp(can_interface_name[l], |
2209 | 0 | interface_name, |
2210 | 0 | MIN(sizeof(interface_name), |
2211 | 0 | sizeof(can_interface_name[l])))) { |
2212 | 0 | GPSD_LOG(LOG_ERROR, &session->context->errout, |
2213 | 0 | "NMEA2000 open: CAN device duplicate open: %s .\n", |
2214 | 0 | interface_name); |
2215 | 0 | return -1; |
2216 | 0 | } |
2217 | 0 | } |
2218 | 0 | for (l = 0; l < NMEA2000_NETS; l++) { |
2219 | 0 | if (0 == can_interface_name[l][0]) { |
2220 | 0 | can_net = l; |
2221 | 0 | break; |
2222 | 0 | } |
2223 | 0 | } |
2224 | 0 | if (0 > can_net) { |
2225 | 0 | GPSD_LOG(LOG_ERROR, &session->context->errout, |
2226 | 0 | "NMEA2000 open: Too many CAN networks open.\n"); |
2227 | 0 | return -1; |
2228 | 0 | } |
2229 | 0 | } |
2230 | | |
2231 | | // Create the socket |
2232 | 0 | sock = socket(PF_CAN, SOCK_RAW, CAN_RAW); |
2233 | |
|
2234 | 0 | if (BAD_SOCKET(sock)) { |
2235 | 0 | GPSD_LOG(LOG_ERROR, &session->context->errout, |
2236 | 0 | "NMEA2000 open: socket(PF_CAN) %s(%d).\n", |
2237 | 0 | strerror(errno), errno); |
2238 | 0 | return -1; |
2239 | 0 | } |
2240 | | |
2241 | 0 | status = fcntl(sock, F_SETFL, O_NONBLOCK); |
2242 | 0 | if (0 != status) { |
2243 | 0 | GPSD_LOG(LOG_ERROR, &session->context->errout, |
2244 | 0 | "NMEA2000 open: fcntl(O_NONBLOCK) %s(%d).\n", |
2245 | 0 | strerror(errno), errno); |
2246 | 0 | close(sock); |
2247 | 0 | return -1; |
2248 | 0 | } |
2249 | | |
2250 | | // turn on CANBUS error reporting |
2251 | 0 | err_mask = CAN_ERR_ACK | CAN_ERR_BUSOFF | CAN_ERR_CRTL | CAN_ERR_LOSTARB | |
2252 | 0 | CAN_ERR_PROT | CAN_ERR_RESTARTED | CAN_ERR_TRX | |
2253 | 0 | CAN_ERR_TX_TIMEOUT; |
2254 | |
|
2255 | 0 | status = setsockopt(sock, SOL_CAN_RAW, CAN_RAW_ERR_FILTER, |
2256 | 0 | &err_mask, sizeof(err_mask)); |
2257 | 0 | if (0 != status) { |
2258 | 0 | GPSD_LOG(LOG_ERROR, &session->context->errout, |
2259 | 0 | "NMEA2000 open: setsockopt() %s(%d)\n", |
2260 | 0 | strerror(errno), errno); |
2261 | 0 | } |
2262 | | |
2263 | | /* enbiggen the receiver buffer size |
2264 | | * try SO_RCVBUFFORCE first, if we run with CAP_NET_ADMIN */ |
2265 | 0 | if (0 > setsockopt(sock, SOL_SOCKET, SO_RCVBUFFORCE, |
2266 | 0 | &rcvbuf_size, sizeof(rcvbuf_size))) { |
2267 | 0 | GPSD_LOG(LOG_ERROR, &session->context->errout, |
2268 | 0 | "NMEA2000 open:SO_RCVBUFFORCE failed try RCVBUF. " |
2269 | 0 | "%s(%d)\n", |
2270 | 0 | strerror(errno), errno); |
2271 | 0 | if (0 > setsockopt(sock, SOL_SOCKET, SO_RCVBUF, |
2272 | 0 | &rcvbuf_size, sizeof(rcvbuf_size))) { |
2273 | 0 | GPSD_LOG(LOG_ERROR, &session->context->errout, |
2274 | 0 | "NMEA2000 open:setsockopt(SO_RCVBUF) %s(%d).\n", |
2275 | 0 | strerror(errno), errno); |
2276 | 0 | } |
2277 | 0 | } |
2278 | 0 | if (0 > getsockopt(sock, SOL_SOCKET, SO_RCVBUF, |
2279 | 0 | &curr_rcvbuf_size, &curr_rcvbuf_size_len)) { |
2280 | 0 | GPSD_LOG(LOG_ERROR, &session->context->errout, |
2281 | 0 | "NMEA2000 open:getsockopt(SO_RCVBUF) %s(%d)\n", |
2282 | 0 | strerror(errno), errno); |
2283 | 0 | } else { |
2284 | 0 | GPSD_LOG(LOG_NOTICE, &session->context->errout, |
2285 | 0 | "NMEA2000 open:getsockopt(SO_RCVBUF) = %d\n", |
2286 | 0 | curr_rcvbuf_size); |
2287 | 0 | } |
2288 | | |
2289 | | // Locate the interface you wish to use |
2290 | 0 | (void)strlcpy(ifr.ifr_name, interface_name, sizeof(ifr.ifr_name)); |
2291 | | // ifr.ifr_ifindex gets filled with that device's index |
2292 | 0 | status = ioctl(sock, SIOCGIFINDEX, &ifr); |
2293 | |
|
2294 | 0 | if (0 != status) { |
2295 | 0 | GPSD_LOG(LOG_ERROR, &session->context->errout, |
2296 | 0 | "NMEA2000 open: can not find CAN device.\n"); |
2297 | 0 | close(sock); |
2298 | 0 | return -1; |
2299 | 0 | } |
2300 | | |
2301 | | // Select that CAN interface, and bind the socket to it. |
2302 | 0 | addr.can_family = AF_CAN; |
2303 | 0 | addr.can_ifindex = ifr.ifr_ifindex; |
2304 | 0 | status = bind(sock, (struct sockaddr*)&addr, sizeof(addr) ); |
2305 | 0 | if (0 != status) { |
2306 | 0 | GPSD_LOG(LOG_ERROR, &session->context->errout, |
2307 | 0 | "NMEA2000 open: bind failed.\n"); |
2308 | 0 | close(sock); |
2309 | 0 | return -1; |
2310 | 0 | } |
2311 | | |
2312 | 0 | gpsd_switch_driver(session, "NMEA2000"); |
2313 | 0 | session->gpsdata.gps_fd = sock; |
2314 | 0 | session->sourcetype = SOURCE_CAN; |
2315 | 0 | session->servicetype = SERVICE_SENSOR; |
2316 | 0 | session->driver.nmea2000.can_net = can_net; |
2317 | |
|
2318 | 0 | (void)strlcpy(can_interface_name[can_net], |
2319 | 0 | interface_name, sizeof(can_interface_name[0])); |
2320 | |
|
2321 | 0 | if (NULL == sa_ptr) { |
2322 | | // Only include EFF CAN frames |
2323 | 0 | can_filter.can_mask = CAN_EFF_FLAG | CAN_RTR_FLAG; |
2324 | 0 | can_filter.can_id = CAN_EFF_FLAG; |
2325 | |
|
2326 | 0 | session->driver.nmea2000.sa_valid = false; |
2327 | | // no source addr, yet. |
2328 | 0 | memset(nmea2000_units[can_net], 0, sizeof(nmea2000_units[can_net])); |
2329 | 0 | } else { |
2330 | | // Only include EFF CAN frames with the specific source address |
2331 | 0 | can_filter.can_mask = CAN_EFF_FLAG | CAN_RTR_FLAG | 0xff; |
2332 | 0 | can_filter.can_id = CAN_EFF_FLAG | source_addr; |
2333 | |
|
2334 | 0 | nmea2000_units[can_net][source_addr] = session; |
2335 | 0 | session->driver.nmea2000.source_addr = source_addr; |
2336 | 0 | session->driver.nmea2000.sa_valid = true; |
2337 | 0 | } |
2338 | |
|
2339 | 0 | status = setsockopt(sock, SOL_CAN_RAW, CAN_RAW_FILTER, |
2340 | 0 | &can_filter, sizeof(can_filter)); |
2341 | 0 | if (0 != status) { |
2342 | 0 | GPSD_LOG(LOG_ERROR, &session->context->errout, |
2343 | 0 | "NMEA2000 open:setsockopt(CAN_RAW_FILTER) %s(%d).\n", |
2344 | 0 | strerror(errno), errno); |
2345 | 0 | close(sock); |
2346 | 0 | session->gpsdata.gps_fd = -1; |
2347 | 0 | return -1; |
2348 | 0 | } |
2349 | | |
2350 | | // how do we know the speed??? |
2351 | 0 | session->gpsdata.dev.parity = 'N'; |
2352 | 0 | session->gpsdata.dev.baudrate = 250000; |
2353 | 0 | session->gpsdata.dev.stopbits = 0; |
2354 | 0 | return session->gpsdata.gps_fd; |
2355 | 0 | } |
2356 | | |
2357 | | void nmea2000_close(struct gps_device_t *session) |
2358 | 0 | { |
2359 | 0 | if (BAD_SOCKET(session->gpsdata.gps_fd)) { |
2360 | 0 | return; |
2361 | 0 | } |
2362 | | |
2363 | | // cast for 32-bit ints. |
2364 | 0 | GPSD_LOG(LOG_PROG, &session->context->errout, |
2365 | 0 | "NMEA2000: close(%ld) in nmea2000_close(%s)\n", |
2366 | 0 | (long)session->gpsdata.gps_fd, session->gpsdata.dev.path); |
2367 | 0 | (void)close(session->gpsdata.gps_fd); |
2368 | 0 | INVALIDATE_SOCKET(session->gpsdata.gps_fd); |
2369 | |
|
2370 | 0 | if (session->driver.nmea2000.sa_valid) { |
2371 | 0 | unsigned int l1, l2; |
2372 | |
|
2373 | 0 | for (l1 = 0; l1 < NMEA2000_NETS; l1++) { |
2374 | 0 | for (l2 = 0; l2 < NMEA2000_ADDRS; l2++) { |
2375 | 0 | if (session == nmea2000_units[l1][l2]) { |
2376 | 0 | memset(&session->driver.nmea2000, 0, |
2377 | 0 | sizeof(session->driver.nmea2000)); |
2378 | | nmea2000_units[l1][l2] = NULL; |
2379 | 0 | } |
2380 | 0 | } |
2381 | 0 | } |
2382 | 0 | } |
2383 | 0 | } |
2384 | | |
2385 | | // *INDENT-OFF* |
2386 | | const struct gps_type_t driver_nmea2000 = { |
2387 | | .type_name = "NMEA2000", // full name of type |
2388 | | .packet_type = NMEA2000_PACKET, // associated lexer packet type |
2389 | | .flags = DRIVER_STICKY, // remember this |
2390 | | .trigger = NULL, // detect their main sentence |
2391 | | .channels = 12, // not an actual GPS at all |
2392 | | .probe_detect = NULL, |
2393 | | .get_packet = nmea2000_get, // how to get a packet |
2394 | | .parse_packet = nmea2000_parse_input, // how to interpret a packet |
2395 | | .rtcm_writer = NULL, // Don't send RTCM to this |
2396 | | .init_query = NULL, // non-perturbing query |
2397 | | .event_hook = NULL, |
2398 | | .speed_switcher = NULL, // no speed switcher |
2399 | | .mode_switcher = NULL, // no mode switcher |
2400 | | .rate_switcher = NULL, // no rate switcher |
2401 | | .min_cycle.tv_sec = 1, // not relevant, no rate switch |
2402 | | .min_cycle.tv_nsec = 0, // not relevant, no rate switch |
2403 | | .control_send = NULL, // how to send control strings |
2404 | | .time_offset = NULL, |
2405 | | }; |
2406 | | // *INDENT-ON* |
2407 | | |
2408 | | // end |
2409 | | |
2410 | | #else // of defined(NMEA2000_ENABLE) |
2411 | | /* dummy variable to some old linkers do not complain about empty |
2412 | | * object file */ |
2413 | | int nmea2000_dummy = 1; |
2414 | | #endif // of defined(NMEA2000_ENABLE) |
2415 | | |
2416 | | // vim: set expandtab shiftwidth=4 |