sensord: add support for Quectel raw gps (#24027)
	
		
	
				
					
				
			* connecting to rawgps
* dumping rawfps packets
* this works, but it might be useless
* fix parsing
* parsing 0x1476
* compare
* compare better
* refactoring
* parsing and broadcasting GPS packet
* glonass support
* compare using submaster
* fix compare for glonass
* update cereal
* make linter happy
* other linter issue
* last mypy complaints
* add support for publishing gpsLocationExternal
* set flags to 1 to match ubloxd
* ready to ship
* qcomdiag
* use unused variables
* last one
Co-authored-by: Comma Device <device@comma.ai>
Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
old-commit-hash: 1df3c86999
			
			
				taco
			
			
		
							parent
							
								
									e818667aad
								
							
						
					
					
						commit
						e929e32bc7
					
				
				 5 changed files with 595 additions and 0 deletions
			
			
		| @ -0,0 +1,66 @@ | ||||
| #!/usr/bin/env python3 | ||||
| import cereal.messaging as messaging | ||||
| from laika import constants | ||||
| 
 | ||||
| if __name__ == "__main__": | ||||
|   sm = messaging.SubMaster(['ubloxGnss', 'qcomGnss']) | ||||
| 
 | ||||
|   meas = None | ||||
|   while 1: | ||||
|     sm.update() | ||||
|     if sm['ubloxGnss'].which() == "measurementReport": | ||||
|       meas = sm['ubloxGnss'].measurementReport.measurements | ||||
|     if not sm.updated['qcomGnss'] or meas is None: | ||||
|       continue | ||||
|     report = sm['qcomGnss'].measurementReport | ||||
|     if report.source not in [0, 1]: | ||||
|       continue | ||||
|     GLONASS = report.source == 1 | ||||
|     recv_time = report.milliseconds / 1000 | ||||
| 
 | ||||
|     car = [] | ||||
|     print("qcom has ", list(sorted([x.svId for x in report.sv]))) | ||||
|     print("ublox has", list(sorted([x.svId for x in meas if x.gnssId == (6 if GLONASS else 0)]))) | ||||
|     for i in report.sv: | ||||
|       # match to ublox | ||||
|       tm = None | ||||
|       for m in meas: | ||||
|         if i.svId == m.svId and m.gnssId == 0 and m.sigId == 0 and not GLONASS: | ||||
|           tm = m | ||||
|         if (i.svId-64) == m.svId and m.gnssId == 6 and m.sigId == 0 and GLONASS: | ||||
|           tm = m | ||||
|       if tm is None: | ||||
|         continue | ||||
|        | ||||
|       if not i.measurementStatus.measurementNotUsable and i.measurementStatus.satelliteTimeIsKnown: | ||||
|         sat_time = (i.unfilteredMeasurementIntegral + i.unfilteredMeasurementFraction + i.latency) / 1000 | ||||
|         ublox_psuedorange = tm.pseudorange | ||||
|         qcom_psuedorange = (recv_time - sat_time)*constants.SPEED_OF_LIGHT | ||||
|         if GLONASS: | ||||
|           glonass_freq = tm.glonassFrequencyIndex - 7 | ||||
|           ublox_speed = -(constants.SPEED_OF_LIGHT / (constants.GLONASS_L1 + glonass_freq*constants.GLONASS_L1_DELTA)) * (tm.doppler) | ||||
|         else: | ||||
|           ublox_speed = -(constants.SPEED_OF_LIGHT / constants.GPS_L1) * tm.doppler | ||||
|         qcom_speed = i.unfilteredSpeed | ||||
|         car.append((i.svId, tm.pseudorange, ublox_speed, qcom_psuedorange, qcom_speed, tm.cno)) | ||||
| 
 | ||||
|     if len(car) == 0: | ||||
|       print("nothing to compare") | ||||
|       continue | ||||
| 
 | ||||
|     pr_err, speed_err = 0., 0. | ||||
|     for c in car: | ||||
|       ublox_psuedorange, ublox_speed, qcom_psuedorange, qcom_speed = c[1:5] | ||||
|       pr_err += ublox_psuedorange - qcom_psuedorange | ||||
|       speed_err += ublox_speed - qcom_speed | ||||
|     pr_err /= len(car) | ||||
|     speed_err /= len(car) | ||||
|     print("avg psuedorange err %f avg speed err %f" % (pr_err, speed_err)) | ||||
|     for c in sorted(car, key=lambda x: abs(x[1] - x[3] - pr_err)): | ||||
|       svid, ublox_psuedorange, ublox_speed, qcom_psuedorange, qcom_speed, cno = c | ||||
|       print("svid: %3d  pseudorange: %10.2f m  speed: %8.2f m/s   meas: %12.2f  speed: %10.2f   meas_err: %10.3f speed_err: %8.3f cno: %d" % | ||||
|         (svid, ublox_psuedorange, ublox_speed, qcom_psuedorange, qcom_speed, | ||||
|         ublox_psuedorange - qcom_psuedorange - pr_err, ublox_speed - qcom_speed - speed_err, cno)) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| @ -0,0 +1,101 @@ | ||||
| import os | ||||
| import time | ||||
| from serial import Serial | ||||
| from crcmod import mkCrcFun | ||||
| from struct import pack, unpack_from, calcsize | ||||
| 
 | ||||
| class ModemDiag: | ||||
|   def __init__(self): | ||||
|     self.serial = self.open_serial() | ||||
| 
 | ||||
|   def open_serial(self): | ||||
|     def op(): | ||||
|       return Serial("/dev/ttyUSB0", baudrate=115200, rtscts=True, dsrdtr=True) | ||||
|     try: | ||||
|       serial = op() | ||||
|     except Exception: | ||||
|       # TODO: this is a hack to get around modemmanager's exclusive open | ||||
|       print("unlocking serial...") | ||||
|       os.system('sudo su -c \'echo "1-1.1:1.0" > /sys/bus/usb/drivers/option/unbind\'') | ||||
|       os.system('sudo su -c \'echo "1-1.1:1.0" > /sys/bus/usb/drivers/option/bind\'') | ||||
|       time.sleep(0.5) | ||||
|       os.system("sudo chmod 666 /dev/ttyUSB0") | ||||
|       serial = op() | ||||
|     serial.flush() | ||||
|     return serial | ||||
| 
 | ||||
|   ccitt_crc16 = mkCrcFun(0x11021, initCrc=0, xorOut=0xffff) | ||||
|   ESCAPE_CHAR = b'\x7d' | ||||
|   TRAILER_CHAR = b'\x7e' | ||||
| 
 | ||||
|   def hdlc_encapsulate(self, payload): | ||||
|     payload += pack('<H', ModemDiag.ccitt_crc16(payload)) | ||||
|     payload = payload.replace(self.ESCAPE_CHAR, bytes([self.ESCAPE_CHAR[0], self.ESCAPE_CHAR[0] ^ 0x20])) | ||||
|     payload = payload.replace(self.TRAILER_CHAR, bytes([self.ESCAPE_CHAR[0], self.TRAILER_CHAR[0] ^ 0x20])) | ||||
|     payload += self.TRAILER_CHAR | ||||
|     return payload | ||||
| 
 | ||||
|   def hdlc_decapsulate(self, payload): | ||||
|     assert len(payload) >= 3 | ||||
|     assert payload[-1:] == self.TRAILER_CHAR | ||||
|     payload = payload[:-1] | ||||
|     payload = payload.replace(bytes([self.ESCAPE_CHAR[0], self.TRAILER_CHAR[0] ^ 0x20]), self.TRAILER_CHAR) | ||||
|     payload = payload.replace(bytes([self.ESCAPE_CHAR[0], self.ESCAPE_CHAR[0] ^ 0x20]), self.ESCAPE_CHAR) | ||||
|     assert payload[-2:] == pack('<H', ModemDiag.ccitt_crc16(payload[:-2])) | ||||
|     return payload[:-2] | ||||
| 
 | ||||
|   def recv(self): | ||||
|     raw_payload = [] | ||||
|     while 1: | ||||
|       char_read = self.serial.read() | ||||
|       raw_payload.append(char_read) | ||||
|       if char_read.endswith(self.TRAILER_CHAR): | ||||
|         break | ||||
|     raw_payload = b''.join(raw_payload) | ||||
|     unframed_message = self.hdlc_decapsulate(raw_payload) | ||||
|     return unframed_message[0], unframed_message[1:] | ||||
| 
 | ||||
|   def send(self, packet_type, packet_payload): | ||||
|     self.serial.write(self.hdlc_encapsulate(bytes([packet_type]) + packet_payload)) | ||||
| 
 | ||||
| # *** end class *** | ||||
| 
 | ||||
| DIAG_LOG_F = 16 | ||||
| DIAG_LOG_CONFIG_F = 115 | ||||
| LOG_CONFIG_RETRIEVE_ID_RANGES_OP = 1 | ||||
| LOG_CONFIG_SET_MASK_OP = 3 | ||||
| LOG_CONFIG_SUCCESS_S = 0 | ||||
| 
 | ||||
| def send_recv(diag, packet_type, packet_payload): | ||||
|   diag.send(packet_type, packet_payload) | ||||
|   while 1: | ||||
|     opcode, payload = diag.recv() | ||||
|     if opcode != DIAG_LOG_F: | ||||
|       break | ||||
|   return opcode, payload | ||||
| 
 | ||||
| def setup_logs(diag, types_to_log): | ||||
|   opcode, payload = send_recv(diag, DIAG_LOG_CONFIG_F, pack('<3xI', LOG_CONFIG_RETRIEVE_ID_RANGES_OP)) | ||||
| 
 | ||||
|   header_spec = '<3xII' | ||||
|   operation, status = unpack_from(header_spec, payload) | ||||
|   assert operation == LOG_CONFIG_RETRIEVE_ID_RANGES_OP | ||||
|   assert status == LOG_CONFIG_SUCCESS_S | ||||
| 
 | ||||
|   log_masks = unpack_from('<16I', payload, calcsize(header_spec)) | ||||
| 
 | ||||
|   for log_type, log_mask_bitsize in enumerate(log_masks): | ||||
|     if log_mask_bitsize: | ||||
|       log_mask = [0] * ((log_mask_bitsize+7)//8) | ||||
|       for i in range(log_mask_bitsize): | ||||
|         if ((log_type<<12)|i) in types_to_log: | ||||
|           log_mask[i//8] |= 1 << (i%8) | ||||
|       opcode, payload = send_recv(diag, DIAG_LOG_CONFIG_F, pack('<3xIII', | ||||
|           LOG_CONFIG_SET_MASK_OP, | ||||
|           log_type, | ||||
|           log_mask_bitsize | ||||
|       ) + bytes(log_mask)) | ||||
|       assert opcode == DIAG_LOG_CONFIG_F | ||||
|       operation, status = unpack_from(header_spec, payload) | ||||
|       assert operation == LOG_CONFIG_SET_MASK_OP | ||||
|       assert status == LOG_CONFIG_SUCCESS_S | ||||
| @ -0,0 +1,201 @@ | ||||
| #!/usr/bin/env python3 | ||||
| import os | ||||
| import sys | ||||
| import signal | ||||
| import itertools | ||||
| import math | ||||
| from typing import NoReturn | ||||
| from struct import unpack_from, calcsize | ||||
| 
 | ||||
| import cereal.messaging as messaging | ||||
| from cereal import log | ||||
| from selfdrive.swaglog import cloudlog | ||||
| 
 | ||||
| from selfdrive.sensord.rawgps.modemdiag import ModemDiag, DIAG_LOG_F, setup_logs | ||||
| from selfdrive.sensord.rawgps.structs import dict_unpacker | ||||
| from selfdrive.sensord.rawgps.structs import gps_measurement_report, gps_measurement_report_sv | ||||
| from selfdrive.sensord.rawgps.structs import glonass_measurement_report, glonass_measurement_report_sv | ||||
| from selfdrive.sensord.rawgps.structs import LOG_GNSS_GPS_MEASUREMENT_REPORT, LOG_GNSS_GLONASS_MEASUREMENT_REPORT | ||||
| from selfdrive.sensord.rawgps.structs import position_report, LOG_GNSS_POSITION_REPORT | ||||
| 
 | ||||
| miscStatusFields = { | ||||
|   "multipathEstimateIsValid": 0, | ||||
|   "directionIsValid": 1, | ||||
| } | ||||
| 
 | ||||
| measurementStatusFields = { | ||||
|   "subMillisecondIsValid": 0, | ||||
|   "subBitTimeIsKnown": 1, | ||||
|   "satelliteTimeIsKnown": 2, | ||||
|   "bitEdgeConfirmedFromSignal": 3, | ||||
|   "measuredVelocity": 4, | ||||
|   "fineOrCoarseVelocity": 5, | ||||
|   "lockPointValid": 6, | ||||
|   "lockPointPositive": 7, | ||||
| 
 | ||||
|   "lastUpdateFromDifference": 9, | ||||
|   "lastUpdateFromVelocityDifference": 10, | ||||
|   "strongIndicationOfCrossCorelation": 11, | ||||
|   "tentativeMeasurement": 12, | ||||
|   "measurementNotUsable": 13, | ||||
|   "sirCheckIsNeeded": 14, | ||||
|   "probationMode": 15, | ||||
| 
 | ||||
|   "multipathIndicator": 24, | ||||
|   "imdJammingIndicator": 25, | ||||
|   "lteB13TxJammingIndicator": 26, | ||||
|   "freshMeasurementIndicator": 27, | ||||
| } | ||||
| 
 | ||||
| measurementStatusGPSFields = { | ||||
|   "gpsRoundRobinRxDiversity": 18, | ||||
|   "gpsRxDiversity": 19, | ||||
|   "gpsLowBandwidthRxDiversityCombined": 20, | ||||
|   "gpsHighBandwidthNu4": 21, | ||||
|   "gpsHighBandwidthNu8": 22, | ||||
|   "gpsHighBandwidthUniform": 23, | ||||
| } | ||||
| 
 | ||||
| measurementStatusGlonassFields = { | ||||
|   "glonassMeanderBitEdgeValid": 16, | ||||
|   "glonassTimeMarkValid": 17 | ||||
| } | ||||
| 
 | ||||
| def main() -> NoReturn: | ||||
|   unpack_gps_meas, size_gps_meas = dict_unpacker(gps_measurement_report, True) | ||||
|   unpack_gps_meas_sv, size_gps_meas_sv = dict_unpacker(gps_measurement_report_sv, True) | ||||
| 
 | ||||
|   unpack_glonass_meas, size_glonass_meas = dict_unpacker(glonass_measurement_report, True) | ||||
|   unpack_glonass_meas_sv, size_glonass_meas_sv = dict_unpacker(glonass_measurement_report_sv, True) | ||||
| 
 | ||||
|   log_types = [ | ||||
|     LOG_GNSS_GPS_MEASUREMENT_REPORT, | ||||
|     LOG_GNSS_GLONASS_MEASUREMENT_REPORT, | ||||
|   ] | ||||
|   pub_types = ['qcomGnss'] | ||||
|   if int(os.getenv("PUBLISH_EXTERNAL", "0")) == 1: | ||||
|     unpack_position, _ = dict_unpacker(position_report) | ||||
|     log_types.append(LOG_GNSS_POSITION_REPORT) | ||||
|     pub_types.append("gpsLocationExternal") | ||||
| 
 | ||||
|   os.system("mmcli -m 0 --location-enable-gps-raw --location-enable-gps-nmea") | ||||
|   diag = ModemDiag() | ||||
| 
 | ||||
|   def try_setup_logs(diag, log_types): | ||||
|     for _ in range(5): | ||||
|       try: | ||||
|         setup_logs(diag, log_types) | ||||
|         break | ||||
|       except Exception: | ||||
|         pass | ||||
| 
 | ||||
|   def disable_logs(sig, frame): | ||||
|     os.system("mmcli -m 0 --location-disable-gps-raw --location-disable-gps-nmea") | ||||
|     cloudlog.warning("rawgpsd: shutting down") | ||||
|     try_setup_logs(diag, []) | ||||
|     cloudlog.warning("rawgpsd: logs disabled") | ||||
|     sys.exit(0) | ||||
|   signal.signal(signal.SIGINT, disable_logs) | ||||
|   try_setup_logs(diag, log_types) | ||||
|   cloudlog.warning("rawgpsd: setup logs done") | ||||
| 
 | ||||
|   pm = messaging.PubMaster(pub_types) | ||||
| 
 | ||||
|   while 1: | ||||
|     opcode, payload = diag.recv() | ||||
|     assert opcode == DIAG_LOG_F | ||||
|     (pending_msgs, log_outer_length), inner_log_packet = unpack_from('<BH', payload), payload[calcsize('<BH'):] | ||||
|     if pending_msgs > 0: | ||||
|       cloudlog.debug("have %d pending messages" % pending_msgs) | ||||
|     assert log_outer_length == len(inner_log_packet) | ||||
|     (log_inner_length, log_type, log_time), log_payload = unpack_from('<HHQ', inner_log_packet), inner_log_packet[calcsize('<HHQ'):] | ||||
|     assert log_inner_length == len(inner_log_packet) | ||||
|     if log_type not in log_types: | ||||
|       continue | ||||
|     #print("got log: %x" % log_type) | ||||
| 
 | ||||
|     if log_type == LOG_GNSS_POSITION_REPORT: | ||||
|       report = unpack_position(log_payload) | ||||
|       if report["u_PosSource"] != 2: | ||||
|         continue | ||||
|       vNED = [report["q_FltVelEnuMps[1]"], report["q_FltVelEnuMps[0]"], -report["q_FltVelEnuMps[2]"]] | ||||
|       vNEDsigma = [report["q_FltVelSigmaMps[1]"], report["q_FltVelSigmaMps[0]"], -report["q_FltVelSigmaMps[2]"]] | ||||
| 
 | ||||
|       msg = messaging.new_message('gpsLocationExternal') | ||||
|       gps = msg.gpsLocationExternal | ||||
|       gps.flags = 1 | ||||
|       gps.latitude = report["t_DblFinalPosLatLon[0]"] * 180/math.pi | ||||
|       gps.longitude = report["t_DblFinalPosLatLon[1]"] * 180/math.pi | ||||
|       gps.altitude = report["q_FltFinalPosAlt"] | ||||
|       gps.speed = math.sqrt(sum([x**2 for x in vNED])) | ||||
|       gps.bearingDeg = report["q_FltHeadingRad"] * 180/math.pi | ||||
|       # TODO: this probably isn't right, use laika for this | ||||
|       gps.timestamp = report['w_GpsWeekNumber']*604800*1000 + report['q_GpsFixTimeMs'] | ||||
|       gps.source = log.GpsLocationData.SensorSource.qcomdiag | ||||
|       gps.vNED = vNED | ||||
|       gps.verticalAccuracy = report["q_FltVdop"] | ||||
|       gps.bearingAccuracyDeg = report["q_FltHeadingUncRad"] * 180/math.pi | ||||
|       gps.speedAccuracy = math.sqrt(sum([x**2 for x in vNEDsigma])) | ||||
| 
 | ||||
|       pm.send('gpsLocationExternal', msg) | ||||
| 
 | ||||
|     if log_type in [LOG_GNSS_GPS_MEASUREMENT_REPORT, LOG_GNSS_GLONASS_MEASUREMENT_REPORT]: | ||||
|       msg = messaging.new_message('qcomGnss') | ||||
| 
 | ||||
|       gnss = msg.qcomGnss | ||||
|       gnss.logTs = log_time | ||||
|       gnss.init('measurementReport') | ||||
|       report = gnss.measurementReport | ||||
| 
 | ||||
|       if log_type == LOG_GNSS_GPS_MEASUREMENT_REPORT: | ||||
|         dat = unpack_gps_meas(log_payload) | ||||
|         sats = log_payload[size_gps_meas:] | ||||
|         unpack_meas_sv, size_meas_sv = unpack_gps_meas_sv, size_gps_meas_sv | ||||
|         report.source = 0  # gps | ||||
|         measurement_status_fields = (measurementStatusFields.items(), measurementStatusGPSFields.items()) | ||||
|       elif log_type == LOG_GNSS_GLONASS_MEASUREMENT_REPORT: | ||||
|         dat = unpack_glonass_meas(log_payload) | ||||
|         sats = log_payload[size_glonass_meas:] | ||||
|         unpack_meas_sv, size_meas_sv = unpack_glonass_meas_sv, size_glonass_meas_sv | ||||
|         report.source = 1  # glonass | ||||
|         measurement_status_fields = (measurementStatusFields.items(), measurementStatusGlonassFields.items()) | ||||
|       else: | ||||
|         assert False | ||||
| 
 | ||||
|       for k,v in dat.items(): | ||||
|         if k == "version": | ||||
|           assert v == 0 | ||||
|         elif k == "week": | ||||
|           report.gpsWeek = v | ||||
|         elif k == "svCount": | ||||
|           pass | ||||
|         else: | ||||
|           setattr(report, k, v) | ||||
|       assert len(sats)//dat['svCount'] == size_meas_sv | ||||
|       report.init('sv', dat['svCount']) | ||||
|       for i in range(dat['svCount']): | ||||
|         sv = report.sv[i] | ||||
|         sv.init('measurementStatus') | ||||
|         sat = unpack_meas_sv(sats[size_meas_sv*i:size_meas_sv*(i+1)]) | ||||
|         for k,v in sat.items(): | ||||
|           if k == "parityErrorCount": | ||||
|             sv.gpsParityErrorCount = v | ||||
|           elif k == "frequencyIndex": | ||||
|             sv.glonassFrequencyIndex = v | ||||
|           elif k == "hemmingErrorCount": | ||||
|             sv.glonassHemmingErrorCount = v | ||||
|           elif k == "measurementStatus": | ||||
|             for kk,vv in itertools.chain(*measurement_status_fields): | ||||
|               setattr(sv.measurementStatus, kk, bool(v & (1<<vv))) | ||||
|           elif k == "miscStatus": | ||||
|             for kk,vv in miscStatusFields.items(): | ||||
|               setattr(sv.measurementStatus, kk, bool(v & (1<<vv))) | ||||
|           elif k == "pad": | ||||
|             pass | ||||
|           else: | ||||
|             setattr(sv, k, v) | ||||
| 
 | ||||
|       pm.send('qcomGnss', msg) | ||||
| 
 | ||||
| if __name__ == "__main__": | ||||
|   main() | ||||
| @ -0,0 +1,224 @@ | ||||
| from struct import unpack_from, calcsize | ||||
| 
 | ||||
| LOG_GNSS_POSITION_REPORT = 0x1476 | ||||
| LOG_GNSS_GPS_MEASUREMENT_REPORT = 0x1477 | ||||
| LOG_GNSS_CLOCK_REPORT = 0x1478 | ||||
| LOG_GNSS_GLONASS_MEASUREMENT_REPORT = 0x1480 | ||||
| LOG_GNSS_BDS_MEASUREMENT_REPORT = 0x1756 | ||||
| LOG_GNSS_GAL_MEASUREMENT_REPORT = 0x1886 | ||||
| 
 | ||||
| LOG_GNSS_OEMDRE_MEASUREMENT_REPORT = 0x14DE | ||||
| LOG_GNSS_OEMDRE_SVPOLY_REPORT = 0x14E1 | ||||
| 
 | ||||
| LOG_GNSS_ME_DPO_STATUS = 0x1838 | ||||
| LOG_GNSS_CD_DB_REPORT = 0x147B | ||||
| LOG_GNSS_PRX_RF_HW_STATUS_REPORT = 0x147E | ||||
| LOG_CGPS_SLOW_CLOCK_CLIB_REPORT = 0x1488 | ||||
| LOG_GNSS_CONFIGURATION_STATE = 0x1516 | ||||
| 
 | ||||
| glonass_measurement_report = """ | ||||
|   uint8_t version; | ||||
|   uint32_t f_count; | ||||
|   uint8_t glonass_cycle_number; | ||||
|   uint16_t glonass_number_of_days; | ||||
|   uint32_t milliseconds; | ||||
|   float time_bias; | ||||
|   float clock_time_uncertainty; | ||||
|   float clock_frequency_bias; | ||||
|   float clock_frequency_uncertainty; | ||||
|   uint8_t sv_count; | ||||
| """ | ||||
| 
 | ||||
| glonass_measurement_report_sv = """ | ||||
|   uint8_t sv_id; | ||||
|   int8_t frequency_index; | ||||
|   uint8_t observation_state; // SVObservationStates | ||||
|   uint8_t observations; | ||||
|   uint8_t good_observations; | ||||
|   uint8_t hemming_error_count; | ||||
|   uint8_t filter_stages; | ||||
|   uint16_t carrier_noise; | ||||
|   int16_t latency; | ||||
|   uint8_t predetect_interval; | ||||
|   uint16_t postdetections; | ||||
|   uint32_t unfiltered_measurement_integral; | ||||
|   float unfiltered_measurement_fraction; | ||||
|   float unfiltered_time_uncertainty; | ||||
|   float unfiltered_speed; | ||||
|   float unfiltered_speed_uncertainty; | ||||
|   uint32_t measurement_status; | ||||
|   uint8_t misc_status; | ||||
|   uint32_t multipath_estimate; | ||||
|   float azimuth; | ||||
|   float elevation; | ||||
|   int32_t carrier_phase_cycles_integral; | ||||
|   uint16_t carrier_phase_cycles_fraction; | ||||
|   float fine_speed; | ||||
|   float fine_speed_uncertainty; | ||||
|   uint8_t cycle_slip_count; | ||||
|   uint32_t pad; | ||||
| """ | ||||
| 
 | ||||
| gps_measurement_report = """ | ||||
|   uint8_t version; | ||||
|   uint32_t f_count; | ||||
|   uint16_t week; | ||||
|   uint32_t milliseconds; | ||||
|   float time_bias; | ||||
|   float clock_time_uncertainty; | ||||
|   float clock_frequency_bias; | ||||
|   float clock_frequency_uncertainty; | ||||
|   uint8_t sv_count; | ||||
| """ | ||||
| 
 | ||||
| gps_measurement_report_sv = """ | ||||
|   uint8_t sv_id; | ||||
|   uint8_t observation_state; // SVObservationStates | ||||
|   uint8_t observations; | ||||
|   uint8_t good_observations; | ||||
|   uint16_t parity_error_count; | ||||
|   uint8_t filter_stages; | ||||
|   uint16_t carrier_noise; | ||||
|   int16_t latency; | ||||
|   uint8_t predetect_interval; | ||||
|   uint16_t postdetections; | ||||
|   uint32_t unfiltered_measurement_integral; | ||||
|   float unfiltered_measurement_fraction; | ||||
|   float unfiltered_time_uncertainty; | ||||
|   float unfiltered_speed; | ||||
|   float unfiltered_speed_uncertainty; | ||||
|   uint32_t measurement_status; | ||||
|   uint8_t misc_status; | ||||
|   uint32_t multipath_estimate; | ||||
|   float azimuth; | ||||
|   float elevation; | ||||
|   int32_t carrier_phase_cycles_integral; | ||||
|   uint16_t carrier_phase_cycles_fraction; | ||||
|   float fine_speed; | ||||
|   float fine_speed_uncertainty; | ||||
|   uint8_t cycle_slip_count; | ||||
|   uint32_t pad; | ||||
| """ | ||||
| 
 | ||||
| position_report = """ | ||||
|   uint8       u_Version;                /* Version number of DM log */ | ||||
|   uint32      q_Fcount;                 /* Local millisecond counter */ | ||||
|   uint8       u_PosSource;              /* Source of position information */ /*  0: None 1: Weighted least-squares 2: Kalman filter 3: Externally injected 4: Internal database    */ | ||||
|   uint32      q_Reserved1;              /* Reserved memory field */ | ||||
|   uint16      w_PosVelFlag;             /* Position velocity bit field: (see DM log 0x1476 documentation) */ | ||||
|   uint32      q_PosVelFlag2;            /* Position velocity 2 bit field: (see DM log 0x1476 documentation) */ | ||||
|   uint8       u_FailureCode;            /* Failure code: (see DM log 0x1476 documentation) */ | ||||
|   uint16      w_FixEvents;              /* Fix events bit field: (see DM log 0x1476 documentation) */ | ||||
|   uint32 _fake_align_week_number; | ||||
|   uint16      w_GpsWeekNumber;          /* GPS week number of position */ | ||||
|   uint32      q_GpsFixTimeMs;           /* GPS fix time of week of in milliseconds */ | ||||
|   uint8       u_GloNumFourYear;         /* Number of Glonass four year cycles */ | ||||
|   uint16      w_GloNumDaysInFourYear;   /* Glonass calendar day in four year cycle */ | ||||
|   uint32      q_GloFixTimeMs;           /* Glonass fix time of day in milliseconds */ | ||||
|   uint32      q_PosCount;               /* Integer count of the number of unique positions reported */ | ||||
|   uint64      t_DblFinalPosLatLon[2];   /* Final latitude and longitude of position in radians */ | ||||
|   uint32      q_FltFinalPosAlt;         /* Final height-above-ellipsoid altitude of position */ | ||||
|   uint32      q_FltHeadingRad;          /* User heading in radians */ | ||||
|   uint32      q_FltHeadingUncRad;       /* User heading uncertainty in radians */ | ||||
|   uint32      q_FltVelEnuMps[3];        /* User velocity in east, north, up coordinate frame. In meters per second. */ | ||||
|   uint32      q_FltVelSigmaMps[3];      /* Gaussian 1-sigma value for east, north, up components of user velocity */ | ||||
|   uint32      q_FltClockBiasMeters;     /* Receiver clock bias in meters */ | ||||
|   uint32      q_FltClockBiasSigmaMeters;  /* Gaussian 1-sigma value for receiver clock bias in meters */ | ||||
|   uint32      q_FltGGTBMeters;          /* GPS to Glonass time bias in meters */ | ||||
|   uint32      q_FltGGTBSigmaMeters;     /* Gaussian 1-sigma value for GPS to Glonass time bias uncertainty in meters */ | ||||
|   uint32      q_FltGBTBMeters;          /* GPS to BeiDou time bias in meters */ | ||||
|   uint32      q_FltGBTBSigmaMeters;     /* Gaussian 1-sigma value for GPS to BeiDou time bias uncertainty in meters */ | ||||
|   uint32      q_FltBGTBMeters;          /* BeiDou to Glonass time bias in meters */ | ||||
|   uint32      q_FltBGTBSigmaMeters;     /* Gaussian 1-sigma value for BeiDou to Glonass time bias uncertainty in meters */ | ||||
|   uint32      q_FltFiltGGTBMeters;      /* Filtered GPS to Glonass time bias in meters */ | ||||
|   uint32      q_FltFiltGGTBSigmaMeters; /* Filtered Gaussian 1-sigma value for GPS to Glonass time bias uncertainty in meters */ | ||||
|   uint32      q_FltFiltGBTBMeters;      /* Filtered GPS to BeiDou time bias in meters */ | ||||
|   uint32      q_FltFiltGBTBSigmaMeters; /* Filtered Gaussian 1-sigma value for GPS to BeiDou time bias uncertainty in meters */ | ||||
|   uint32      q_FltFiltBGTBMeters;      /* Filtered BeiDou to Glonass time bias in meters */ | ||||
|   uint32      q_FltFiltBGTBSigmaMeters; /* Filtered Gaussian 1-sigma value for BeiDou to Glonass time bias uncertainty in meters */ | ||||
|   uint32      q_FltSftOffsetSec;        /* SFT offset as computed by WLS in seconds */ | ||||
|   uint32      q_FltSftOffsetSigmaSec;   /* Gaussian 1-sigma value for SFT offset in seconds */ | ||||
|   uint32      q_FltClockDriftMps;       /* Clock drift (clock frequency bias) in meters per second */ | ||||
|   uint32      q_FltClockDriftSigmaMps;  /* Gaussian 1-sigma value for clock drift in meters per second */ | ||||
|   uint32      q_FltFilteredAlt;         /* Filtered height-above-ellipsoid altitude in meters as computed by WLS */ | ||||
|   uint32      q_FltFilteredAltSigma;    /* Gaussian 1-sigma value for filtered height-above-ellipsoid altitude in meters */ | ||||
|   uint32      q_FltRawAlt;              /* Raw height-above-ellipsoid altitude in meters as computed by WLS */ | ||||
|   uint32      q_FltRawAltSigma;         /* Gaussian 1-sigma value for raw height-above-ellipsoid altitude in meters */ | ||||
|   uint32   align_Flt[14]; | ||||
|   uint32      q_FltPdop;                /* 3D position dilution of precision as computed from the unweighted  | ||||
|   uint32      q_FltHdop;                /* Horizontal position dilution of precision as computed from the unweighted least-squares covariance matrix */ | ||||
|   uint32      q_FltVdop;                /* Vertical position dilution of precision as computed from the unweighted least-squares covariance matrix */ | ||||
|   uint8       u_EllipseConfidence;      /* Statistical measure of the confidence (percentage) associated with the uncertainty ellipse values */ | ||||
|   uint32      q_FltEllipseAngle;        /* Angle of semimajor axis with respect to true North, with increasing angles moving clockwise from North. In units of degrees. */ | ||||
|   uint32      q_FltEllipseSemimajorAxis;  /* Semimajor axis of final horizontal position uncertainty error ellipse.  In units of meters. */ | ||||
|   uint32      q_FltEllipseSemiminorAxis;  /* Semiminor axis of final horizontal position uncertainty error ellipse.  In units of meters. */ | ||||
|   uint32      q_FltPosSigmaVertical;    /* Gaussian 1-sigma value for final position height-above-ellipsoid altitude in meters */ | ||||
|   uint8       u_HorizontalReliability;  /* Horizontal position reliability 0: Not set 1: Very Low 2: Low 3: Medium 4: High    */ | ||||
|   uint8       u_VerticalReliability;    /* Vertical position reliability */ | ||||
|   uint16      w_Reserved2;              /* Reserved memory field */ | ||||
|   uint32      q_FltGnssHeadingRad;      /* User heading in radians derived from GNSS only solution  */ | ||||
|   uint32      q_FltGnssHeadingUncRad;   /* User heading uncertainty in radians derived from GNSS only solution  */ | ||||
|   uint32      q_SensorDataUsageMask;    /* Denotes which additional sensor data were used to compute this position fix.  BIT[0] 0x00000001 <96> Accelerometer BIT[1] 0x00000002 <96> Gyro 0x0000FFFC - Reserved A bit set to 1 indicates that certain fields as defined by the SENSOR_AIDING_MASK were aided with sensor data*/ | ||||
|   uint32      q_SensorAidMask;         /* Denotes which component of the position report was assisted with additional sensors defined in SENSOR_DATA_USAGE_MASK BIT[0] 0x00000001 <96> Heading aided with sensor data BIT[1] 0x00000002 <96> Speed aided with sensor data BIT[2] 0x00000004 <96> Position aided with sensor data BIT[3] 0x00000008 <96> Velocity aided with sensor data 0xFFFFFFF0 <96> Reserved */ | ||||
|   uint8       u_NumGpsSvsUsed;          /* The number of GPS SVs used in the fix */ | ||||
|   uint8       u_TotalGpsSvs;            /* Total number of GPS SVs detected by searcher, including ones not used in position calculation */ | ||||
|   uint8       u_NumGloSvsUsed;          /* The number of Glonass SVs used in the fix */ | ||||
|   uint8       u_TotalGloSvs;            /* Total number of Glonass SVs detected by searcher, including ones not used in position calculation */ | ||||
|   uint8       u_NumBdsSvsUsed;          /* The number of BeiDou SVs used in the fix */ | ||||
|   uint8       u_TotalBdsSvs;            /* Total number of BeiDou SVs detected by searcher, including ones not used in position calculation */ | ||||
| """ | ||||
| 
 | ||||
| def name_to_camelcase(nam): | ||||
|   ret = [] | ||||
|   i = 0 | ||||
|   while i < len(nam): | ||||
|     if nam[i] == "_": | ||||
|       ret.append(nam[i+1].upper()) | ||||
|       i += 2 | ||||
|     else: | ||||
|       ret.append(nam[i]) | ||||
|       i += 1 | ||||
|   return ''.join(ret) | ||||
| 
 | ||||
| def parse_struct(ss): | ||||
|   st = "<" | ||||
|   nams = [] | ||||
|   for l in ss.strip().split("\n"): | ||||
|     typ, nam = l.split(";")[0].split() | ||||
|     #print(typ, nam) | ||||
|     if typ == "float" or '_Flt' in nam: | ||||
|       st += "f" | ||||
|     elif typ == "double" or '_Dbl' in nam: | ||||
|       st += "d" | ||||
|     elif typ in ["uint8", "uint8_t"]: | ||||
|       st += "B" | ||||
|     elif typ in ["int8", "int8_t"]: | ||||
|       st += "b" | ||||
|     elif typ in ["uint32", "uint32_t"]: | ||||
|       st += "I" | ||||
|     elif typ in ["int32", "int32_t"]: | ||||
|       st += "i" | ||||
|     elif typ in ["uint16", "uint16_t"]: | ||||
|       st += "H" | ||||
|     elif typ in ["int16", "int16_t"]: | ||||
|       st += "h" | ||||
|     elif typ == "uint64": | ||||
|       st += "Q" | ||||
|     else: | ||||
|       print("unknown type", typ) | ||||
|       assert False | ||||
|     if '[' in nam: | ||||
|       cnt = int(nam.split("[")[1].split("]")[0]) | ||||
|       st += st[-1]*(cnt-1) | ||||
|       for i in range(cnt): | ||||
|         nams.append("%s[%d]" % (nam.split("[")[0], i)) | ||||
|     else: | ||||
|       nams.append(nam) | ||||
|   return st, nams | ||||
| 
 | ||||
| def dict_unpacker(ss, camelcase = False): | ||||
|   st, nams = parse_struct(ss) | ||||
|   if camelcase: | ||||
|     nams = [name_to_camelcase(x) for x in nams] | ||||
|   sz = calcsize(st) | ||||
|   return lambda x: dict(zip(nams, unpack_from(st, x))), sz | ||||
					Loading…
					
					
				
		Reference in new issue