/* * This file is Copyright (c) 2010 by the GPSD project * SPDX-License-Identifier: BSD-2-clause */ #include #include #include #include #include #include "gpsd.h" #include "strfuncs.h" /* * Support for generic binary drivers. These functions dump NMEA for passing * to the client in raw mode. They assume that (a) the public gps.h structure * members are in a valid state, (b) that the private members hours, minutes, * and seconds have also been filled in, (c) that if the private member * mag_var is not NAN it is a magnetic variation in degrees that should be * passed on, and (d) if the private member separation does not have the * value NAN, it is a valid WGS84 geoidal separation in meters for the fix. */ static double degtodm(double angle) /* decimal degrees to GPS-style, degrees first followed by minutes */ { double fraction, integer; fraction = modf(angle, &integer); return floor(angle) * 100 + fraction * 60; } void gpsd_position_fix_dump(struct gps_device_t *session, char bufp[], size_t len) { struct tm tm; time_t intfixtime; intfixtime = (time_t) session->gpsdata.fix.time; (void)gmtime_r(&intfixtime, &tm); if (session->gpsdata.fix.mode > MODE_NO_FIX) { (void)snprintf(bufp, len, "$GPGGA,%02d%02d%02d,%09.4f,%c,%010.4f,%c,%d,%02d,", tm.tm_hour, tm.tm_min, tm.tm_sec, degtodm(fabs(session->gpsdata.fix.latitude)), ((session->gpsdata.fix.latitude > 0) ? 'N' : 'S'), degtodm(fabs(session->gpsdata.fix.longitude)), ((session->gpsdata.fix.longitude > 0) ? 'E' : 'W'), session->gpsdata.status, session->gpsdata.satellites_used); if (0 == isfinite(session->gpsdata.dop.hdop)) (void)strlcat(bufp, ",", len); else str_appendf(bufp, len, "%.2f,", session->gpsdata.dop.hdop); if (0 == isfinite(session->gpsdata.fix.altitude)) (void)strlcat(bufp, ",", len); else str_appendf(bufp, len, "%.2f,M,", session->gpsdata.fix.altitude); if (0 == isfinite(session->gpsdata.separation)) (void)strlcat(bufp, ",", len); else str_appendf(bufp, len, "%.3f,M,", session->gpsdata.separation); if (0 == isfinite(session->mag_var)) (void)strlcat(bufp, ",", len); else { str_appendf(bufp, len, "%3.2f,", fabs(session->mag_var)); (void)strlcat(bufp, (session->mag_var > 0) ? "E" : "W", len); } nmea_add_checksum(bufp); } } static void gpsd_transit_fix_dump(struct gps_device_t *session, char bufp[], size_t len) { struct tm tm; time_t intfixtime; tm.tm_mday = tm.tm_mon = tm.tm_year = tm.tm_hour = tm.tm_min = tm.tm_sec = 0; if (0 != isfinite(session->gpsdata.fix.time)) { intfixtime = (time_t) session->gpsdata.fix.time; (void)gmtime_r(&intfixtime, &tm); tm.tm_mon++; tm.tm_year %= 100; } #define ZEROIZE(x) (isfinite(x)==0 ? 0.0 : x) (void)snprintf(bufp, len, "$GPRMC,%02d%02d%02d,%c,%09.4f,%c,%010.4f,%c,%.4f,%.3f,%02d%02d%02d,,", tm.tm_hour, tm.tm_min, tm.tm_sec, session->gpsdata.status ? 'A' : 'V', ZEROIZE(degtodm(fabs(session->gpsdata.fix.latitude))), ((session->gpsdata.fix.latitude > 0) ? 'N' : 'S'), ZEROIZE(degtodm(fabs(session->gpsdata.fix.longitude))), ((session->gpsdata.fix.longitude > 0) ? 'E' : 'W'), ZEROIZE(session->gpsdata.fix.speed * MPS_TO_KNOTS), ZEROIZE(session->gpsdata.fix.track), tm.tm_mday, tm.tm_mon, tm.tm_year); #undef ZEROIZE nmea_add_checksum(bufp); } static void gpsd_binary_satellite_dump(struct gps_device_t *session, char bufp[], size_t len) { int i; char *bufp2 = bufp; bufp[0] = '\0'; for (i = 0; i < session->gpsdata.satellites_visible; i++) { if (i % 4 == 0) { bufp2 = bufp + strlen(bufp); str_appendf(bufp, len, "$GPGSV,%d,%d,%02d", ((session->gpsdata.satellites_visible - 1) / 4) + 1, (i / 4) + 1, session->gpsdata.satellites_visible); } if (i < session->gpsdata.satellites_visible) { if ( 0 == session->gpsdata.skyview[i].PRN) { /* bad prn, just make all zeros */ str_appendf(bufp, len, ",00,00,000,00"); } else { str_appendf(bufp, len, ",%02d,%02d,%03d,%02.0f", session->gpsdata.skyview[i].PRN, session->gpsdata.skyview[i].elevation, session->gpsdata.skyview[i].azimuth, session->gpsdata.skyview[i].ss); } } if (i % 4 == 3 || i == session->gpsdata.satellites_visible - 1) { nmea_add_checksum(bufp2); } } #ifdef ZODIAC_ENABLE if (session->lexer.type == ZODIAC_PACKET && session->driver.zodiac.Zs[0] != 0) { bufp2 = bufp + strlen(bufp); str_appendf(bufp, len, "$PRWIZCH"); for (i = 0; i < ZODIAC_CHANNELS; i++) { str_appendf(bufp, len, ",%02u,%X", session->driver.zodiac.Zs[i], session->driver.zodiac.Zv[i] & 0x0f); } nmea_add_checksum(bufp2); } #endif /* ZODIAC_ENABLE */ } static void gpsd_binary_quality_dump(struct gps_device_t *session, char bufp[], size_t len) { char *bufp2; bufp[0] = '\0'; if (session->device_type != NULL) { int i, j; int max_channels = session->device_type->channels; /* GPGSA commonly has exactly 12 channels, enforce that as a MAX */ if ( 12 < max_channels ) { /* what to do with the excess channels? */ max_channels = 12; } bufp2 = bufp + strlen(bufp); (void)snprintf(bufp, len, "$GPGSA,%c,%d,", 'A', session->gpsdata.fix.mode); j = 0; for (i = 0; i < max_channels; i++) { if (session->gpsdata.skyview[i].used == true){ str_appendf(bufp, len, "%d,", session->gpsdata.skyview[i].PRN); j++; } } for (i = j; i < max_channels; i++) { /* fill out the empty slots */ (void)strlcat(bufp, ",", len); } if (session->gpsdata.fix.mode == MODE_NO_FIX) (void)strlcat(bufp, ",,,", len); else { /* output the DOPs, NaN as blanks */ if ( 0 != isfinite( session->gpsdata.dop.pdop ) ) { str_appendf(bufp, len, "%.1f,", session->gpsdata.dop.pdop); } else { (void)strlcat(bufp, ",", len); } if ( 0 != isfinite( session->gpsdata.dop.hdop ) ) { str_appendf(bufp, len, "%.1f,", session->gpsdata.dop.hdop); } else { (void)strlcat(bufp, ",", len); } if ( 0 != isfinite( session->gpsdata.dop.vdop ) ) { str_appendf(bufp, len, "%.1f*", session->gpsdata.dop.vdop); } else { (void)strlcat(bufp, "*", len); } } nmea_add_checksum(bufp2); } if (isfinite(session->gpsdata.fix.epx) != 0 && isfinite(session->gpsdata.fix.epy) != 0 && isfinite(session->gpsdata.fix.epv) != 0 && isfinite(session->gpsdata.epe) != 0) { struct tm tm; time_t intfixtime; tm.tm_hour = tm.tm_min = tm.tm_sec = 0; if (0 != isfinite(session->gpsdata.fix.time)) { intfixtime = (time_t) session->gpsdata.fix.time; (void)gmtime_r(&intfixtime, &tm); } bufp2 = bufp + strlen(bufp); #define ZEROIZE(x) (isfinite(x)==0 ? 0.0 : x) str_appendf(bufp, len, "$GPGBS,%02d%02d%02d,%.2f,M,%.2f,M,%.2f,M", tm.tm_hour, tm.tm_min, tm.tm_sec, ZEROIZE(session->gpsdata.fix.epx), ZEROIZE(session->gpsdata.fix.epy), ZEROIZE(session->gpsdata.fix.epv)); nmea_add_checksum(bufp2); } #undef ZEROIZE } static void gpsd_binary_time_dump(struct gps_device_t *session, char bufp[], size_t len) { struct tm tm; double integral; time_t integral_time; if (session->newdata.mode > MODE_NO_FIX) { double fractional = modf(session->newdata.time, &integral); integral_time = (time_t) integral; (void)gmtime_r(&integral_time, &tm); /* * We pin this report to the GMT/UTC timezone. This may be technically * incorrect; our sources on ZDA suggest that it should report local * timezone. But no GPS we've ever seen actually does this, because it * would require embedding a location-to-TZ database in the receiver. * And even if we could do that, it would make our regression tests * break any time they were run in a timezone different from the one * where they were generated. */ (void)snprintf(bufp, len, "$GPZDA,%02d%02d%05.2f,%02d,%02d,%04d,00,00", tm.tm_hour, tm.tm_min, (double)tm.tm_sec + fractional, tm.tm_mday, tm.tm_mon + 1, tm.tm_year + 1900); nmea_add_checksum(bufp); } } static void gpsd_binary_almanac_dump(struct gps_device_t *session, char bufp[], size_t len) { if ( session->gpsdata.subframe.is_almanac ) { (void)snprintf(bufp, len, "$GPALM,1,1,%02d,%04d,%02x,%04x,%02x,%04x,%04x,%05x,%06x,%06x,%06x,%03x,%03x", (int)session->gpsdata.subframe.sub5.almanac.sv, (int)session->context->gps_week % 1024, (unsigned int)session->gpsdata.subframe.sub5.almanac.svh, (unsigned int)session->gpsdata.subframe.sub5.almanac.e, (unsigned int)session->gpsdata.subframe.sub5.almanac.toa, (unsigned int)session->gpsdata.subframe.sub5.almanac.deltai, (unsigned int)session->gpsdata.subframe.sub5.almanac.Omegad, (unsigned int)session->gpsdata.subframe.sub5.almanac.sqrtA, (unsigned int)session->gpsdata.subframe.sub5.almanac.omega, (unsigned int)session->gpsdata.subframe.sub5.almanac.Omega0, (unsigned int)session->gpsdata.subframe.sub5.almanac.M0, (unsigned int)session->gpsdata.subframe.sub5.almanac.af0, (unsigned int)session->gpsdata.subframe.sub5.almanac.af1); nmea_add_checksum(bufp); } } #ifdef AIVDM_ENABLE #define GETLEFT(a) (((a%6) == 0) ? 0 : (6 - (a%6))) static void gpsd_binary_ais_dump(struct gps_device_t *session, char bufp[], size_t len) { char type[8] = "!AIVDM"; unsigned char data[256]; unsigned int msg1, msg2; char numc[4]; char channel; unsigned int left; unsigned int datalen; unsigned int offset; channel = 'A'; if (session->driver.aivdm.ais_channel == 'B') { channel = 'B'; } memset(data, 0, sizeof(data)); datalen = ais_binary_encode(&session->gpsdata.ais, &data[0], 0); if (datalen > 6*60) { static int number1 = 0; msg1 = datalen / (6*60); if ((datalen % (6*60)) != 0) { msg1 += 1; } numc[0] = '0' + (char)(number1 & 0x0f); numc[1] = '\0'; number1 += 1; if (number1 > 9) { number1 = 0; } offset = 0; for (msg2=1;msg2<=msg1;msg2++) { unsigned char old; old = '\0'; if (strlen((char *)&data[(msg2-1)*60]) > 60) { old = data[(msg2-0)*60]; data[(msg2-0)*60] = '\0'; } if (datalen >= (6*60)) { left = 0; datalen -= 6*60; } else { left = GETLEFT(datalen); } (void)snprintf(&bufp[offset], len-offset, "%s,%u,%u,%s,%c,%s,%u", type, msg1, msg2, numc, channel, (char *)&data[(msg2-1)*60], left); nmea_add_checksum(&bufp[offset]); if (old != (unsigned char)'\0') { data[(msg2-0)*60] = old; } offset = (unsigned int) strlen(bufp); } } else if (datalen > 0) { msg1 = 1; msg2 = 1; numc[0] = '\0'; left = GETLEFT(datalen); (void)snprintf(bufp, len, "%s,%u,%u,%s,%c,%s,%u", type, msg1, msg2, numc, channel, (char *)data, left); nmea_add_checksum(bufp); } if (session->gpsdata.ais.type == 24) { msg1 = 1; msg2 = 1; numc[0] = '\0'; memset(data, 0, sizeof(data)); datalen = ais_binary_encode(&session->gpsdata.ais, &data[0], 1); if (datalen > 0) { left = GETLEFT(datalen); offset = (unsigned int)strlen(bufp); (void)snprintf(&bufp[offset], len-offset, "%s,%u,%u,%s,%c,%s,%u", type, msg1, msg2, numc, channel, (char *)data, left); nmea_add_checksum(bufp+offset); } } } #endif /* AIVDM_ENABLE */ /* *INDENT-OFF* */ void nmea_tpv_dump(struct gps_device_t *session, char bufp[], size_t len) { bufp[0] = '\0'; if ((session->gpsdata.set & TIME_SET) != 0) gpsd_binary_time_dump(session, bufp + strlen(bufp), len - strlen(bufp)); if ((session->gpsdata.set & LATLON_SET) != 0) { gpsd_position_fix_dump(session, bufp + strlen(bufp), len - strlen(bufp)); gpsd_transit_fix_dump(session, bufp + strlen(bufp), len - strlen(bufp)); } if ((session->gpsdata.set & (MODE_SET | DOP_SET | USED_IS | HERR_SET | VERR_SET)) != 0) gpsd_binary_quality_dump(session, bufp + strlen(bufp), len - strlen(bufp)); } /* *INDENT-ON* */ void nmea_sky_dump(struct gps_device_t *session, char bufp[], size_t len) { bufp[0] = '\0'; if ((session->gpsdata.set & SATELLITE_SET) != 0) gpsd_binary_satellite_dump(session, bufp + strlen(bufp), len - strlen(bufp)); } void nmea_subframe_dump(struct gps_device_t *session, char bufp[], size_t len) { bufp[0] = '\0'; if ((session->gpsdata.set & SUBFRAME_SET) != 0) gpsd_binary_almanac_dump(session, bufp + strlen(bufp), len - strlen(bufp)); } #ifdef AIVDM_ENABLE void nmea_ais_dump(struct gps_device_t *session, char bufp[], size_t len) { bufp[0] = '\0'; if ((session->gpsdata.set & AIS_SET) != 0) gpsd_binary_ais_dump(session, bufp + strlen(bufp), len - strlen(bufp)); } #endif /* AIVDM_ENABLE */ /* pseudonmea.c ends here */