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