12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793 |
- /*
- * This file is Copyright 2010 by the GPSD project
- * SPDX-License-Identifier: BSD-2-clause
- */
- #include "../include/gpsd_config.h" /* must be before all includes */
- #include <assert.h>
- #include <ctype.h>
- #include <stdarg.h>
- #include <stdbool.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <unistd.h>
- #include "../include/bits.h" // for getbeu16(), to extract big-endian words
- #include "../include/compiler.h" // for FALLTHROUGH
- #include "../include/gpsd.h"
- #include "../include/strfuncs.h"
- ssize_t generic_get(struct gps_device_t *session)
- {
- return packet_get(session->gpsdata.gps_fd, &session->lexer);
- }
- // This handles only bad, comment, and maybe NMEA packets.
- gps_mask_t generic_parse_input(struct gps_device_t *session)
- {
- if (BAD_PACKET == session->lexer.type ||
- COMMENT_PACKET == session->lexer.type) {
- // ignore bad packets and comment packets
- return 0;
- }
- if (NMEA_PACKET == session->lexer.type) {
- const struct gps_type_t **dp;
- gps_mask_t st = 0;
- char *sentence = (char *)session->lexer.outbuffer;
- if ('\n' != sentence[strlen(sentence)-1]) {
- GPSD_LOG(LOG_IO, &session->context->errout,
- "<= GPS: %s\n", sentence);
- } else {
- GPSD_LOG(LOG_IO, &session->context->errout,
- "<= GPS: %s", sentence);
- }
- if (0 == (st=nmea_parse(sentence, session))) {
- GPSD_LOG(LOG_WARN, &session->context->errout,
- "unknown sentence: \"%s\"\n", sentence);
- }
- for (dp = gpsd_drivers; *dp; dp++) {
- char *trigger = (*dp)->trigger;
- if (trigger!=NULL && str_starts_with(sentence, trigger)) {
- GPSD_LOG(LOG_PROG, &session->context->errout,
- "found trigger string %s.\n", trigger);
- if (*dp != session->device_type) {
- (void)gpsd_switch_driver(session, (*dp)->type_name);
- if (session->device_type != NULL
- && session->device_type->event_hook != NULL)
- session->device_type->event_hook(session,
- event_triggermatch);
- st |= DEVICEID_SET;
- }
- }
- }
- return st;
- }
- GPSD_LOG(LOG_SHOUT, &session->context->errout,
- "packet type %d fell through (should never happen): %s.\n",
- session->lexer.type, gpsd_prettydump(session));
- return 0;
- }
- /**************************************************************************
- *
- * Generic driver -- make no assumptions about the device type
- *
- **************************************************************************/
- /* *INDENT-OFF* */
- const struct gps_type_t driver_unknown = {
- .type_name = "Unknown", /* full name of type */
- .packet_type = COMMENT_PACKET, /* associated lexer packet type */
- .flags = DRIVER_NOFLAGS, /* no flags set */
- .trigger = NULL, /* it's the default */
- .channels = 12, /* consumer-grade GPS */
- .probe_detect = NULL, /* no probe */
- .get_packet = generic_get, /* use generic packet getter */
- .parse_packet = generic_parse_input, /* how to interpret a packet */
- .rtcm_writer = NULL, /* write RTCM data straight */
- .init_query = NULL, /* non-perturbing initial query */
- .event_hook = NULL, /* lifetime event handler */
- .speed_switcher = NULL, /* no speed switcher */
- .mode_switcher = NULL, /* no mode switcher */
- .rate_switcher = NULL, /* no sample-rate switcher */
- .min_cycle.tv_sec = 1, /* not relevant, no rate switch */
- .min_cycle.tv_nsec = 0, /* not relevant, no rate switch */
- .control_send = NULL, /* how to send control strings */
- .time_offset = NULL, /* no method for NTP fudge factor */
- };
- /* *INDENT-ON* */
- /**************************************************************************
- *
- * NMEA 0183
- *
- * This is separate from the 'unknown' driver because we don't want to
- * ship NMEA subtype probe strings to a device until we've seen at
- * least one NMEA packet. This avoids spamming devices that might
- * actually be USB modems or other things in USB device class FF that
- * just happen to have one of 'our' adaptor chips in front of them.
- *
- **************************************************************************/
- static void nmea_event_hook(struct gps_device_t *session, event_t event)
- {
- if (session->context->readonly)
- return;
- /*
- * This is where we try to tickle NMEA devices into revealing their
- * inner natures.
- */
- if (event == event_configure) {
- /*
- * The reason for splitting these probes up by packet sequence
- * number, interleaving them with the first few packet receives,
- * is because many generic-NMEA devices get confused if you send
- * too much at them in one go.
- *
- * A fast response to an early probe will change drivers so the
- * later ones won't be sent at all. Thus, for best overall
- * performance, order these to probe for the most popular types
- * soonest.
- *
- * Note: don't make the trigger strings identical to the probe,
- * because some NMEA devices (notably SiRFs) will just echo
- * unknown strings right back at you. A useful dodge is to append
- * a comma to the trigger, because that won't be in the response
- * unless there is actual following data.
- */
- switch (session->lexer.counter) {
- case 0:
- /* probe for Garmin serial GPS -- expect $PGRMC followed by data */
- GPSD_LOG(LOG_PROG, &session->context->errout,
- "=> Probing for Garmin NMEA\n");
- (void)nmea_send(session, "$PGRMCE");
- break;
- #ifdef SIRF_ENABLE
- case 1:
- /*
- * We used to try to probe for SiRF by issuing
- * "$PSRF105,1" and expecting "$Ack Input105.". But it
- * turns out this only works for SiRF-IIs; SiRF-I and
- * SiRF-III don't respond. Sadly, the MID132 binary
- * request for firmware version is ignored in NMEA mode.
- * Thus the only reliable probe is to try to flip the SiRF
- * into binary mode, cluing in the library to revert it on
- * close.
- *
- * SiRFs dominate the consumer-grade GPS-mouse market, so
- * we used to put this test first. Unfortunately this
- * causes problems for gpsctl, as it cannot select the
- * NMEA driver without switching the device back to binary
- * mode! Fix this if we ever find a nondisruptive probe
- * string.
- */
- GPSD_LOG(LOG_PROG, &session->context->errout,
- "=> Probing for SiRF\n");
- (void)nmea_send(session,
- "$PSRF100,0,%d,%d,%d,0",
- session->gpsdata.dev.baudrate,
- 9 - session->gpsdata.dev.stopbits,
- session->gpsdata.dev.stopbits);
- break;
- #endif /* SIRF_ENABLE */
- case 2:
- /* probe for the FV-18 -- expect $PFEC,GPint followed by data */
- GPSD_LOG(LOG_PROG, &session->context->errout,
- "=> Probing for FV-18\n");
- (void)nmea_send(session, "$PFEC,GPint");
- break;
- case 3:
- /* probe for the Trimble Copernicus */
- GPSD_LOG(LOG_PROG, &session->context->errout,
- "=> Probing for Trimble Copernicus\n");
- (void)nmea_send(session, "$PTNLSNM,0139,01");
- break;
- #ifdef EVERMORE_ENABLE
- case 4:
- GPSD_LOG(LOG_PROG, &session->context->errout,
- "=> Probing for Evermore\n");
- // FIXME: not passive compatible
- /* Enable checksum and GGA(1s), GLL(0s), GSA(1s), GSV(1s),
- * RMC(1s), VTG(0s), PEMT101(0s)
- * EverMore will reply with: \x10\x02\x04\x38\x8E\xC6\x10\x03 */
- (void)gpsd_write(session,
- "\x10\x02\x12\x8E\x7F\x01\x01\x00\x01\x01\x01"
- "\x00\x00\x00\x00\x00\x00\x00\x00\x13\x10\x03",
- 22);
- break;
- #endif /* EVERMORE_ENABLE */
- #ifdef GPSCLOCK_ENABLE
- case 5:
- /* probe for Furuno Electric GH-79L4-N (GPSClock);
- * expect $PFEC,GPssd */
- GPSD_LOG(LOG_PROG, &session->context->errout,
- "=> Probing for GPSClock\n");
- (void)nmea_send(session, "$PFEC,GPsrq");
- break;
- #endif /* GPSCLOCK_ENABLE */
- case 6:
- /* probe for Ashtech -- expect $PASHR,RID */
- GPSD_LOG(LOG_PROG, &session->context->errout,
- "=> Probing for Ashtech\n");
- (void)nmea_send(session, "$PASHQ,RID");
- break;
- #ifdef UBLOX_ENABLE
- case 7:
- // probe for UBX
- GPSD_LOG(LOG_PROG, &session->context->errout,
- "=> Probing for UBX\n");
- // query port configuration UBX-MON-VER
- (void)ubx_write(session, 0x0a, 0x04, NULL, 0);
- // query port configuration UBX-CFG-PRT
- (void)ubx_write(session, 0x06, 0x00, NULL, 0);
- break;
- #endif /* UBLOX_ENABLE */
- case 8:
- /* probe for MTK-3301 -- expect $PMTK705 */
- GPSD_LOG(LOG_PROG, &session->context->errout,
- "=> Probing for MediaTek\n");
- (void)nmea_send(session, "$PMTK605");
- break;
- #ifdef GREIS_ENABLE
- case 9:
- /* probe for Javad GREIS -- expect reply with JAVAD */
- GPSD_LOG(LOG_PROG, &session->context->errout,
- "=> Probing for Javad GREIS\n");
- /* TODO: confirm this actually gets JAVAD response */
- (void)nmea_send(session, "print,/par/rcv/vendor");
- break;
- #endif /* GREIS_ENABLE */
- default:
- break;
- }
- }
- }
- /* *INDENT-OFF* */
- const struct gps_type_t driver_nmea0183 = {
- .type_name = "NMEA0183", /* full name of type */
- .packet_type = NMEA_PACKET, /* associated lexer packet type */
- .flags = DRIVER_NOFLAGS, /* remember this */
- .trigger = NULL, /* it's the default */
- .channels = 12, /* consumer-grade GPS */
- .probe_detect = NULL, /* no probe */
- .get_packet = generic_get, /* use generic packet getter */
- .parse_packet = generic_parse_input, /* how to interpret a packet */
- .rtcm_writer = gpsd_write, /* write RTCM data straight */
- .init_query = NULL, /* non-perturbing initial query */
- .event_hook = nmea_event_hook, /* lifetime event handler */
- .speed_switcher = NULL, /* no speed switcher */
- #ifdef BINARY_ENABLE
- .mode_switcher = NULL, /* maybe switchable if it was a SiRF */
- #else
- .mode_switcher = NULL, /* no binary mode to revert to */
- #endif /* BINARY_ENABLE */
- .rate_switcher = NULL, /* no sample-rate switcher */
- .min_cycle.tv_sec = 1, /* not relevant, no rate switch */
- .min_cycle.tv_nsec = 0, /* not relevant, no rate switch */
- .control_send = nmea_write, /* how to send control strings */
- .time_offset = NULL, /* no method for NTP fudge factor */
- };
- /* *INDENT-ON* */
- #if defined(GARMIN_ENABLE)
- /**************************************************************************
- *
- * Garmin NMEA
- *
- **************************************************************************/
- static void garmin_mode_switch(struct gps_device_t *session, int mode)
- /* only does anything in one direction, going to Garmin binary driver */
- {
- struct timespec delay;
- if (mode == MODE_BINARY) {
- (void)nmea_send(session, "$PGRMC1,1,2,1,,,,2,W,N");
- (void)nmea_send(session, "$PGRMI,,,,,,,R");
- /* wait 333 uSec, standard Garmin settling time */
- delay.tv_sec = 0;
- delay.tv_nsec = 333000L;
- nanosleep(&delay, NULL);
- }
- }
- static void garmin_nmea_event_hook(struct gps_device_t *session,
- event_t event)
- {
- if (session->context->readonly)
- return;
- if (event == event_driver_switch) {
- /* forces a reconfigure as the following packets come in */
- session->lexer.counter = 0;
- }
- if (session->context->passive) {
- return;
- }
- if (event == event_configure) {
- /*
- * And here's that reconfigure. It's split up like this because
- * receivers like the Garmin GPS-10 don't handle having having a lot of
- * probes shoved at them very well.
- */
- switch (session->lexer.counter) {
- case 0:
- /* reset some config, AutoFix, WGS84, PPS
- * Set the PPS pulse length to 40ms which leaves the Garmin 18-5hz
- * with a 160ms low state.
- * NOTE: new PPS only takes effect after next power cycle
- */
- (void)nmea_send(session, "$PGRMC,A,,100,,,,,,A,,1,2,1,30");
- break;
- case 1:
- /* once a sec, no averaging, NMEA 2.3, WAAS */
- (void)nmea_send(session, "$PGRMC1,1,1,1,,,,2,W,N");
- break;
- case 2:
- /* get some more config info */
- (void)nmea_send(session, "$PGRMC1E");
- break;
- case 3:
- /* turn off all output except GGA */
- (void)nmea_send(session, "$PGRMO,,2");
- (void)nmea_send(session, "$PGRMO,GPGGA,1");
- break;
- case 4:
- /* enable GPGGA, GPGSA, GPGSV, GPRMC on Garmin serial GPS */
- (void)nmea_send(session, "$PGRMO,GPGSA,1");
- break;
- case 5:
- (void)nmea_send(session, "$PGRMO,GPGSV,1");
- break;
- case 6:
- (void)nmea_send(session, "$PGRMO,GPRMC,1");
- break;
- case 7:
- (void)nmea_send(session, "$PGRMO,PGRME,1");
- break;
- }
- }
- }
- /* *INDENT-OFF* */
- const struct gps_type_t driver_garmin = {
- .type_name = "Garmin NMEA", /* full name of type */
- .packet_type = NMEA_PACKET, /* associated lexer packet type */
- .flags = DRIVER_STICKY, /* remember this */
- .trigger = "$PGRMC,", /* Garmin private */
- .channels = 12, /* not used by this driver */
- .probe_detect = NULL, /* no probe */
- .get_packet = generic_get, /* use generic packet getter */
- .parse_packet = generic_parse_input, /* how to interpret a packet */
- .rtcm_writer = NULL, /* some do, some don't, skip for now */
- .init_query = NULL, /* non-perturbing initial query */
- .event_hook = garmin_nmea_event_hook, /* lifetime event handler */
- .speed_switcher = NULL, /* no speed switcher */
- .mode_switcher = garmin_mode_switch, /* mode switcher */
- .rate_switcher = NULL, /* no sample-rate switcher */
- .min_cycle.tv_sec = 1, /* not relevant, no rate switch */
- .min_cycle.tv_nsec = 0, /* not relevant, no rate switch */
- .control_send = nmea_write, /* how to send control strings */
- .time_offset = NULL, /* no method for NTP fudge factor */
- };
- // *INDENT-ON*
- #endif // GARMIN_ENABLE
- /**************************************************************************
- *
- * Ashtech (then Thales, now Magellan Professional) Receivers
- *
- **************************************************************************/
- static void ashtech_event_hook(struct gps_device_t *session, event_t event)
- {
- if (session->context->readonly)
- return;
- if (event == event_wakeup)
- (void)nmea_send(session, "$PASHQ,RID");
- if ( session->context->passive) {
- return;
- }
- if (event == event_identified) {
- /* turn WAAS on. can't hurt... */
- (void)nmea_send(session, "$PASHS,WAS,ON");
- /* reset to known output state */
- (void)nmea_send(session, "$PASHS,NME,ALL,A,OFF");
- /* then turn on some useful sentences */
- #ifdef __future__
- /* we could parse these, but they're oversize so they get dropped */
- (void)nmea_send(session, "$PASHS,NME,POS,A,ON");
- (void)nmea_send(session, "$PASHS,NME,SAT,A,ON");
- #else
- (void)nmea_send(session, "$PASHS,NME,GGA,A,ON");
- (void)nmea_send(session, "$PASHS,NME,GSA,A,ON");
- (void)nmea_send(session, "$PASHS,NME,GSV,A,ON");
- (void)nmea_send(session, "$PASHS,NME,RMC,A,ON");
- #endif
- (void)nmea_send(session, "$PASHS,NME,ZDA,A,ON");
- }
- }
- /* *INDENT-OFF* */
- const struct gps_type_t driver_ashtech = {
- .type_name = "Ashtech", /* full name of type */
- .packet_type = NMEA_PACKET, /* associated lexer packet type */
- .flags = DRIVER_STICKY, /* remember this */
- .trigger = "$PASHR,RID,", /* Ashtech receivers respond thus */
- .channels = 24, /* not used, GG24 has 24 channels */
- .probe_detect = NULL, /* no probe */
- .get_packet = generic_get, /* how to get a packet */
- .parse_packet = generic_parse_input, /* how to interpret a packet */
- .rtcm_writer = gpsd_write, /* write RTCM data straight */
- .init_query = NULL, /* non-perturbing initial query */
- .event_hook = ashtech_event_hook, /* lifetime event handler */
- .speed_switcher = NULL, /* no speed switcher */
- .mode_switcher = NULL, /* no mode switcher */
- .rate_switcher = NULL, /* no sample-rate switcher */
- .min_cycle.tv_sec = 1, /* not relevant, no rate switch */
- .min_cycle.tv_nsec = 0, /* not relevant, no rate switch */
- .control_send = nmea_write, /* how to send control strings */
- .time_offset = NULL, /* no method for NTP fudge factor */
- };
- /* *INDENT-ON* */
- #ifdef FV18_ENABLE
- /**************************************************************************
- *
- * FV18 -- uses 2 stop bits, needs to be told to send GSAs
- *
- **************************************************************************/
- static void fv18_event_hook(struct gps_device_t *session, event_t event)
- {
- if (session->context->readonly)
- return;
- /*
- * Tell an FV18 to send GSAs so we'll know if 3D is accurate.
- * Suppress GLL and VTG. Enable ZDA so dates will be accurate for replay.
- * It's possible we might not need to redo this on event_reactivate,
- * but doing so is safe and cheap.
- */
- if (event == event_identified || event == event_reactivate)
- (void)nmea_send(session,
- "$PFEC,GPint,GSA01,DTM00,ZDA01,RMC01,GLL00,VTG00,GSV05");
- }
- /* *INDENT-OFF* */
- const struct gps_type_t driver_fv18 = {
- .type_name = "San Jose Navigation FV18", /* full name of type */
- .packet_type = NMEA_PACKET, /* associated lexer packet type */
- .flags = DRIVER_STICKY, /* remember this */
- .trigger = "$PFEC,GPint,", /* FV18s should echo the probe */
- .channels = 12, /* not used by this driver */
- .probe_detect = NULL, /* no probe */
- .get_packet = generic_get, /* how to get a packet */
- .parse_packet = generic_parse_input, /* how to interpret a packet */
- .rtcm_writer = gpsd_write, /* write RTCM data straight */
- .init_query = NULL, /* non-perturbing initial query */
- .event_hook = fv18_event_hook, /* lifetime event handler */
- .speed_switcher = NULL, /* no speed switcher */
- .mode_switcher = NULL, /* no mode switcher */
- .rate_switcher = NULL, /* no sample-rate switcher */
- .min_cycle.tv_sec = 1, /* not relevant, no rate switch */
- .min_cycle.tv_nsec = 0, /* not relevant, no rate switch */
- .control_send = nmea_write, /* how to send control strings */
- .time_offset = NULL, /* no method for NTP fudge factor */
- };
- /* *INDENT-ON* */
- #endif /* FV18_ENABLE */
- #ifdef GPSCLOCK_ENABLE
- /**************************************************************************
- *
- * Furuno Electric GPSClock (GH-79L4)
- *
- **************************************************************************/
- /*
- * Based on http://www.tecsys.de/fileadmin/user_upload/pdf/gh79_1an_intant.pdf
- */
- /* *INDENT-OFF* */
- const struct gps_type_t driver_gpsclock = {
- .type_name = "Furuno Electric GH-79L4", /* full name of type */
- .packet_type = NMEA_PACKET, /* associated lexer packet type */
- .flags = DRIVER_STICKY, /* remember this */
- .trigger = "$PFEC,GPssd", /* GPSClock should return this */
- .channels = 12, /* not used by this driver */
- .probe_detect = NULL, /* no probe */
- .get_packet = generic_get, /* how to get a packet */
- .parse_packet = generic_parse_input, /* how to interpret a packet */
- .rtcm_writer = gpsd_write, /* write RTCM data straight */
- .init_query = NULL, /* non-perturbing initial query */
- .event_hook = NULL, /* no lifetime event handler */
- .speed_switcher = NULL, /* no speed switcher */
- .mode_switcher = NULL, /* no mode switcher */
- .rate_switcher = NULL, /* sample rate is fixed */
- .min_cycle.tv_sec = 1, /* not relevant, no rate switch */
- .min_cycle.tv_nsec = 0, /* not relevant, no rate switch */
- .control_send = nmea_write, /* how to send control strings */
- .time_offset = NULL, /* no method for NTP fudge factor */
- };
- /* *INDENT-ON* */
- #endif /* GPSCLOCK_ENABLE */
- #ifdef TRIPMATE_ENABLE
- /**************************************************************************
- *
- * TripMate -- extended NMEA, gets faster fix when primed with lat/long/time
- *
- **************************************************************************/
- /*
- * Some technical FAQs on the TripMate:
- * http://vancouver-webpages.com/pub/peter/tripmate.faq
- * http://www.asahi-net.or.jp/~KN6Y-GTU/tripmate/trmfaqe.html
- * The TripMate was discontinued sometime before November 1998
- * and was replaced by the Zodiac EarthMate.
- */
- static void tripmate_event_hook(struct gps_device_t *session, event_t event)
- {
- if (session->context->readonly)
- return;
- /* TripMate requires this response to the ASTRAL it sends at boot time */
- if (event == event_identified)
- (void)nmea_send(session, "$IIGPQ,ASTRAL");
- /* stop it sending PRWIZCH */
- if (event == event_identified || event == event_reactivate)
- (void)nmea_send(session, "$PRWIILOG,ZCH,V,,");
- }
- /* *INDENT-OFF* */
- static const struct gps_type_t driver_tripmate = {
- .type_name = "Delorme TripMate", /* full name of type */
- .packet_type = NMEA_PACKET, /* lexer packet type */
- .flags = DRIVER_STICKY, /* no rollover or other flags */
- .trigger ="ASTRAL", /* tells us to switch */
- .channels = 12, /* consumer-grade GPS */
- .probe_detect = NULL, /* no probe */
- .get_packet = generic_get, /* how to get a packet */
- .parse_packet = generic_parse_input, /* how to interpret a packet */
- .rtcm_writer = gpsd_write, /* send RTCM data straight */
- .init_query = NULL, /* non-perturbing query */
- .event_hook = tripmate_event_hook, /* lifetime event handler */
- .speed_switcher= NULL, /* no speed switcher */
- .mode_switcher = NULL, /* no mode switcher */
- .rate_switcher = NULL, /* no sample-rate switcher */
- .min_cycle.tv_sec = 1, /* not relevant, no rate switch */
- .min_cycle.tv_nsec = 0, /* not relevant, no rate switch */
- .control_send = nmea_write, /* how to send control strings */
- .time_offset = NULL, /* no method for NTP fudge factor */
- };
- /* *INDENT-ON* */
- #endif /* TRIPMATE_ENABLE */
- #ifdef EARTHMATE_ENABLE
- /**************************************************************************
- *
- * Zodiac EarthMate textual mode
- *
- * Note: This is the pre-2003 version using Zodiac binary protocol.
- * There is a good HOWTO at <http://www.hamhud.net/ka9mva/earthmate.htm>.
- * It has been replaced with a design that uses a SiRF chipset.
- *
- **************************************************************************/
- static void earthmate_event_hook(struct gps_device_t *session, event_t event)
- {
- struct timespec delay;
- if (session->context->readonly)
- return;
- if (event == event_triggermatch) {
- (void)gpsd_write(session, "EARTHA\r\n", 8);
- /* wait 10,000 uSec */
- delay.tv_sec = 0;
- delay.tv_nsec = 10000000L;
- nanosleep(&delay, NULL);
- (void)gpsd_switch_driver(session, "Zodiac");
- }
- }
- /* *INDENT-OFF* */
- static const struct gps_type_t driver_earthmate = {
- .type_name = "Pre-2003 Delorme EarthMate",
- .packet_type = NMEA_PACKET, /* associated lexer packet type */
- .flags = DRIVER_STICKY, /* no rollover or other flags */
- .trigger = "EARTHA", /* Earthmate trigger string */
- .channels = 12, /* not used by NMEA parser */
- .probe_detect = NULL, /* no probe */
- .get_packet = generic_get, /* how to get a packet */
- .parse_packet = generic_parse_input, /* how to interpret a packet */
- .rtcm_writer = NULL, /* don't send RTCM data */
- .init_query = NULL, /* non-perturbing query */
- .event_hook = earthmate_event_hook, /* lifetime event handler */
- .speed_switcher= NULL, /* no speed switcher */
- .mode_switcher = NULL, /* no mode switcher */
- .rate_switcher = NULL, /* no sample-rate switcher */
- .min_cycle.tv_sec = 1, /* not relevant, no rate switch */
- .min_cycle.tv_nsec = 0, /* not relevant, no rate switch */
- .control_send = nmea_write, /* never actually used. */
- .time_offset = NULL, /* no method for NTP fudge factor */
- };
- /* *INDENT-ON* */
- #endif /* EARTHMATE_ENABLE */
- #ifdef TNT_ENABLE
- /**************************************************************************
- * True North Technologies - Revolution 2X Digital compass
- *
- * More info: http://www.tntc.com/
- *
- * This is a digital compass which uses magnetometers to measure the
- * strength of the earth's magnetic field. Based on these measurements
- * it provides a compass heading using NMEA formatted output strings.
- * This is useful to supplement the heading provided by another GPS
- * unit. A GPS heading is unreliable at slow speed or no speed.
- *
- **************************************************************************/
- static ssize_t tnt_control_send(struct gps_device_t *session,
- char *msg, size_t len UNUSED)
- /* send a control string in TNT native formal */
- {
- ssize_t status;
- unsigned char sum = '\0';
- char c, *p = msg;
- if (*p == '@') {
- p++;
- }
- #ifdef __UNUSED__
- else {
- GPSD_LOG(LOG_ERROR, &session->context->errout,
- "Bad TNT sentence: '%s'\n", msg);
- }
- #endif /* __UNUSED__ */
- while (((c = *p) != '\0')) {
- sum ^= c;
- p++;
- }
- (void)snprintf(p, 6, "*%02X\r\n", (unsigned int)sum);
- status = gpsd_write(session, msg, strlen(msg));
- return status;
- }
- static bool tnt_send(struct gps_device_t *session, const char *fmt, ...)
- /* printf(3)-like TNT command generator */
- {
- char buf[BUFSIZ];
- va_list ap;
- ssize_t sent;
- va_start(ap, fmt);
- (void)vsnprintf(buf, sizeof(buf) - 5, fmt, ap);
- va_end(ap);
- sent = tnt_control_send(session, buf, strlen(buf));
- if (sent == (ssize_t) strlen(buf)) {
- GPSD_LOG(LOG_IO, &session->context->errout,
- "=> GPS: %s\n", buf);
- return true;
- } else {
- GPSD_LOG(LOG_WARN, &session->context->errout,
- "=> GPS: %s FAILED\n", buf);
- return false;
- }
- }
- static bool tnt_speed(struct gps_device_t *session,
- speed_t speed, char parity UNUSED, int stopbits UNUSED)
- {
- /*
- * Baud rate change followed by device reset.
- * See page 40 of Technical Guide 1555-B. We need:
- * 2400 -> 1, 4800 -> 2, 9600 -> 3, 19200 -> 4, 38400 -> 5
- */
- unsigned int val = speed / 2400u; // 2400->1, 4800->2, 9600->4, 19200->8...
- unsigned int i = 0;
- /* fast way to compute log2(val) */
- while ((val >> i) > 1)
- ++i;
- return tnt_send(session, "@B6=%d", i + 1)
- && tnt_send(session, "@F28.6=1");
- }
- /* TNT lifetime event hook */
- static void tnt_event_hook(struct gps_device_t *session, event_t event)
- {
- if (session->context->readonly)
- return;
- if (event == event_wakeup) {
- (void)tnt_send(session, "@F0.3=1"); /* set run mode */
- (void)tnt_send(session, "@F2.2=1"); /* report in degrees */
- }
- }
- /* *INDENT-OFF* */
- const struct gps_type_t driver_trueNorth = {
- .type_name = "True North", /* full name of type */
- .packet_type = NMEA_PACKET, /* associated lexer packet type */
- .flags = DRIVER_STICKY, /* remember this */
- .trigger = "$PTNTHTM", /* their proprietary sentence */
- .channels = 0, /* not an actual GPS at all */
- .probe_detect = NULL, /* no probe in run mode */
- .get_packet = generic_get, /* how to get a packet */
- .parse_packet = generic_parse_input, /* how to interpret a packet */
- .rtcm_writer = NULL, /* Don't send */
- .init_query = NULL, /* non-perturbing initial query */
- .event_hook = tnt_event_hook, /* lifetime event handler */
- .speed_switcher = tnt_speed, /* no speed switcher */
- .mode_switcher = NULL, /* no mode switcher */
- .rate_switcher = NULL, /* no wrapup */
- .min_cycle.tv_sec = 0, /* not relevant, no rate switch */
- .min_cycle.tv_nsec = 500000000, /* not relevant, no rate switch */
- .control_send = tnt_control_send, /* how to send control strings */
- .time_offset = NULL,
- };
- /* *INDENT-ON* */
- #endif
- #ifdef OCEANSERVER_ENABLE
- /**************************************************************************
- * OceanServer - Digital Compass, OS5000 Series
- *
- * More info: http://www.ocean-server.com/download/OS5000_Compass_Manual.pdf
- *
- * This is a digital compass which uses magnetometers to measure the
- * strength of the earth's magnetic field. Based on these measurements
- * it provides a compass heading using NMEA formatted output strings.
- * This is useful to supplement the heading provided by another GPS
- * unit. A GPS heading is unreliable at slow speed or no speed.
- *
- **************************************************************************/
- static int oceanserver_send(struct gpsd_errout_t *errout,
- const int fd, const char *fmt, ...)
- {
- int status;
- char buf[BUFSIZ];
- va_list ap;
- va_start(ap, fmt);
- (void)vsnprintf(buf, sizeof(buf) - 5, fmt, ap);
- va_end(ap);
- (void)strlcat(buf, "", sizeof(buf));
- status = (int)write(fd, buf, strlen(buf));
- (void)tcdrain(fd);
- if (status == (int)strlen(buf)) {
- GPSD_LOG(LOG_IO, errout, "=> GPS: %s\n", buf);
- return status;
- } else {
- GPSD_LOG(LOG_WARN, errout, "=> GPS: %s FAILED\n", buf);
- return -1;
- }
- }
- static void oceanserver_event_hook(struct gps_device_t *session,
- event_t event)
- {
- if (session->context->readonly)
- return;
- if (event == event_configure && session->lexer.counter == 0) {
- /* report in NMEA format */
- (void)oceanserver_send(&session->context->errout,
- session->gpsdata.gps_fd, "2\n");
- /* ship all fields */
- (void)oceanserver_send(&session->context->errout,
- session->gpsdata.gps_fd, "X2047");
- }
- }
- /* *INDENT-OFF* */
- static const struct gps_type_t driver_oceanServer = {
- .type_name = "OceanServer OS5000", /* full name of type */
- .packet_type = NMEA_PACKET, /* associated lexer packet type */
- .flags = DRIVER_STICKY, /* no rollover or other flags */
- .trigger = "$OHPR,", /* detect their main sentence */
- .channels = 0, /* not an actual GPS at all */
- .probe_detect = NULL,
- .get_packet = generic_get, /* how to get a packet */
- .parse_packet = generic_parse_input, /* how to interpret a packet */
- .rtcm_writer = NULL, /* Don't send */
- .init_query = NULL, /* non-perturbing initial query */
- .event_hook = oceanserver_event_hook,
- .speed_switcher = NULL, /* no speed switcher */
- .mode_switcher = NULL, /* no mode switcher */
- .rate_switcher = NULL, /* no wrapup */
- .min_cycle.tv_sec = 1, /* not relevant, no rate switch */
- .min_cycle.tv_nsec = 0, /* not relevant, no rate switch */
- .control_send = nmea_write, /* how to send control strings */
- .time_offset = NULL,
- };
- /* *INDENT-ON* */
- #endif
- #ifdef FURY_ENABLE
- /**************************************************************************
- *
- * Jackson Labs Fury, a high-precision laboratory clock
- *
- * Will also support other Jackon Labs boards, including the Firefly.
- *
- * Note: you must either build with fixed_port_speed=115200 or tweak the
- * speed on the port to 115200 before running. The device's default mode
- * does not stream output, so our hunt loop will simply time out otherwise.
- *
- **************************************************************************/
- static bool fury_rate_switcher(struct gps_device_t *session, double rate)
- {
- char buf[78];
- double inverted;
- /* rate is a frequency, but the command takes interval in # of seconds */
- if (rate == 0.0)
- inverted = 0.0;
- else
- inverted = 1.0/rate;
- if (inverted > 256)
- return false;
- (void)snprintf(buf, sizeof(buf), "GPS:GPGGA %d\r\n", (int)inverted);
- (void)gpsd_write(session, buf, strlen(buf));
- return true;
- }
- static void fury_event_hook(struct gps_device_t *session, event_t event)
- {
- if (event == event_wakeup && gpsd_get_speed(session) == 115200)
- (void)fury_rate_switcher(session, 1.0);
- else if (event == event_deactivate)
- (void)fury_rate_switcher(session, 0.0);
- }
- /* *INDENT-OFF* */
- static const struct gps_type_t driver_fury = {
- .type_name = "Jackson Labs Fury", /* full name of type */
- .packet_type = NMEA_PACKET, /* associated lexer packet type */
- .flags = DRIVER_STICKY, /* no rollover or other flags */
- .trigger = NULL, /* detect their main sentence */
- .channels = 0, /* not an actual GPS at all */
- .probe_detect = NULL,
- .get_packet = generic_get, /* how to get a packet */
- .parse_packet = generic_parse_input, /* how to interpret a packet */
- .rtcm_writer = NULL, /* Don't send */
- .init_query = NULL, /* non-perturbing initial query */
- .event_hook = fury_event_hook,
- .speed_switcher = NULL, /* no speed switcher */
- .mode_switcher = NULL, /* no mode switcher */
- .rate_switcher = fury_rate_switcher,
- .min_cycle.tv_sec = 1, /* not relevant, no rate switch */
- .min_cycle.tv_nsec = 0, /* not relevant, no rate switch */
- .control_send = nmea_write, /* how to send control strings */
- .time_offset = NULL,
- };
- /* *INDENT-ON* */
- #endif /* FURY_ENABLE */
- #ifdef RTCM104V2_ENABLE
- /**************************************************************************
- *
- * RTCM-104 (v2), used for broadcasting DGPS corrections and by DGPS radios
- *
- **************************************************************************/
- static gps_mask_t rtcm104v2_analyze(struct gps_device_t *session)
- {
- rtcm2_unpack(session, &session->gpsdata.rtcm2,
- (char *)session->lexer.isgps.buf);
- session->cycle_end_reliable = true;
- return RTCM2_SET;
- }
- /* *INDENT-OFF* */
- static const struct gps_type_t driver_rtcm104v2 = {
- .type_name = "RTCM104V2", /* full name of type */
- .packet_type = RTCM2_PACKET, /* associated lexer packet type */
- .flags = DRIVER_NOFLAGS, /* no rollover or other flags */
- .trigger = NULL, /* no recognition string */
- .channels = 0, /* not used */
- .probe_detect = NULL, /* no probe */
- .get_packet = generic_get, /* how to get a packet */
- .parse_packet = rtcm104v2_analyze, /* */
- .rtcm_writer = NULL, /* don't send RTCM data, */
- .init_query = NULL, /* non-perturbing initial query */
- .event_hook = NULL, /* no event_hook */
- .speed_switcher= NULL, /* no speed switcher */
- .mode_switcher = NULL, /* no mode switcher */
- .rate_switcher = NULL, /* no sample-rate switcher */
- .min_cycle.tv_sec = 1, /* not relevant, no rate switch */
- .min_cycle.tv_nsec = 0, /* not relevant, no rate switch */
- .control_send = NULL, /* how to send control strings */
- .time_offset = NULL,
- };
- /* *INDENT-ON* */
- #endif /* RTCM104V2_ENABLE */
- #ifdef RTCM104V3_ENABLE
- /**************************************************************************
- *
- * RTCM-104 (v3), used for broadcasting DGPS corrections and by DGPS radios
- *
- **************************************************************************/
- static gps_mask_t rtcm104v3_analyze(struct gps_device_t *session)
- {
- uint16_t type = getbeu16(session->lexer.outbuffer, 3) >> 4;
- GPSD_LOG(LOG_RAW, &session->context->errout, "RTCM 3.x packet %d\n", type);
- rtcm3_unpack(session->context,
- &session->gpsdata.rtcm3,
- (char *)session->lexer.outbuffer);
- session->cycle_end_reliable = true;
- return RTCM3_SET;
- }
- /* *INDENT-OFF* */
- static const struct gps_type_t driver_rtcm104v3 = {
- .type_name = "RTCM104V3", /* full name of type */
- .packet_type = RTCM3_PACKET, /* associated lexer packet type */
- .flags = DRIVER_NOFLAGS, /* no rollover or other flags */
- .trigger = NULL, /* no recognition string */
- .channels = 0, /* not used */
- .probe_detect = NULL, /* no probe */
- .get_packet = generic_get, /* how to get a packet */
- .parse_packet = rtcm104v3_analyze, /* */
- .rtcm_writer = NULL, /* don't send RTCM data, */
- .init_query = NULL, /* non-perturbing initial query */
- .event_hook = NULL, /* no event hook */
- .speed_switcher= NULL, /* no speed switcher */
- .mode_switcher = NULL, /* no mode switcher */
- .rate_switcher = NULL, /* no sample-rate switcher */
- .min_cycle.tv_sec = 1, /* not relevant, no rate switch */
- .min_cycle.tv_nsec = 0, /* not relevant, no rate switch */
- .control_send = NULL, /* how to send control strings */
- .time_offset = NULL,
- };
- /* *INDENT-ON* */
- #endif /* RTCM104V3_ENABLE */
- #ifdef GARMINTXT_ENABLE
- /**************************************************************************
- *
- * Garmin Simple Text protocol
- *
- **************************************************************************/
- /* *INDENT-OFF* */
- static const struct gps_type_t driver_garmintxt = {
- .type_name = "Garmin Simple Text", /* full name of type */
- .packet_type = GARMINTXT_PACKET, /* associated lexer packet type */
- .flags = DRIVER_NOFLAGS, /* no rollover or other flags */
- .trigger = NULL, /* no recognition string */
- .channels = 0, /* not used */
- .probe_detect = NULL, /* no probe */
- .get_packet = generic_get, /* how to get a packet */
- .parse_packet = garmintxt_parse, /* how to parse one */
- .rtcm_writer = NULL, /* don't send RTCM data, */
- .init_query = NULL, /* non-perturbing initial query */
- .event_hook = NULL, /* no event hook */
- .speed_switcher= NULL, /* no speed switcher */
- .mode_switcher = NULL, /* no mode switcher */
- .rate_switcher = NULL, /* no sample-rate switcher */
- .min_cycle.tv_sec = 1, /* not relevant, no rate switch */
- .min_cycle.tv_nsec = 0, /* not relevant, no rate switch */
- .control_send = NULL, /* how to send control strings */
- .time_offset = NULL,
- };
- /* *INDENT-ON* */
- #endif /* GARMINTXT_ENABLE */
- /**************************************************************************
- *
- * MediaTek MTK-3301, 3329, 3333, 3339
- *
- * OEMs for several GPS vendors, notably including Garmin, FasTrax, Trimble,
- * and AdaFruit. Website at <http://www.mediatek.com/>.
- *
- * The Trimble Condor appears to be an MTK3329. It behaves as an MTK3301
- * and positively acknowledges all 3301 sentences as valid. It ignores $PMTK
- * sentence fields that are not implemented in the Trimble Condor. It does
- * not have power-save mode and ignores $PMTK320. For $PMTK314 it silently
- * ignores periodic enabling of messages that aren't supported.
- *
- * From its datasheet the MTK3339 seems to add QZSS support not present in
- * earlier versions. The data sheet says it has 66 channels, which mkes
- * sense given the multi-constellation capability. The channel count
- * in the driver is never used by the NMEA driver so leaving the lower MTK3301
- * value in there is OK.
- *
- * MTK3333 support 10Hz.
- *
- * The Adafruit GPS HAT for the Raspberry Pi is an MTK3339. It works with this
- * driver; in fact AdaFruit's overview page for the product features GPSD.
- *
- * SIMCom DIM68M appears to be an MTK3333.
- *
- **************************************************************************/
- static void mtk3301_event_hook(struct gps_device_t *session, event_t event)
- {
- /*
- 0 NMEA_SEN_GLL, GPGLL interval - Geographic Position - Latitude longitude
- 1 NMEA_SEN_RMC, GPRMC interval - Recommended Minimum Specific GNSS Sentence
- 2 NMEA_SEN_VTG, GPVTG interval - Course Over Ground and Ground Speed
- 3 NMEA_SEN_GGA, GPGGA interval - GPS Fix Data
- 4 NMEA_SEN_GSA, GPGSA interval - GNSS DOPS and Active Satellites
- 5 NMEA_SEN_GSV, GPGSV interval - GNSS Satellites in View
- 6 NMEA_SEN_GRS, GPGRS interval - GNSS Range Residuals
- 7 NMEA_SEN_GST, GPGST interval - GNSS Pseudorange Errors Statistics
- 13 NMEA_SEN_MALM, PMTKALM interval - GPS almanac information
- 14 NMEA_SEN_MEPH, PMTKEPH interval - GPS ephemeris information
- 15 NMEA_SEN_MDGP, PMTKDGP interval - GPS differential correction information
- 16 NMEA_SEN_MDBG, PMTKDBG interval – MTK debug information
- 17 NMEA_SEN_ZDA, GPZDA interval - Time & Date
- 18 NMEA_SEN_MCHN, PMTKCHN interval – GPS channel status
- // MTK 3333
- 19 NMEA_SEN_DTM, GPDTM interval - Datum reference
- "$PMTK314,1,1,1,1,1,5,1,1,0,0,0,0,0,0,0,0,0,1,0"
- */
- if (session->context->readonly)
- return;
- if (event == event_triggermatch) {
- (void)nmea_send(session, "$PMTK320,0"); /* power save off */
- // Fix interval, 1000 millseconds
- (void)nmea_send(session, "$PMTK300,1000,0,0,0.0,0.0");
- // Set NMEA sentences.
- (void)nmea_send(session,
- "$PMTK314,0,1,0,1,1,5,1,1,0,0,0,0,0,0,0,0,0,1,0");
- // DGPS is WAAS
- (void)nmea_send(session, "$PMTK301,2");
- // SBAS enable
- (void)nmea_send(session, "$PMTK313,1");
- /* PMTK_API_Q_OUTPUT_CTL - Query PPS pulse width - Trimble only?
- * http://trl.trimble.com/docushare/dsweb/Get/Document-482603/CONDOR_UG_2C_75263-00.pdf *
- * badly documented */
- (void)nmea_send(session, "$PMTK424");
- }
- }
- // mtk3301_speed_switcher()
- // Always returns True
- static bool mtk3301_speed_switcher(struct gps_device_t* session,
- speed_t speed, char parity UNUSED,
- int stopbits UNUSED)
- {
- int i;
- char buf[BUFSIZ] = {0};
- (void)snprintf(buf, sizeof(buf), "$PQBAUD,W,%d", (int) speed);
- // Calling PQBAUD fails to set the receiver's baud rate
- // some of the time. Sending it twice seems to work every time.
- // So just for good measure, send it three times.
- for (i = 0; i < 3; i++) {
- (void)nmea_send(session, buf);
- }
- return true;
- }
- static bool mtk3301_rate_switcher(struct gps_device_t *session, double rate)
- {
- char buf[78];
- unsigned int milliseconds = (unsigned int)(1000 * rate);
- if (rate > 1) {
- // force no slower than 1Hz
- milliseconds = 1000;
- } else if (rate < 0.1) {
- // force no faster than 10Hz, MTK3333 can do 10Hz
- milliseconds = 100;
- }
- (void)snprintf(buf, sizeof(buf), "$PMTK300,%u,0,0,0,0", milliseconds);
- (void)nmea_send(session, buf); /* Fix interval */
- return true;
- }
- /* *INDENT-OFF* */
- const struct gps_type_t driver_mtk3301 = {
- .type_name = "MTK-3301", /* full name of type */
- .packet_type = NMEA_PACKET, /* associated lexer packet type */
- .flags = DRIVER_STICKY, /* remember this */
- .trigger = "$PMTK705,", /* firmware release name and version */
- .channels = 12, /* not used by this driver */
- .probe_detect = NULL, /* no probe */
- .get_packet = generic_get, /* how to get a packet */
- .parse_packet = generic_parse_input, /* how to interpret a packet */
- .rtcm_writer = gpsd_write, /* write RTCM data straight */
- .init_query = NULL, /* non-perturbing initial query */
- .event_hook = mtk3301_event_hook, /* lifetime event handler */
- .speed_switcher = mtk3301_speed_switcher, // sample speed switcher
- .mode_switcher = NULL, /* no mode switcher */
- .rate_switcher = mtk3301_rate_switcher, /* sample rate switcher */
- .min_cycle.tv_sec = 0,
- .min_cycle.tv_nsec = 100000000, /* max 10Hz */
- .control_send = nmea_write, /* how to send control strings */
- .time_offset = NULL,
- };
- /* *INDENT-ON* */
- #ifdef ISYNC_ENABLE
- /**************************************************************************
- *
- * Spectratime LNRCLOK / GRCLOK iSync GPS-disciplined rubidium oscillators
- *
- * These devices comprise a u-blox 6 attached to a separate iSync
- * microcontroller which drives the rubidium oscillator. The iSync
- * microcontroller can be configured to pass through the underlying
- * GPS communication channel, while still using the GPS PPSREF signal
- * to discipline the rubidium oscillator.
- *
- * The iSync can also generate its own periodic status packets in NMEA
- * format. These describe the state of the oscillator (including
- * whether or not the oscillator PPSOUT is synced to the GPS PPSREF).
- * These packets are transmitted in the middle of the underlying GPS
- * packets, forcing us to handle interrupted NMEA packets.
- *
- * The default state of the device is to generate no periodic output.
- * We send a probe string to initiate beating of the iSync-proprietary
- * $PTNTS,B message, which is then detected as a NMEA trigger.
- *
- **************************************************************************/
- static ssize_t isync_write(struct gps_device_t *session, const char *data)
- {
- return gpsd_write(session, data, strlen(data));
- }
- static bool isync_detect(struct gps_device_t *session)
- {
- speed_t old_baudrate;
- char old_parity;
- unsigned int old_stopbits;
- /* Set 9600 8N1 */
- old_baudrate = session->gpsdata.dev.baudrate;
- old_parity = session->gpsdata.dev.parity;
- old_stopbits = session->gpsdata.dev.stopbits;
- gpsd_set_speed(session, 9600, 'N', 1);
- /* Cancel pass-through mode and initiate beating of $PTNTS,B
- * message, which can subsequently be detected as a trigger.
- */
- (void)isync_write(session, "@@@@\r\nMAW0C0B\r\n");
- /* return serial port to original settings */
- gpsd_set_speed(session, old_baudrate, old_parity, old_stopbits);
- return false;
- }
- static void isync_event_hook(struct gps_device_t *session, event_t event)
- {
- if (session->context->readonly)
- return;
- if (event == event_driver_switch) {
- session->lexer.counter = 0;
- }
- if (event == event_configure) {
- switch (session->lexer.counter) {
- case 1:
- /* Configure timing and frequency flags:
- * - Thermal compensation active
- * - PPSREF active
- * - PPSOUT active
- */
- (void)isync_write(session, "MAW040B\r\n");
- /* Configure tracking flags:
- * - Save frequency every 24 hours
- * - Align PPSOUT to PPSINT
- * - Track PPSREF
- */
- (void)isync_write(session, "MAW0513\r\n");
- /* Configure tracking start flags:
- * - Tracking restart allowed
- * - Align to PPSREF
- */
- (void)isync_write(session, "MAW0606\r\n");
- /* Configure tracking window:
- * - 4us
- */
- (void)isync_write(session, "MAW1304\r\n");
- /* Configure alarm window:
- * - 4us
- */
- (void)isync_write(session, "MAW1404\r\n");
- break;
- case 2:
- /* Configure pulse every d second:
- * - pulse every second
- */
- (void)isync_write(session, "MAW1701\r\n");
- /* Configure pulse origin:
- * - zero offset
- */
- (void)isync_write(session, "MAW1800\r\n");
- /* Configure pulse width:
- * - 600ms
- */
- (void)isync_write(session, "MAW1223C34600\r\n");
- break;
- case 3:
- /* Configure GPS resource utilization:
- * - do not consider GPS messages
- */
- (void)isync_write(session, "MAW2200\r\n");
- /* Restart sync */
- (void)isync_write(session, "SY1\r\n");
- /* Restart tracking */
- (void)isync_write(session, "TR1\r\n");
- break;
- case 4:
- /* Cancel BTx messages (if any) */
- (void)isync_write(session, "BT0\r\n");
- /* Configure messages coming out every second:
- * - Oscillator status ($PTNTA) at 750ms
- */
- (void)isync_write(session, "MAW0B00\r\n");
- (void)isync_write(session, "MAW0C0A\r\n");
- break;
- case 5:
- /* Enable GPS passthrough and force u-blox driver to
- * select NMEA mode.
- */
- session->mode = 0;
- session->drivers_identified = 0;
- (void)isync_write(session, "@@@@GPS\r\n");
- break;
- case 6:
- /* Trigger detection of underlying u-blox (if necessary) */
- (void)ubx_write(session, 0x06, 0x00, NULL, 0);
- break;
- }
- }
- }
- /* *INDENT-OFF* */
- const struct gps_type_t driver_isync = {
- .type_name = "iSync", /* full name of type */
- .packet_type = NMEA_PACKET, /* associated lexer packet type */
- .flags = DRIVER_STICKY, /* remember this */
- .trigger = "$PTNTS,B,", /* iSync status message */
- .channels = 50, /* copied from driver_ubx */
- .probe_detect = isync_detect, /* how to detect at startup time */
- .get_packet = generic_get, /* how to get a packet */
- .parse_packet = generic_parse_input, /* how to interpret a packet */
- .init_query = NULL, /* non-perturbing initial query */
- .event_hook = isync_event_hook, /* lifetime event handler */
- .speed_switcher = NULL, /* no speed switcher */
- .mode_switcher = NULL, /* no mode switcher */
- .rate_switcher = NULL, /* no sample-rate switcher */
- .min_cycle.tv_sec = 1, /* not relevant, no rate switch */
- .min_cycle.tv_nsec = 0, /* not relevant, no rate switch */
- .control_send = nmea_write, /* how to send control strings */
- .time_offset = NULL, /* no method for NTP fudge factor */
- };
- /* *INDENT-ON* */
- #endif /* ISYNC_ENABLE */
- #ifdef AIVDM_ENABLE
- /**************************************************************************
- *
- * AIVDM - ASCII armoring of binary AIS packets
- *
- **************************************************************************/
- static bool aivdm_decode(const char *buf, size_t buflen,
- struct gps_device_t *session,
- struct ais_t *ais,
- int debug)
- {
- #ifdef __UNUSED_DEBUG__
- char *sixbits[64] = {
- "000000", "000001", "000010", "000011", "000100",
- "000101", "000110", "000111", "001000", "001001",
- "001010", "001011", "001100", "001101", "001110",
- "001111", "010000", "010001", "010010", "010011",
- "010100", "010101", "010110", "010111", "011000",
- "011001", "011010", "011011", "011100", "011101",
- "011110", "011111", "100000", "100001", "100010",
- "100011", "100100", "100101", "100110", "100111",
- "101000", "101001", "101010", "101011", "101100",
- "101101", "101110", "101111", "110000", "110001",
- "110010", "110011", "110100", "110101", "110110",
- "110111", "111000", "111001", "111010", "111011",
- "111100", "111101", "111110", "111111",
- };
- #endif /* __UNUSED_DEBUG__ */
- int nfrags, ifrag, nfields = 0;
- unsigned char *field[NMEA_MAX*2];
- unsigned char fieldcopy[NMEA_MAX*2+1];
- unsigned char *data, *cp;
- char const *cp1;
- int pad;
- struct aivdm_context_t *ais_context;
- int i;
- if (buflen == 0)
- return false;
- /* we may need to dump the raw packet */
- GPSD_LOG(LOG_PROG, &session->context->errout,
- "AIVDM packet length %zd: %s\n", buflen, buf);
- /* first clear the result, making sure we don't return garbage */
- memset(ais, 0, sizeof(*ais));
- /* discard overlong sentences */
- if (strlen(buf) > sizeof(fieldcopy)-1) {
- GPSD_LOG(LOG_ERROR, &session->context->errout,
- "overlong AIVDM packet.\n");
- return false;
- }
- /* discard sentences with high-half characters in them, they're corrupted */
- for (cp1 = buf; *cp1; cp1++) {
- if (!isascii(*cp1)) {
- GPSD_LOG(LOG_ERROR, &session->context->errout,
- "corrupted AIVDM packet.\n");
- return false;
- }
- }
- /* extract packet fields */
- (void)strlcpy((char *)fieldcopy, buf, sizeof(fieldcopy));
- field[nfields++] = (unsigned char *)buf;
- for (cp = fieldcopy;
- cp < fieldcopy + buflen; cp++)
- {
- if (
- (*cp == (unsigned char)',') ||
- (*cp == (unsigned char)'*')
- ) {
- *cp = '\0';
- field[nfields++] = cp + 1;
- }
- }
- #ifdef __UNDEF_DEBUG_
- for(int nf = 0; nf < nfields; nf++)
- GPSD_LOG(LOG_DATA, &session->context->errout,
- "field [%d] [%s]\n", nf, field[nf]);
- #endif
- /* discard sentences with exiguous commas; catches run-ons */
- if (nfields < 7) {
- GPSD_LOG(LOG_ERROR, &session->context->errout,
- "malformed AIVDM packet.\n");
- return false;
- }
- switch (field[4][0]) {
- case '\0':
- /*
- * Apparently an empty channel is normal for AIVDO sentences,
- * which makes sense as they don't come in over radio. This
- * is going to break if there's ever an AIVDO type 24, though.
- */
- if (!str_starts_with((const char *)field[0], "!AIVDO"))
- GPSD_LOG(LOG_INF, &session->context->errout,
- "invalid empty AIS channel. Assuming 'A'\n");
- ais_context = &session->driver.aivdm.context[0];
- session->driver.aivdm.ais_channel ='A';
- break;
- case '1':
- if (strcmp((char *)field[4], (char *)"12") == 0) {
- GPSD_LOG(LOG_INF, &session->context->errout,
- "ignoring bogus AIS channel '12'.\n");
- return false;
- }
- FALLTHROUGH
- case 'A':
- ais_context = &session->driver.aivdm.context[0];
- session->driver.aivdm.ais_channel ='A';
- break;
- case '2':
- case 'B':
- ais_context = &session->driver.aivdm.context[1];
- session->driver.aivdm.ais_channel ='B';
- break;
- case 'C':
- GPSD_LOG(LOG_INF, &session->context->errout,
- "ignoring AIS channel C (secure AIS).\n");
- return false;
- default:
- GPSD_LOG(LOG_ERROR, &session->context->errout,
- "invalid AIS channel 0x%0X .\n", field[4][0]);
- return false;
- }
- nfrags = atoi((char *)field[1]); /* number of fragments to expect */
- ifrag = atoi((char *)field[2]); /* fragment id */
- data = field[5];
- pad = 0;
- if(isdigit(field[6][0]))
- pad = field[6][0] - '0'; /* number of padding bits ASCII encoded*/
- GPSD_LOG(LOG_PROG, &session->context->errout,
- "nfrags=%d, ifrag=%d, decoded_frags=%d, data=%s, pad=%d\n",
- nfrags, ifrag, ais_context->decoded_frags, data, pad);
- /* assemble the binary data */
- /* check fragment ordering */
- if (ifrag != ais_context->decoded_frags + 1) {
- GPSD_LOG(LOG_ERROR, &session->context->errout,
- "invalid fragment #%d received, expected #%d.\n",
- ifrag, ais_context->decoded_frags + 1);
- if (ifrag != 1)
- return false;
- /* else, ifrag==1: Just discard all that was previously decoded and
- * simply handle that packet */
- ais_context->decoded_frags = 0;
- }
- if (ifrag == 1) {
- (void)memset(ais_context->bits, '\0', sizeof(ais_context->bits));
- ais_context->bitlen = 0;
- }
- /* wacky 6-bit encoding, shades of FIELDATA */
- for (cp = data; cp < data + strlen((char *)data); cp++) {
- unsigned char ch;
- ch = *cp;
- ch -= 48;
- if (ch >= 40)
- ch -= 8;
- #ifdef __UNUSED_DEBUG__
- GPSD_LOG(LOG_RAW, &session->context->errout,
- "%c: %s\n", *cp, sixbits[ch]);
- #endif /* __UNUSED_DEBUG__ */
- for (i = 5; i >= 0; i--) {
- if ((ch >> i) & 0x01) {
- ais_context->bits[ais_context->bitlen / 8] |=
- (1 << (7 - ais_context->bitlen % 8));
- }
- ais_context->bitlen++;
- if (ais_context->bitlen > sizeof(ais_context->bits)) {
- GPSD_LOG(LOG_INF, &session->context->errout,
- "overlong AIVDM payload truncated.\n");
- return false;
- }
- }
- }
- ais_context->bitlen -= pad;
- /* time to pass buffered-up data to where it's actually processed? */
- if (ifrag == nfrags) {
- if (debug >= LOG_INF) {
- size_t clen = BITS_TO_BYTES(ais_context->bitlen);
- GPSD_LOG(LOG_INF, &session->context->errout,
- "AIVDM payload is %zd bits, %zd chars: %s\n",
- ais_context->bitlen, clen,
- gpsd_hexdump(session->msgbuf, sizeof(session->msgbuf),
- (char *)ais_context->bits, clen));
- }
- /* clear waiting fragments count */
- ais_context->decoded_frags = 0;
- /* decode the assembled binary packet */
- return ais_binary_decode(&session->context->errout,
- ais,
- ais_context->bits,
- ais_context->bitlen,
- &ais_context->type24_queue);
- }
- /* we're still waiting on another sentence */
- ais_context->decoded_frags++;
- return false;
- }
- static gps_mask_t aivdm_analyze(struct gps_device_t *session)
- {
- if (session->lexer.type == AIVDM_PACKET) {
- if (aivdm_decode
- ((char *)session->lexer.outbuffer, session->lexer.outbuflen,
- session, &session->gpsdata.ais,
- session->context->errout.debug)) {
- return ONLINE_SET | AIS_SET;
- } else
- return ONLINE_SET;
- } else if (session->lexer.type == NMEA_PACKET) {
- return nmea_parse((char *)session->lexer.outbuffer, session);
- } else
- return 0;
- }
- /* *INDENT-OFF* */
- const struct gps_type_t driver_aivdm = {
- /* Full name of type */
- .type_name = "AIVDM", /* associated lexer packet type */
- .packet_type = AIVDM_PACKET, /* numeric packet type */
- .flags = DRIVER_NOFLAGS, /* no rollover or other flags */
- .trigger = NULL, /* identifying response */
- .channels = 0, /* not used by this driver */
- .probe_detect = NULL, /* no probe */
- .get_packet = generic_get, /* how to get a packet */
- .parse_packet = aivdm_analyze, /* how to analyze a packet */
- .rtcm_writer = NULL, /* don't send RTCM data, */
- .init_query = NULL, /* non-perturbing initial query */
- .event_hook = NULL, /* lifetime event handler */
- .speed_switcher = NULL, /* no speed switcher */
- .mode_switcher = NULL, /* no mode switcher */
- .rate_switcher = NULL, /* no rate switcher */
- .min_cycle.tv_sec = 1, /* max 1Hz */
- .min_cycle.tv_nsec = 0,
- .control_send = NULL, /* no control sender */
- .time_offset = NULL, /* no NTP communication */
- };
- /* *INDENT-ON* */
- #endif /* AIVDM_ENABLE */
- /**************************************************************************
- *
- * JSON passthrough driver
- *
- **************************************************************************/
- /* prepend the session path to the value of a specified attribute */
- static void path_rewrite(struct gps_device_t *session, char *prefix)
- {
- /*
- * Hack the packet to reflect its origin. This code is supposed
- * to insert the path naming the remote gpsd instance into the
- * beginning of the path attribute, followed by a # to separate it
- * from the device.
- */
- char *prefloc;
- assert(prefix != NULL && session->lexer.outbuffer != NULL);
- /* possibly the rewrite has been done already, this comw up in gpsmon */
- if (strstr((char *)session->lexer.outbuffer,
- session->gpsdata.dev.path) != NULL)
- return;
- for (prefloc = (char *)session->lexer.outbuffer;
- prefloc < (char *)session->lexer.outbuffer+session->lexer.outbuflen;
- prefloc++)
- if (str_starts_with(prefloc, prefix)) {
- char *sfxloc;
- char copy[sizeof(session->lexer.outbuffer)+1];
- (void)strlcpy(copy,
- (char *)session->lexer.outbuffer,
- sizeof(copy));
- prefloc += strlen(prefix);
- (void)strlcpy(prefloc,
- session->gpsdata.dev.path,
- sizeof(session->gpsdata.dev.path));
- if ((sfxloc = strchr(prefloc, '#')))
- *sfxloc = '\0'; /* Avoid accumulating multiple device names */
- (void)strlcat((char *)session->lexer.outbuffer, "#",
- sizeof(session->lexer.outbuffer));
- (void)strlcat((char *)session->lexer.outbuffer,
- copy + (prefloc-(char *)session->lexer.outbuffer),
- sizeof(session->lexer.outbuffer));
- }
- session->lexer.outbuflen = strlen((char *)session->lexer.outbuffer);
- }
- static gps_mask_t json_pass_packet(struct gps_device_t *session)
- {
- GPSD_LOG(LOG_IO, &session->context->errout,
- "<= GPS: %s\n", (char *)session->lexer.outbuffer);
- if (strstr(session->gpsdata.dev.path, ":/") != NULL &&
- strstr(session->gpsdata.dev.path, "localhost") == NULL)
- {
- /* devices and paths need to be edited */
- if (strstr((char *)session->lexer.outbuffer, "DEVICE") != NULL)
- path_rewrite(session, "\"path\":\"");
- path_rewrite(session, "\"device\":\"");
- /* mark certain responses without a path or device attribute */
- if (session->gpsdata.dev.path[0] != '\0') {
- if (strstr((char *)session->lexer.outbuffer, "VERSION") != NULL
- || strstr((char *)session->lexer.outbuffer, "WATCH") != NULL
- || strstr((char *)session->lexer.outbuffer, "DEVICES") != NULL) {
- session->lexer.outbuffer[session->lexer.outbuflen-1] = '\0';
- (void)strlcat((char *)session->lexer.outbuffer, ",\"remote\":\"",
- sizeof(session->lexer.outbuffer));
- (void)strlcat((char *)session->lexer.outbuffer,
- session->gpsdata.dev.path,
- sizeof(session->lexer.outbuffer));
- (void)strlcat((char *)session->lexer.outbuffer, "\"}",
- sizeof(session->lexer.outbuffer));
- }
- session->lexer.outbuflen = strlen((char *)session->lexer.outbuffer);
- }
- }
- GPSD_LOG(LOG_PROG, &session->context->errout,
- "JSON, passing through %s\n",
- (char *)session->lexer.outbuffer);
- return PASSTHROUGH_IS;
- }
- /* *INDENT-OFF* */
- const struct gps_type_t driver_json_passthrough = {
- .type_name = "JSON slave driver", /* full name of type */
- .packet_type = JSON_PACKET, /* associated lexer packet type */
- .flags = DRIVER_NOFLAGS, /* don't remember this */
- .trigger = NULL, /* it's the default */
- .channels = 0, /* not used */
- .probe_detect = NULL, /* no probe */
- .get_packet = generic_get, /* use generic packet getter */
- .parse_packet = json_pass_packet, /* how to interpret a packet */
- .rtcm_writer = NULL, /* write RTCM data straight */
- .init_query = NULL, /* non-perturbing initial query */
- .event_hook = NULL, /* lifetime event handler */
- .speed_switcher = NULL, /* no speed switcher */
- .mode_switcher = NULL, /* no mode switcher */
- .rate_switcher = NULL, /* no sample-rate switcher */
- .min_cycle.tv_sec = 1, /* not relevant, no rate switch */
- .min_cycle.tv_nsec = 0, /* not relevant, no rate switch */
- .control_send = NULL, /* how to send control strings */
- .time_offset = NULL, /* no method for NTP fudge factor */
- };
- /* *INDENT-ON* */
- /* *INDENT-OFF* */
- const struct gps_type_t driver_pps = {
- .type_name = "PPS", /* full name of type */
- .packet_type = BAD_PACKET, /* associated lexer packet type */
- .flags = DRIVER_NOFLAGS, /* don't remember this */
- .trigger = NULL, /* it's the default */
- .channels = 0, /* not used */
- .probe_detect = NULL, /* no probe */
- .get_packet = NULL, /* use generic packet getter */
- .parse_packet = NULL, /* how to interpret a packet */
- .rtcm_writer = NULL, /* write RTCM data straight */
- .init_query = NULL, /* non-perturbing initial query */
- .event_hook = NULL, /* lifetime event handler */
- .speed_switcher = NULL, /* no speed switcher */
- .mode_switcher = NULL, /* no mode switcher */
- .rate_switcher = NULL, /* no sample-rate switcher */
- .min_cycle.tv_sec = 1, /* not relevant, no rate switch */
- .min_cycle.tv_nsec = 0, /* not relevant, no rate switch */
- .control_send = NULL, /* how to send control strings */
- .time_offset = NULL, /* no method for NTP fudge factor */
- };
- /* *INDENT-ON* */
- extern const struct gps_type_t driver_evermore;
- extern const struct gps_type_t driver_garmin_ser_binary;
- extern const struct gps_type_t driver_garmin_usb_binary;
- extern const struct gps_type_t driver_geostar;
- extern const struct gps_type_t driver_greis;
- extern const struct gps_type_t driver_italk;
- extern const struct gps_type_t driver_navcom;
- extern const struct gps_type_t driver_nmea2000;
- extern const struct gps_type_t driver_oncore;
- extern const struct gps_type_t driver_sirf;
- extern const struct gps_type_t driver_skytraq;
- extern const struct gps_type_t driver_superstar2;
- extern const struct gps_type_t driver_tsip;
- extern const struct gps_type_t driver_ubx;
- extern const struct gps_type_t driver_zodiac;
- // the point of this rigamarole is to not have to export a table size
- static const struct gps_type_t *gpsd_driver_array[] = {
- &driver_unknown,
- &driver_nmea0183,
- &driver_ashtech,
- #ifdef TRIPMATE_ENABLE
- &driver_tripmate,
- #endif // TRIPMATE_ENABLE
- #ifdef EARTHMATE_ENABLE
- &driver_earthmate,
- #endif // EARTHMATE_ENABLE
- #ifdef GPSCLOCK_ENABLE
- &driver_gpsclock,
- #endif // GPSCLOCK_ENABLE
- #ifdef GARMIN_ENABLE
- &driver_garmin,
- #endif // GARMIN_ENABLE
- &driver_mtk3301,
- #ifdef OCEANSERVER_ENABLE
- &driver_oceanServer,
- #endif // OCEANSERVER_ENABLE
- #ifdef FV18_ENABLE
- &driver_fv18,
- #endif // FV18_ENABLE
- #ifdef TNT_ENABLE
- &driver_trueNorth,
- #endif // TNT_ENABLE
- #ifdef FURY_ENABLE
- &driver_fury,
- #endif // FURY_ENABLE
- #ifdef AIVDM_ENABLE
- &driver_aivdm,
- #endif // AIVDM_ENABLE
- #ifdef EVERMORE_ENABLE
- &driver_evermore,
- #endif // EVERMORE_ENABLE
- #ifdef GARMIN_ENABLE
- // be sure to try Garmin Serial Binary before Garmin USB Binary
- &driver_garmin_ser_binary,
- &driver_garmin_usb_binary,
- #endif // GARMIN_ENABLE
- #ifdef GEOSTAR_ENABLE
- &driver_geostar,
- #endif // GEOSTAR_ENABLE
- #ifdef GREIS_ENABLE
- &driver_greis,
- #endif // GREIS_ENABLE
- #ifdef ITRAX_ENABLE
- &driver_italk,
- #endif // ITRAX_ENABLE
- #ifdef ONCORE_ENABLE
- &driver_oncore,
- #endif // ONCORE_ENABLE
- #ifdef NAVCOM_ENABLE
- &driver_navcom,
- #endif // NAVCOM_ENABLE
- #ifdef SIRF_ENABLE
- &driver_sirf,
- #endif // SIRF_ENABLE
- #ifdef SKYTRAQ_ENABLE
- &driver_skytraq,
- #endif // SKYTRAQ_ENABLE
- #ifdef SUPERSTAR2_ENABLE
- &driver_superstar2,
- #endif // SUPERSTAR2_ENABLE
- #ifdef TSIP_ENABLE
- &driver_tsip,
- #endif // TSIP_ENABLE
- #ifdef ISYNC_ENABLE
- &driver_isync,
- #endif // ISYNC_ENABLE
- #ifdef UBLOX_ENABLE
- &driver_ubx,
- #endif // UBLOX_ENABLE
- #ifdef ZODIAC_ENABLE
- &driver_zodiac,
- #endif // ZODIAC_ENABLE
- #ifdef NMEA2000_ENABLE
- &driver_nmea2000,
- #endif // NMEA2000_ENABLE
- #ifdef RTCM104V2_ENABLE
- &driver_rtcm104v2,
- #endif // RTCM104V2_ENABLE
- #ifdef RTCM104V3_ENABLE
- &driver_rtcm104v3,
- #endif // RTCM104V3_ENABLE
- #ifdef GARMINTXT_ENABLE
- &driver_garmintxt,
- #endif // GARMINTXT_ENABLE
- &driver_json_passthrough,
- &driver_pps,
- NULL,
- };
- const struct gps_type_t **gpsd_drivers = &gpsd_driver_array[0];
- // vim: set expandtab shiftwidth=4
|