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>pull/24042/head
parent
ea74a90ca0
commit
1df3c86999
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