/* * File: M10Gtop.cpp * Author: Viproz * Used code from rs1729 * Created on December 13, 2018, 4:39 PM */ /* #define stdFLEN 0x64 // pos[0]=0x64 #define pos_GPSTOW 0x0A // 4 byte #define pos_GPSlat 0x0E // 4 byte #define pos_GPSlon 0x12 // 4 byte #define pos_GPSalt 0x16 // 4 byte #define pos_GPSweek 0x20 // 2 byte //Velocity East-North-Up (ENU) #define pos_GPSvE 0x04 // 2 byte #define pos_GPSvN 0x06 // 2 byte #define pos_GPSvU 0x08 // 2 byte #define pos_SN 0x5D // 2+3 byte #define pos_Check (stdFLEN-1) // 2 byte*/ #include "M10TrimbleParser.h" #include #include #include char M10TrimbleParser::similarData[] = "xxxx----------------------xxxxxxxxxxxxxxxxxxxxxxxxxxx---xxxxxxx--xxxx-----xx----xxxxx------xxxxxxx---"; char M10TrimbleParser::insertSpaces[] = "---xx-x-x-x---x---x---x---x-----x-x-----------x---x--x--x-----xx-x-x-x-xx-x-x-x-x----x---x-x-x----xxxx-x-------------x"; M10TrimbleParser::M10TrimbleParser() { } M10TrimbleParser::~M10TrimbleParser() { } void M10TrimbleParser::changeData(std::array data, bool good) { M10GeneralParser::changeData(data, good); int i; unsigned byte; unsigned short gpsweek_bytes[2]; //Number of weeks for (i = 0; i < 2; i++) { byte = frame_bytes[0x20 + i]; gpsweek_bytes[i] = byte; } week = (gpsweek_bytes[0] << 8) + gpsweek_bytes[1]; //Time in ms unsigned short gpstime_bytes[4]; for (i = 0; i < 4; i++) { byte = frame_bytes[0x0A + i]; gpstime_bytes[i] = byte; } time = 0; for (i = 0; i < 4; i++) { time |= gpstime_bytes[i] << (8 * (3 - i)); } gps2Date(week, time / 1000, &year, &month, &day); } double M10TrimbleParser::getLatitude() { int i; unsigned byte; unsigned short gpslat_bytes[4]; int gpslat; double B60B60 = 0xB60B60; for (i = 0; i < 4; i++) { byte = frame_bytes[0x0E + i]; gpslat_bytes[i] = byte; } gpslat = 0; for (i = 0; i < 4; i++) { gpslat |= gpslat_bytes[i] << (8 * (3 - i)); } return gpslat / B60B60; } double M10TrimbleParser::getLongitude() { int i; unsigned byte; unsigned short gpslon_bytes[4]; int gpslon; double B60B60 = 0xB60B60; for (i = 0; i < 4; i++) { byte = frame_bytes[0x12 + i]; gpslon_bytes[i] = byte; } gpslon = 0; for (i = 0; i < 4; i++) { gpslon |= gpslon_bytes[i] << (8 * (3 - i)); } return gpslon / B60B60; } double M10TrimbleParser::getAltitude() { int i; unsigned byte; unsigned short gpsalt_bytes[4]; int gpsalt; for (i = 0; i < 4; i++) { byte = frame_bytes[0x16 + i]; gpsalt_bytes[i] = byte; } gpsalt = 0; for (i = 0; i < 4; i++) { gpsalt |= gpsalt_bytes[i] << (8 * (3 - i)); } return gpsalt / 1000.0; } int M10TrimbleParser::getDay() { return day; } int M10TrimbleParser::getMonth() { return month; } int M10TrimbleParser::getYear() { return year; } int M10TrimbleParser::getHours() { return (time / 1000 % (24 * 3600)) / 3600; } int M10TrimbleParser::getMinutes() { return ((time / 1000 % (24 * 3600)) % 3600) / 60; } int M10TrimbleParser::getSeconds() { return (time / 1000 % (24 * 3600)) % 60; } int M10TrimbleParser::getSatellites() { unsigned char sats; sats = frame_bytes[30]; return sats; } double M10TrimbleParser::getVerticalSpeed() { int i; unsigned byte; unsigned short gpsVel_bytes[2]; short vel16; const double ms2kn100 = 2e2; // m/s -> knots: 1 m/s = 3.6/1.852 kn = 1.94 kn for (i = 0; i < 2; i++) { byte = frame_bytes[0x08 + i]; gpsVel_bytes[i] = byte; } vel16 = gpsVel_bytes[0] << 8 | gpsVel_bytes[1]; return vel16 / ms2kn100; } double M10TrimbleParser::getHorizontalSpeed() { int i; unsigned byte; unsigned short gpsVel_bytes[2]; short vel16; double vx, vy; const double ms2kn100 = 2e2; // m/s -> knots: 1 m/s = 3.6/1.852 kn = 1.94 kn for (i = 0; i < 2; i++) { byte = frame_bytes[0x04 + i]; gpsVel_bytes[i] = byte; } vel16 = gpsVel_bytes[0] << 8 | gpsVel_bytes[1]; vx = vel16 / ms2kn100; // est for (i = 0; i < 2; i++) { byte = frame_bytes[0x06 + i]; gpsVel_bytes[i] = byte; } vel16 = gpsVel_bytes[0] << 8 | gpsVel_bytes[1]; vy = vel16 / ms2kn100; // nord return sqrt(vx * vx + vy * vy); } double M10TrimbleParser::getDirection() { int i; unsigned byte; unsigned short gpsVel_bytes[2]; short vel16; double vx, vy, dir; const double ms2kn100 = 2e2; // m/s -> knots: 1 m/s = 3.6/1.852 kn = 1.94 kn for (i = 0; i < 2; i++) { byte = frame_bytes[0x04 + i]; gpsVel_bytes[i] = byte; } vel16 = gpsVel_bytes[0] << 8 | gpsVel_bytes[1]; vx = vel16 / ms2kn100; // est for (i = 0; i < 2; i++) { byte = frame_bytes[0x06 + i]; gpsVel_bytes[i] = byte; } vel16 = gpsVel_bytes[0] << 8 | gpsVel_bytes[1]; vy = vel16 / ms2kn100; // nord ///* dir = atan2(vx, vy)*180 / M_PI; if (dir < 0) dir += 360; return dir; } double M10TrimbleParser::getTemperature() { // NTC-Thermistor Shibaura PB5-41E // T00 = 273.15 + 0.0 , R00 = 15e3 // T25 = 273.15 + 25.0 , R25 = 5.369e3 // B00 = 3450.0 Kelvin // 0C..100C, poor fit low temps // [ T/C , R/1e3 ] ( [P__-43]/2.0 ): // [ -50.0 , 204.0 ] // [ -45.0 , 150.7 ] // [ -40.0 , 112.6 ] // [ -35.0 , 84.90 ] // [ -30.0 , 64.65 ] // [ -25.0 , 49.66 ] // [ -20.0 , 38.48 ] // [ -15.0 , 30.06 ] // [ -10.0 , 23.67 ] // [ -5.0 , 18.78 ] // [ 0.0 , 15.00 ] // [ 5.0 , 12.06 ] // [ 10.0 , 9.765 ] // [ 15.0 , 7.955 ] // [ 20.0 , 6.515 ] // [ 25.0 , 5.370 ] // [ 30.0 , 4.448 ] // [ 35.0 , 3.704 ] // [ 40.0 , 3.100 ] // -> Steinhart–Hart coefficients (polyfit): float p0 = 1.07303516e-03, p1 = 2.41296733e-04, p2 = 2.26744154e-06, p3 = 6.52855181e-08; // T/K = 1/( p0 + p1*ln(R) + p2*ln(R)^2 + p3*ln(R)^3 ) // range/scale 0, 1, 2: // M10-pcb float Rs[3] = {12.1e3, 36.5e3, 475.0e3}; // bias/series float Rp[3] = {1e20, 330.0e3, 3000.0e3}; // parallel, Rp[0]=inf unsigned char scT; // {0,1,2}, range/scale voltage divider unsigned short ADC_RT; // ADC12 P6.7(A7) , adr_0377h,adr_0376h //unsigned short Tcal[2]; // adr_1000h[scT*4] float adc_max = 4095.0; // ADC12 float x, R; float T = 0; // T/Kelvin scT = frame_bytes[0x3E]; // adr_0455h ADC_RT = (frame_bytes[0x40] << 8) | frame_bytes[0x3F]; ADC_RT -= 0xA000; //Tcal[0] = (frame_bytes[0x42] << 8) | frame_bytes[0x41]; // Unused for now //Tcal[1] = (frame_bytes[0x44] << 8) | frame_bytes[0x43]; x = (adc_max - ADC_RT) / ADC_RT; // (Vcc-Vout)/Vout if (scT < 3) R = Rs[scT] / (x - Rs[scT] / Rp[scT]); else R = -1; if (R > 0) T = 1 / (p0 + p1 * log(R) + p2 * log(R) * log(R) + p3 * log(R) * log(R) * log(R)); /*if (1) { // on-chip temperature unsigned short ADC_Ti_raw = (frame_bytes[0x49] << 8) | frame_bytes[0x48]; // int.temp.diode, ref: 4095->1.5V float vti, ti; // INCH1A (temp.diode), slau144 vti = ADC_Ti_raw / 4095.0 * 1.5; // V_REF+ = 1.5V, no calibration ti = (vti - 0.986) / 0.00355; // 0.986/0.00355=277.75, 1.5/4095/0.00355=0.1032 fprintf(stdout, " (Ti:%.1fC)\n", ti); // SegmentA-Calibration: //ui16_t T30 = adr_10e2h; // CAL_ADC_15T30 //ui16_t T85 = adr_10e4h; // CAL_ADC_15T85 //float tic = (ADC_Ti_raw-T30)*(85.0-30.0)/(T85-T30) + 30.0; //fprintf(stdout, " (Tic:%.1fC)", tic); }//*/ return T - 273.15; // Celsius } double M10TrimbleParser::getHumidity() { return 0; } double M10TrimbleParser::getDp() { return 0; } double M10TrimbleParser::getBatteryLevel() { unsigned short batLvl; batLvl = (frame_bytes[70] << 8) | frame_bytes[69]; // Thanks F5MVO for the formula ! return (double)batLvl/1000.*6.62; } std::string M10TrimbleParser::getSerialNumber() { int i; unsigned byte; unsigned short sn_bytes[5]; char SN[18]; for (i = 0; i < 17; i++) SN[i] = ' '; SN[17] = '\0'; for (i = 0; i < 5; i++) { byte = frame_bytes[0x5D + i]; sn_bytes[i] = byte; } /* * The serial number is in the form M10-A-BCC-D-EEEE * - A is the frame type, T for Trimble, the original GPS used for this modulation * G for Gtop GPS * - B is the year of fabrication (8 = 2018) * - CC is the month of fabrication * - D is the product type, 2 is production type * - EEEE is the RS serial number NOTE: Removed -T from callsign. 2019-09-21 */ byte = sn_bytes[2]; sprintf(SN, "M10-%1X%02u", (byte >> 4)&0xF, byte & 0xF); byte = sn_bytes[3] | (sn_bytes[4] << 8); sprintf(SN + 7, "-%1X-%1u%04u", sn_bytes[0]&0xF, (byte >> 13)&0x7, byte & 0x1FFF); return SN; } std::string M10TrimbleParser::getdxlSerialNumber() { int i; unsigned byte; unsigned short sn_bytes[5]; for (i = 0; i < 5; i++) { byte = frame_bytes[0x5D + i]; sn_bytes[i] = byte; } // The way used by dxlARPS used for compatibility. uint32_t id; char ids[9]; id = (uint32_t) (((uint32_t) ((uint32_t) (uint8_t) sn_bytes[4] + 256UL * (uint32_t) (uint8_t) sn_bytes[3] + 65536UL * (uint32_t) (uint8_t) sn_bytes[2])^(uint32_t) ((uint32_t) (uint8_t) sn_bytes[0] / 16UL + 16UL * (uint32_t) (uint8_t) sn_bytes[1] + 4096UL * (uint32_t) (uint8_t) sn_bytes[2]))&0xFFFFFUL); i = 8UL; ids[8U] = 0; --i; do { ids[i] = (char) (id % 10UL + 48UL); id = id / 10UL; --i; } while (i != 1UL); ids[i] = 'E'; --i; ids[i] = 'M'; return ids; } std::array M10TrimbleParser::replaceWithPrevious(std::array data) { unsigned short valMax; unsigned short posMax; if (!correctCRC) { // Use probabilities int threshold = statValues[0][0x64] / 2; // more than 50% if (threshold > 4) { // Meaning less under 4 values for (int i = 0; i < FRAME_LEN; ++i) { if (similarData[i] == 'x') { valMax = 0; posMax = 0; for (unsigned short k = 0; k < 0xFF + 1; ++k) { // Find maximum if (statValues[i][k] > valMax) { valMax = statValues[i][k]; posMax = k; } } data[i] = posMax; } } } } else { // Use correct frame for (int i = 0; i < FRAME_LEN; ++i) { if (similarData[i] == 'x') { data[i] = frame_bytes[i]; } } } return data; } void M10TrimbleParser::printFrame() { if (dispRaw) { for (int i = 0; i < frameLength + 1; ++i) { if (insertSpaces[i] == 'x') printf(" "); printf("%02X", frame_bytes[i]); } if (correctCRC) printf(" [OK]"); else printf(" [NO]"); printf("\n"); } else { setenv("TZ", "", 1); // Set local timezone to UTC time_t frame = 0; struct tm timeinfo; timeinfo.tm_hour = getHours(); timeinfo.tm_min = getMinutes(); timeinfo.tm_sec = getSeconds(); timeinfo.tm_mday = getDay(); timeinfo.tm_mon = getMonth() - 1; timeinfo.tm_year = getYear() - 1900; timeinfo.tm_isdst = 0; frame = mktime(&timeinfo); // Aux data tag if the payload lenght is long std::string auxstr = ""; if (frame_bytes[0x00] != 0x64) { auxstr = "\"aux\": \"not_supported_yet\", "; } // Decoder sensible to comma at the end, strict json printf("{ " "\"sub_type\": \"%s\", " "\"frame\": %ld, " "\"id\": \"%s\", " "\"dxlid\": \"%s\", " "\"datetime\": \"%04d-%02d-%02dT%02d:%02d:%02dZ\", " "%s" // Aux data "\"sats\": %d, " "\"lat\": %.5f, " "\"lon\": %.5f, " "\"alt\": %.2f, " "\"vel_h\": %.5f, " "\"heading\": %.5f, " "\"vel_v\": %.2f, " "\"temp\": %.1f, " "\"battery\": %.2f, " "\"crc\": %d " "}\n", "Trimble", frame, getSerialNumber().c_str(), getdxlSerialNumber().c_str(), getYear(), getMonth(), getDay(), getHours(), getMinutes(), getSeconds(), auxstr.c_str(), getSatellites(), getLatitude(), getLongitude(), getAltitude(), getHorizontalSpeed(), getDirection(), getVerticalSpeed(), getTemperature(), getBatteryLevel(), correctCRC); } fflush(stdout); } /* * Convert GPS Week and Seconds to Modified Julian Day. * - Adapted from sci.astro FAQ. * - Ignores UTC leap seconds. */ void M10TrimbleParser::gps2Date(long GpsWeek, long GpsSeconds, int *Year, int *Month, int *Day) { long GpsDays, Mjd; long J, C, Y, M; GpsDays = GpsWeek * 7 + (GpsSeconds / 86400); Mjd = 44244 + GpsDays; J = Mjd + 2468570; C = 4 * J / 146097; J = J - (146097 * C + 3) / 4; Y = 4000 * (J + 1) / 1461001; J = J - 1461 * Y / 4 + 31; M = 80 * J / 2447; *Day = J - 2447 * M / 80; J = M / 11; *Month = M + 2 - (12 * J); *Year = 100 * (C - 49) + Y + J; }