Release 260111
This commit is contained in:
247
system/ubloxd/generated/glonass.py
Normal file
247
system/ubloxd/generated/glonass.py
Normal file
@@ -0,0 +1,247 @@
|
||||
# This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild
|
||||
|
||||
import kaitaistruct
|
||||
from kaitaistruct import KaitaiStruct, KaitaiStream, BytesIO
|
||||
|
||||
|
||||
if getattr(kaitaistruct, 'API_VERSION', (0, 9)) < (0, 9):
|
||||
raise Exception("Incompatible Kaitai Struct Python API: 0.9 or later is required, but you have %s" % (kaitaistruct.__version__))
|
||||
|
||||
class Glonass(KaitaiStruct):
|
||||
def __init__(self, _io, _parent=None, _root=None):
|
||||
self._io = _io
|
||||
self._parent = _parent
|
||||
self._root = _root if _root else self
|
||||
self._read()
|
||||
|
||||
def _read(self):
|
||||
self.idle_chip = self._io.read_bits_int_be(1) != 0
|
||||
self.string_number = self._io.read_bits_int_be(4)
|
||||
# workaround for kaitai bit alignment issue (see glonass_fix.patch for C++)
|
||||
# self._io.align_to_byte()
|
||||
_on = self.string_number
|
||||
if _on == 4:
|
||||
self.data = Glonass.String4(self._io, self, self._root)
|
||||
elif _on == 1:
|
||||
self.data = Glonass.String1(self._io, self, self._root)
|
||||
elif _on == 3:
|
||||
self.data = Glonass.String3(self._io, self, self._root)
|
||||
elif _on == 5:
|
||||
self.data = Glonass.String5(self._io, self, self._root)
|
||||
elif _on == 2:
|
||||
self.data = Glonass.String2(self._io, self, self._root)
|
||||
else:
|
||||
self.data = Glonass.StringNonImmediate(self._io, self, self._root)
|
||||
self.hamming_code = self._io.read_bits_int_be(8)
|
||||
self.pad_1 = self._io.read_bits_int_be(11)
|
||||
self.superframe_number = self._io.read_bits_int_be(16)
|
||||
self.pad_2 = self._io.read_bits_int_be(8)
|
||||
self.frame_number = self._io.read_bits_int_be(8)
|
||||
|
||||
class String4(KaitaiStruct):
|
||||
def __init__(self, _io, _parent=None, _root=None):
|
||||
self._io = _io
|
||||
self._parent = _parent
|
||||
self._root = _root if _root else self
|
||||
self._read()
|
||||
|
||||
def _read(self):
|
||||
self.tau_n_sign = self._io.read_bits_int_be(1) != 0
|
||||
self.tau_n_value = self._io.read_bits_int_be(21)
|
||||
self.delta_tau_n_sign = self._io.read_bits_int_be(1) != 0
|
||||
self.delta_tau_n_value = self._io.read_bits_int_be(4)
|
||||
self.e_n = self._io.read_bits_int_be(5)
|
||||
self.not_used_1 = self._io.read_bits_int_be(14)
|
||||
self.p4 = self._io.read_bits_int_be(1) != 0
|
||||
self.f_t = self._io.read_bits_int_be(4)
|
||||
self.not_used_2 = self._io.read_bits_int_be(3)
|
||||
self.n_t = self._io.read_bits_int_be(11)
|
||||
self.n = self._io.read_bits_int_be(5)
|
||||
self.m = self._io.read_bits_int_be(2)
|
||||
|
||||
@property
|
||||
def tau_n(self):
|
||||
if hasattr(self, '_m_tau_n'):
|
||||
return self._m_tau_n
|
||||
|
||||
self._m_tau_n = ((self.tau_n_value * -1) if self.tau_n_sign else self.tau_n_value)
|
||||
return getattr(self, '_m_tau_n', None)
|
||||
|
||||
@property
|
||||
def delta_tau_n(self):
|
||||
if hasattr(self, '_m_delta_tau_n'):
|
||||
return self._m_delta_tau_n
|
||||
|
||||
self._m_delta_tau_n = ((self.delta_tau_n_value * -1) if self.delta_tau_n_sign else self.delta_tau_n_value)
|
||||
return getattr(self, '_m_delta_tau_n', None)
|
||||
|
||||
|
||||
class StringNonImmediate(KaitaiStruct):
|
||||
def __init__(self, _io, _parent=None, _root=None):
|
||||
self._io = _io
|
||||
self._parent = _parent
|
||||
self._root = _root if _root else self
|
||||
self._read()
|
||||
|
||||
def _read(self):
|
||||
self.data_1 = self._io.read_bits_int_be(64)
|
||||
self.data_2 = self._io.read_bits_int_be(8)
|
||||
|
||||
|
||||
class String5(KaitaiStruct):
|
||||
def __init__(self, _io, _parent=None, _root=None):
|
||||
self._io = _io
|
||||
self._parent = _parent
|
||||
self._root = _root if _root else self
|
||||
self._read()
|
||||
|
||||
def _read(self):
|
||||
self.n_a = self._io.read_bits_int_be(11)
|
||||
self.tau_c = self._io.read_bits_int_be(32)
|
||||
self.not_used = self._io.read_bits_int_be(1) != 0
|
||||
self.n_4 = self._io.read_bits_int_be(5)
|
||||
self.tau_gps = self._io.read_bits_int_be(22)
|
||||
self.l_n = self._io.read_bits_int_be(1) != 0
|
||||
|
||||
|
||||
class String1(KaitaiStruct):
|
||||
def __init__(self, _io, _parent=None, _root=None):
|
||||
self._io = _io
|
||||
self._parent = _parent
|
||||
self._root = _root if _root else self
|
||||
self._read()
|
||||
|
||||
def _read(self):
|
||||
self.not_used = self._io.read_bits_int_be(2)
|
||||
self.p1 = self._io.read_bits_int_be(2)
|
||||
self.t_k = self._io.read_bits_int_be(12)
|
||||
self.x_vel_sign = self._io.read_bits_int_be(1) != 0
|
||||
self.x_vel_value = self._io.read_bits_int_be(23)
|
||||
self.x_accel_sign = self._io.read_bits_int_be(1) != 0
|
||||
self.x_accel_value = self._io.read_bits_int_be(4)
|
||||
self.x_sign = self._io.read_bits_int_be(1) != 0
|
||||
self.x_value = self._io.read_bits_int_be(26)
|
||||
|
||||
@property
|
||||
def x_vel(self):
|
||||
if hasattr(self, '_m_x_vel'):
|
||||
return self._m_x_vel
|
||||
|
||||
self._m_x_vel = ((self.x_vel_value * -1) if self.x_vel_sign else self.x_vel_value)
|
||||
return getattr(self, '_m_x_vel', None)
|
||||
|
||||
@property
|
||||
def x_accel(self):
|
||||
if hasattr(self, '_m_x_accel'):
|
||||
return self._m_x_accel
|
||||
|
||||
self._m_x_accel = ((self.x_accel_value * -1) if self.x_accel_sign else self.x_accel_value)
|
||||
return getattr(self, '_m_x_accel', None)
|
||||
|
||||
@property
|
||||
def x(self):
|
||||
if hasattr(self, '_m_x'):
|
||||
return self._m_x
|
||||
|
||||
self._m_x = ((self.x_value * -1) if self.x_sign else self.x_value)
|
||||
return getattr(self, '_m_x', None)
|
||||
|
||||
|
||||
class String2(KaitaiStruct):
|
||||
def __init__(self, _io, _parent=None, _root=None):
|
||||
self._io = _io
|
||||
self._parent = _parent
|
||||
self._root = _root if _root else self
|
||||
self._read()
|
||||
|
||||
def _read(self):
|
||||
self.b_n = self._io.read_bits_int_be(3)
|
||||
self.p2 = self._io.read_bits_int_be(1) != 0
|
||||
self.t_b = self._io.read_bits_int_be(7)
|
||||
self.not_used = self._io.read_bits_int_be(5)
|
||||
self.y_vel_sign = self._io.read_bits_int_be(1) != 0
|
||||
self.y_vel_value = self._io.read_bits_int_be(23)
|
||||
self.y_accel_sign = self._io.read_bits_int_be(1) != 0
|
||||
self.y_accel_value = self._io.read_bits_int_be(4)
|
||||
self.y_sign = self._io.read_bits_int_be(1) != 0
|
||||
self.y_value = self._io.read_bits_int_be(26)
|
||||
|
||||
@property
|
||||
def y_vel(self):
|
||||
if hasattr(self, '_m_y_vel'):
|
||||
return self._m_y_vel
|
||||
|
||||
self._m_y_vel = ((self.y_vel_value * -1) if self.y_vel_sign else self.y_vel_value)
|
||||
return getattr(self, '_m_y_vel', None)
|
||||
|
||||
@property
|
||||
def y_accel(self):
|
||||
if hasattr(self, '_m_y_accel'):
|
||||
return self._m_y_accel
|
||||
|
||||
self._m_y_accel = ((self.y_accel_value * -1) if self.y_accel_sign else self.y_accel_value)
|
||||
return getattr(self, '_m_y_accel', None)
|
||||
|
||||
@property
|
||||
def y(self):
|
||||
if hasattr(self, '_m_y'):
|
||||
return self._m_y
|
||||
|
||||
self._m_y = ((self.y_value * -1) if self.y_sign else self.y_value)
|
||||
return getattr(self, '_m_y', None)
|
||||
|
||||
|
||||
class String3(KaitaiStruct):
|
||||
def __init__(self, _io, _parent=None, _root=None):
|
||||
self._io = _io
|
||||
self._parent = _parent
|
||||
self._root = _root if _root else self
|
||||
self._read()
|
||||
|
||||
def _read(self):
|
||||
self.p3 = self._io.read_bits_int_be(1) != 0
|
||||
self.gamma_n_sign = self._io.read_bits_int_be(1) != 0
|
||||
self.gamma_n_value = self._io.read_bits_int_be(10)
|
||||
self.not_used = self._io.read_bits_int_be(1) != 0
|
||||
self.p = self._io.read_bits_int_be(2)
|
||||
self.l_n = self._io.read_bits_int_be(1) != 0
|
||||
self.z_vel_sign = self._io.read_bits_int_be(1) != 0
|
||||
self.z_vel_value = self._io.read_bits_int_be(23)
|
||||
self.z_accel_sign = self._io.read_bits_int_be(1) != 0
|
||||
self.z_accel_value = self._io.read_bits_int_be(4)
|
||||
self.z_sign = self._io.read_bits_int_be(1) != 0
|
||||
self.z_value = self._io.read_bits_int_be(26)
|
||||
|
||||
@property
|
||||
def gamma_n(self):
|
||||
if hasattr(self, '_m_gamma_n'):
|
||||
return self._m_gamma_n
|
||||
|
||||
self._m_gamma_n = ((self.gamma_n_value * -1) if self.gamma_n_sign else self.gamma_n_value)
|
||||
return getattr(self, '_m_gamma_n', None)
|
||||
|
||||
@property
|
||||
def z_vel(self):
|
||||
if hasattr(self, '_m_z_vel'):
|
||||
return self._m_z_vel
|
||||
|
||||
self._m_z_vel = ((self.z_vel_value * -1) if self.z_vel_sign else self.z_vel_value)
|
||||
return getattr(self, '_m_z_vel', None)
|
||||
|
||||
@property
|
||||
def z_accel(self):
|
||||
if hasattr(self, '_m_z_accel'):
|
||||
return self._m_z_accel
|
||||
|
||||
self._m_z_accel = ((self.z_accel_value * -1) if self.z_accel_sign else self.z_accel_value)
|
||||
return getattr(self, '_m_z_accel', None)
|
||||
|
||||
@property
|
||||
def z(self):
|
||||
if hasattr(self, '_m_z'):
|
||||
return self._m_z
|
||||
|
||||
self._m_z = ((self.z_value * -1) if self.z_sign else self.z_value)
|
||||
return getattr(self, '_m_z', None)
|
||||
|
||||
|
||||
193
system/ubloxd/generated/gps.py
Normal file
193
system/ubloxd/generated/gps.py
Normal file
@@ -0,0 +1,193 @@
|
||||
# This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild
|
||||
|
||||
import kaitaistruct
|
||||
from kaitaistruct import KaitaiStruct, KaitaiStream, BytesIO
|
||||
|
||||
|
||||
if getattr(kaitaistruct, 'API_VERSION', (0, 9)) < (0, 9):
|
||||
raise Exception("Incompatible Kaitai Struct Python API: 0.9 or later is required, but you have %s" % (kaitaistruct.__version__))
|
||||
|
||||
class Gps(KaitaiStruct):
|
||||
def __init__(self, _io, _parent=None, _root=None):
|
||||
self._io = _io
|
||||
self._parent = _parent
|
||||
self._root = _root if _root else self
|
||||
self._read()
|
||||
|
||||
def _read(self):
|
||||
self.tlm = Gps.Tlm(self._io, self, self._root)
|
||||
self.how = Gps.How(self._io, self, self._root)
|
||||
_on = self.how.subframe_id
|
||||
if _on == 1:
|
||||
self.body = Gps.Subframe1(self._io, self, self._root)
|
||||
elif _on == 2:
|
||||
self.body = Gps.Subframe2(self._io, self, self._root)
|
||||
elif _on == 3:
|
||||
self.body = Gps.Subframe3(self._io, self, self._root)
|
||||
elif _on == 4:
|
||||
self.body = Gps.Subframe4(self._io, self, self._root)
|
||||
|
||||
class Subframe1(KaitaiStruct):
|
||||
def __init__(self, _io, _parent=None, _root=None):
|
||||
self._io = _io
|
||||
self._parent = _parent
|
||||
self._root = _root if _root else self
|
||||
self._read()
|
||||
|
||||
def _read(self):
|
||||
self.week_no = self._io.read_bits_int_be(10)
|
||||
self.code = self._io.read_bits_int_be(2)
|
||||
self.sv_accuracy = self._io.read_bits_int_be(4)
|
||||
self.sv_health = self._io.read_bits_int_be(6)
|
||||
self.iodc_msb = self._io.read_bits_int_be(2)
|
||||
self.l2_p_data_flag = self._io.read_bits_int_be(1) != 0
|
||||
self.reserved1 = self._io.read_bits_int_be(23)
|
||||
self.reserved2 = self._io.read_bits_int_be(24)
|
||||
self.reserved3 = self._io.read_bits_int_be(24)
|
||||
self.reserved4 = self._io.read_bits_int_be(16)
|
||||
self._io.align_to_byte()
|
||||
self.t_gd = self._io.read_s1()
|
||||
self.iodc_lsb = self._io.read_u1()
|
||||
self.t_oc = self._io.read_u2be()
|
||||
self.af_2 = self._io.read_s1()
|
||||
self.af_1 = self._io.read_s2be()
|
||||
self.af_0_sign = self._io.read_bits_int_be(1) != 0
|
||||
self.af_0_value = self._io.read_bits_int_be(21)
|
||||
self.reserved5 = self._io.read_bits_int_be(2)
|
||||
|
||||
@property
|
||||
def af_0(self):
|
||||
if hasattr(self, '_m_af_0'):
|
||||
return self._m_af_0
|
||||
|
||||
self._m_af_0 = ((self.af_0_value - (1 << 21)) if self.af_0_sign else self.af_0_value)
|
||||
return getattr(self, '_m_af_0', None)
|
||||
|
||||
|
||||
class Subframe3(KaitaiStruct):
|
||||
def __init__(self, _io, _parent=None, _root=None):
|
||||
self._io = _io
|
||||
self._parent = _parent
|
||||
self._root = _root if _root else self
|
||||
self._read()
|
||||
|
||||
def _read(self):
|
||||
self.c_ic = self._io.read_s2be()
|
||||
self.omega_0 = self._io.read_s4be()
|
||||
self.c_is = self._io.read_s2be()
|
||||
self.i_0 = self._io.read_s4be()
|
||||
self.c_rc = self._io.read_s2be()
|
||||
self.omega = self._io.read_s4be()
|
||||
self.omega_dot_sign = self._io.read_bits_int_be(1) != 0
|
||||
self.omega_dot_value = self._io.read_bits_int_be(23)
|
||||
self._io.align_to_byte()
|
||||
self.iode = self._io.read_u1()
|
||||
self.idot_sign = self._io.read_bits_int_be(1) != 0
|
||||
self.idot_value = self._io.read_bits_int_be(13)
|
||||
self.reserved = self._io.read_bits_int_be(2)
|
||||
|
||||
@property
|
||||
def omega_dot(self):
|
||||
if hasattr(self, '_m_omega_dot'):
|
||||
return self._m_omega_dot
|
||||
|
||||
self._m_omega_dot = ((self.omega_dot_value - (1 << 23)) if self.omega_dot_sign else self.omega_dot_value)
|
||||
return getattr(self, '_m_omega_dot', None)
|
||||
|
||||
@property
|
||||
def idot(self):
|
||||
if hasattr(self, '_m_idot'):
|
||||
return self._m_idot
|
||||
|
||||
self._m_idot = ((self.idot_value - (1 << 13)) if self.idot_sign else self.idot_value)
|
||||
return getattr(self, '_m_idot', None)
|
||||
|
||||
|
||||
class Subframe4(KaitaiStruct):
|
||||
def __init__(self, _io, _parent=None, _root=None):
|
||||
self._io = _io
|
||||
self._parent = _parent
|
||||
self._root = _root if _root else self
|
||||
self._read()
|
||||
|
||||
def _read(self):
|
||||
self.data_id = self._io.read_bits_int_be(2)
|
||||
self.page_id = self._io.read_bits_int_be(6)
|
||||
self._io.align_to_byte()
|
||||
_on = self.page_id
|
||||
if _on == 56:
|
||||
self.body = Gps.Subframe4.IonosphereData(self._io, self, self._root)
|
||||
|
||||
class IonosphereData(KaitaiStruct):
|
||||
def __init__(self, _io, _parent=None, _root=None):
|
||||
self._io = _io
|
||||
self._parent = _parent
|
||||
self._root = _root if _root else self
|
||||
self._read()
|
||||
|
||||
def _read(self):
|
||||
self.a0 = self._io.read_s1()
|
||||
self.a1 = self._io.read_s1()
|
||||
self.a2 = self._io.read_s1()
|
||||
self.a3 = self._io.read_s1()
|
||||
self.b0 = self._io.read_s1()
|
||||
self.b1 = self._io.read_s1()
|
||||
self.b2 = self._io.read_s1()
|
||||
self.b3 = self._io.read_s1()
|
||||
|
||||
|
||||
|
||||
class How(KaitaiStruct):
|
||||
def __init__(self, _io, _parent=None, _root=None):
|
||||
self._io = _io
|
||||
self._parent = _parent
|
||||
self._root = _root if _root else self
|
||||
self._read()
|
||||
|
||||
def _read(self):
|
||||
self.tow_count = self._io.read_bits_int_be(17)
|
||||
self.alert = self._io.read_bits_int_be(1) != 0
|
||||
self.anti_spoof = self._io.read_bits_int_be(1) != 0
|
||||
self.subframe_id = self._io.read_bits_int_be(3)
|
||||
self.reserved = self._io.read_bits_int_be(2)
|
||||
|
||||
|
||||
class Tlm(KaitaiStruct):
|
||||
def __init__(self, _io, _parent=None, _root=None):
|
||||
self._io = _io
|
||||
self._parent = _parent
|
||||
self._root = _root if _root else self
|
||||
self._read()
|
||||
|
||||
def _read(self):
|
||||
self.preamble = self._io.read_bytes(1)
|
||||
if not self.preamble == b"\x8B":
|
||||
raise kaitaistruct.ValidationNotEqualError(b"\x8B", self.preamble, self._io, u"/types/tlm/seq/0")
|
||||
self.tlm = self._io.read_bits_int_be(14)
|
||||
self.integrity_status = self._io.read_bits_int_be(1) != 0
|
||||
self.reserved = self._io.read_bits_int_be(1) != 0
|
||||
|
||||
|
||||
class Subframe2(KaitaiStruct):
|
||||
def __init__(self, _io, _parent=None, _root=None):
|
||||
self._io = _io
|
||||
self._parent = _parent
|
||||
self._root = _root if _root else self
|
||||
self._read()
|
||||
|
||||
def _read(self):
|
||||
self.iode = self._io.read_u1()
|
||||
self.c_rs = self._io.read_s2be()
|
||||
self.delta_n = self._io.read_s2be()
|
||||
self.m_0 = self._io.read_s4be()
|
||||
self.c_uc = self._io.read_s2be()
|
||||
self.e = self._io.read_s4be()
|
||||
self.c_us = self._io.read_s2be()
|
||||
self.sqrt_a = self._io.read_u4be()
|
||||
self.t_oe = self._io.read_u2be()
|
||||
self.fit_interval_flag = self._io.read_bits_int_be(1) != 0
|
||||
self.aoda = self._io.read_bits_int_be(5)
|
||||
self.reserved = self._io.read_bits_int_be(2)
|
||||
|
||||
|
||||
|
||||
273
system/ubloxd/generated/ubx.py
Normal file
273
system/ubloxd/generated/ubx.py
Normal file
@@ -0,0 +1,273 @@
|
||||
# This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild
|
||||
|
||||
import kaitaistruct
|
||||
from kaitaistruct import KaitaiStruct, KaitaiStream, BytesIO
|
||||
from enum import Enum
|
||||
|
||||
|
||||
if getattr(kaitaistruct, 'API_VERSION', (0, 9)) < (0, 9):
|
||||
raise Exception("Incompatible Kaitai Struct Python API: 0.9 or later is required, but you have %s" % (kaitaistruct.__version__))
|
||||
|
||||
class Ubx(KaitaiStruct):
|
||||
|
||||
class GnssType(Enum):
|
||||
gps = 0
|
||||
sbas = 1
|
||||
galileo = 2
|
||||
beidou = 3
|
||||
imes = 4
|
||||
qzss = 5
|
||||
glonass = 6
|
||||
def __init__(self, _io, _parent=None, _root=None):
|
||||
self._io = _io
|
||||
self._parent = _parent
|
||||
self._root = _root if _root else self
|
||||
self._read()
|
||||
|
||||
def _read(self):
|
||||
self.magic = self._io.read_bytes(2)
|
||||
if not self.magic == b"\xB5\x62":
|
||||
raise kaitaistruct.ValidationNotEqualError(b"\xB5\x62", self.magic, self._io, u"/seq/0")
|
||||
self.msg_type = self._io.read_u2be()
|
||||
self.length = self._io.read_u2le()
|
||||
_on = self.msg_type
|
||||
if _on == 2569:
|
||||
self.body = Ubx.MonHw(self._io, self, self._root)
|
||||
elif _on == 533:
|
||||
self.body = Ubx.RxmRawx(self._io, self, self._root)
|
||||
elif _on == 531:
|
||||
self.body = Ubx.RxmSfrbx(self._io, self, self._root)
|
||||
elif _on == 309:
|
||||
self.body = Ubx.NavSat(self._io, self, self._root)
|
||||
elif _on == 2571:
|
||||
self.body = Ubx.MonHw2(self._io, self, self._root)
|
||||
elif _on == 263:
|
||||
self.body = Ubx.NavPvt(self._io, self, self._root)
|
||||
|
||||
class RxmRawx(KaitaiStruct):
|
||||
def __init__(self, _io, _parent=None, _root=None):
|
||||
self._io = _io
|
||||
self._parent = _parent
|
||||
self._root = _root if _root else self
|
||||
self._read()
|
||||
|
||||
def _read(self):
|
||||
self.rcv_tow = self._io.read_f8le()
|
||||
self.week = self._io.read_u2le()
|
||||
self.leap_s = self._io.read_s1()
|
||||
self.num_meas = self._io.read_u1()
|
||||
self.rec_stat = self._io.read_u1()
|
||||
self.reserved1 = self._io.read_bytes(3)
|
||||
self._raw_meas = []
|
||||
self.meas = []
|
||||
for i in range(self.num_meas):
|
||||
self._raw_meas.append(self._io.read_bytes(32))
|
||||
_io__raw_meas = KaitaiStream(BytesIO(self._raw_meas[i]))
|
||||
self.meas.append(Ubx.RxmRawx.Measurement(_io__raw_meas, self, self._root))
|
||||
|
||||
|
||||
class Measurement(KaitaiStruct):
|
||||
def __init__(self, _io, _parent=None, _root=None):
|
||||
self._io = _io
|
||||
self._parent = _parent
|
||||
self._root = _root if _root else self
|
||||
self._read()
|
||||
|
||||
def _read(self):
|
||||
self.pr_mes = self._io.read_f8le()
|
||||
self.cp_mes = self._io.read_f8le()
|
||||
self.do_mes = self._io.read_f4le()
|
||||
self.gnss_id = KaitaiStream.resolve_enum(Ubx.GnssType, self._io.read_u1())
|
||||
self.sv_id = self._io.read_u1()
|
||||
self.reserved2 = self._io.read_bytes(1)
|
||||
self.freq_id = self._io.read_u1()
|
||||
self.lock_time = self._io.read_u2le()
|
||||
self.cno = self._io.read_u1()
|
||||
self.pr_stdev = self._io.read_u1()
|
||||
self.cp_stdev = self._io.read_u1()
|
||||
self.do_stdev = self._io.read_u1()
|
||||
self.trk_stat = self._io.read_u1()
|
||||
self.reserved3 = self._io.read_bytes(1)
|
||||
|
||||
|
||||
|
||||
class RxmSfrbx(KaitaiStruct):
|
||||
def __init__(self, _io, _parent=None, _root=None):
|
||||
self._io = _io
|
||||
self._parent = _parent
|
||||
self._root = _root if _root else self
|
||||
self._read()
|
||||
|
||||
def _read(self):
|
||||
self.gnss_id = KaitaiStream.resolve_enum(Ubx.GnssType, self._io.read_u1())
|
||||
self.sv_id = self._io.read_u1()
|
||||
self.reserved1 = self._io.read_bytes(1)
|
||||
self.freq_id = self._io.read_u1()
|
||||
self.num_words = self._io.read_u1()
|
||||
self.reserved2 = self._io.read_bytes(1)
|
||||
self.version = self._io.read_u1()
|
||||
self.reserved3 = self._io.read_bytes(1)
|
||||
self.body = []
|
||||
for i in range(self.num_words):
|
||||
self.body.append(self._io.read_u4le())
|
||||
|
||||
|
||||
|
||||
class NavSat(KaitaiStruct):
|
||||
def __init__(self, _io, _parent=None, _root=None):
|
||||
self._io = _io
|
||||
self._parent = _parent
|
||||
self._root = _root if _root else self
|
||||
self._read()
|
||||
|
||||
def _read(self):
|
||||
self.itow = self._io.read_u4le()
|
||||
self.version = self._io.read_u1()
|
||||
self.num_svs = self._io.read_u1()
|
||||
self.reserved = self._io.read_bytes(2)
|
||||
self._raw_svs = []
|
||||
self.svs = []
|
||||
for i in range(self.num_svs):
|
||||
self._raw_svs.append(self._io.read_bytes(12))
|
||||
_io__raw_svs = KaitaiStream(BytesIO(self._raw_svs[i]))
|
||||
self.svs.append(Ubx.NavSat.Nav(_io__raw_svs, self, self._root))
|
||||
|
||||
|
||||
class Nav(KaitaiStruct):
|
||||
def __init__(self, _io, _parent=None, _root=None):
|
||||
self._io = _io
|
||||
self._parent = _parent
|
||||
self._root = _root if _root else self
|
||||
self._read()
|
||||
|
||||
def _read(self):
|
||||
self.gnss_id = KaitaiStream.resolve_enum(Ubx.GnssType, self._io.read_u1())
|
||||
self.sv_id = self._io.read_u1()
|
||||
self.cno = self._io.read_u1()
|
||||
self.elev = self._io.read_s1()
|
||||
self.azim = self._io.read_s2le()
|
||||
self.pr_res = self._io.read_s2le()
|
||||
self.flags = self._io.read_u4le()
|
||||
|
||||
|
||||
|
||||
class NavPvt(KaitaiStruct):
|
||||
def __init__(self, _io, _parent=None, _root=None):
|
||||
self._io = _io
|
||||
self._parent = _parent
|
||||
self._root = _root if _root else self
|
||||
self._read()
|
||||
|
||||
def _read(self):
|
||||
self.i_tow = self._io.read_u4le()
|
||||
self.year = self._io.read_u2le()
|
||||
self.month = self._io.read_u1()
|
||||
self.day = self._io.read_u1()
|
||||
self.hour = self._io.read_u1()
|
||||
self.min = self._io.read_u1()
|
||||
self.sec = self._io.read_u1()
|
||||
self.valid = self._io.read_u1()
|
||||
self.t_acc = self._io.read_u4le()
|
||||
self.nano = self._io.read_s4le()
|
||||
self.fix_type = self._io.read_u1()
|
||||
self.flags = self._io.read_u1()
|
||||
self.flags2 = self._io.read_u1()
|
||||
self.num_sv = self._io.read_u1()
|
||||
self.lon = self._io.read_s4le()
|
||||
self.lat = self._io.read_s4le()
|
||||
self.height = self._io.read_s4le()
|
||||
self.h_msl = self._io.read_s4le()
|
||||
self.h_acc = self._io.read_u4le()
|
||||
self.v_acc = self._io.read_u4le()
|
||||
self.vel_n = self._io.read_s4le()
|
||||
self.vel_e = self._io.read_s4le()
|
||||
self.vel_d = self._io.read_s4le()
|
||||
self.g_speed = self._io.read_s4le()
|
||||
self.head_mot = self._io.read_s4le()
|
||||
self.s_acc = self._io.read_s4le()
|
||||
self.head_acc = self._io.read_u4le()
|
||||
self.p_dop = self._io.read_u2le()
|
||||
self.flags3 = self._io.read_u1()
|
||||
self.reserved1 = self._io.read_bytes(5)
|
||||
self.head_veh = self._io.read_s4le()
|
||||
self.mag_dec = self._io.read_s2le()
|
||||
self.mag_acc = self._io.read_u2le()
|
||||
|
||||
|
||||
class MonHw2(KaitaiStruct):
|
||||
|
||||
class ConfigSource(Enum):
|
||||
flash = 102
|
||||
otp = 111
|
||||
config_pins = 112
|
||||
rom = 113
|
||||
def __init__(self, _io, _parent=None, _root=None):
|
||||
self._io = _io
|
||||
self._parent = _parent
|
||||
self._root = _root if _root else self
|
||||
self._read()
|
||||
|
||||
def _read(self):
|
||||
self.ofs_i = self._io.read_s1()
|
||||
self.mag_i = self._io.read_u1()
|
||||
self.ofs_q = self._io.read_s1()
|
||||
self.mag_q = self._io.read_u1()
|
||||
self.cfg_source = KaitaiStream.resolve_enum(Ubx.MonHw2.ConfigSource, self._io.read_u1())
|
||||
self.reserved1 = self._io.read_bytes(3)
|
||||
self.low_lev_cfg = self._io.read_u4le()
|
||||
self.reserved2 = self._io.read_bytes(8)
|
||||
self.post_status = self._io.read_u4le()
|
||||
self.reserved3 = self._io.read_bytes(4)
|
||||
|
||||
|
||||
class MonHw(KaitaiStruct):
|
||||
|
||||
class AntennaStatus(Enum):
|
||||
init = 0
|
||||
dontknow = 1
|
||||
ok = 2
|
||||
short = 3
|
||||
open = 4
|
||||
|
||||
class AntennaPower(Enum):
|
||||
false = 0
|
||||
true = 1
|
||||
dontknow = 2
|
||||
def __init__(self, _io, _parent=None, _root=None):
|
||||
self._io = _io
|
||||
self._parent = _parent
|
||||
self._root = _root if _root else self
|
||||
self._read()
|
||||
|
||||
def _read(self):
|
||||
self.pin_sel = self._io.read_u4le()
|
||||
self.pin_bank = self._io.read_u4le()
|
||||
self.pin_dir = self._io.read_u4le()
|
||||
self.pin_val = self._io.read_u4le()
|
||||
self.noise_per_ms = self._io.read_u2le()
|
||||
self.agc_cnt = self._io.read_u2le()
|
||||
self.a_status = KaitaiStream.resolve_enum(Ubx.MonHw.AntennaStatus, self._io.read_u1())
|
||||
self.a_power = KaitaiStream.resolve_enum(Ubx.MonHw.AntennaPower, self._io.read_u1())
|
||||
self.flags = self._io.read_u1()
|
||||
self.reserved1 = self._io.read_bytes(1)
|
||||
self.used_mask = self._io.read_u4le()
|
||||
self.vp = self._io.read_bytes(17)
|
||||
self.jam_ind = self._io.read_u1()
|
||||
self.reserved2 = self._io.read_bytes(2)
|
||||
self.pin_irq = self._io.read_u4le()
|
||||
self.pull_h = self._io.read_u4le()
|
||||
self.pull_l = self._io.read_u4le()
|
||||
|
||||
|
||||
@property
|
||||
def checksum(self):
|
||||
if hasattr(self, '_m_checksum'):
|
||||
return self._m_checksum
|
||||
|
||||
_pos = self._io.pos()
|
||||
self._io.seek((self.length + 6))
|
||||
self._m_checksum = self._io.read_u2le()
|
||||
self._io.seek(_pos)
|
||||
return getattr(self, '_m_checksum', None)
|
||||
|
||||
|
||||
176
system/ubloxd/glonass.ksy
Normal file
176
system/ubloxd/glonass.ksy
Normal file
@@ -0,0 +1,176 @@
|
||||
# http://gauss.gge.unb.ca/GLONASS.ICD.pdf
|
||||
# some variables are misprinted but good in the old doc
|
||||
# https://www.unavco.org/help/glossary/docs/ICD_GLONASS_4.0_(1998)_en.pdf
|
||||
meta:
|
||||
id: glonass
|
||||
endian: be
|
||||
bit-endian: be
|
||||
seq:
|
||||
- id: idle_chip
|
||||
type: b1
|
||||
- id: string_number
|
||||
type: b4
|
||||
- id: data
|
||||
type:
|
||||
switch-on: string_number
|
||||
cases:
|
||||
1: string_1
|
||||
2: string_2
|
||||
3: string_3
|
||||
4: string_4
|
||||
5: string_5
|
||||
_: string_non_immediate
|
||||
- id: hamming_code
|
||||
type: b8
|
||||
- id: pad_1
|
||||
type: b11
|
||||
- id: superframe_number
|
||||
type: b16
|
||||
- id: pad_2
|
||||
type: b8
|
||||
- id: frame_number
|
||||
type: b8
|
||||
|
||||
types:
|
||||
string_1:
|
||||
seq:
|
||||
- id: not_used
|
||||
type: b2
|
||||
- id: p1
|
||||
type: b2
|
||||
- id: t_k
|
||||
type: b12
|
||||
- id: x_vel_sign
|
||||
type: b1
|
||||
- id: x_vel_value
|
||||
type: b23
|
||||
- id: x_accel_sign
|
||||
type: b1
|
||||
- id: x_accel_value
|
||||
type: b4
|
||||
- id: x_sign
|
||||
type: b1
|
||||
- id: x_value
|
||||
type: b26
|
||||
instances:
|
||||
x_vel:
|
||||
value: 'x_vel_sign ? (x_vel_value * (-1)) : x_vel_value'
|
||||
x_accel:
|
||||
value: 'x_accel_sign ? (x_accel_value * (-1)) : x_accel_value'
|
||||
x:
|
||||
value: 'x_sign ? (x_value * (-1)) : x_value'
|
||||
string_2:
|
||||
seq:
|
||||
- id: b_n
|
||||
type: b3
|
||||
- id: p2
|
||||
type: b1
|
||||
- id: t_b
|
||||
type: b7
|
||||
- id: not_used
|
||||
type: b5
|
||||
- id: y_vel_sign
|
||||
type: b1
|
||||
- id: y_vel_value
|
||||
type: b23
|
||||
- id: y_accel_sign
|
||||
type: b1
|
||||
- id: y_accel_value
|
||||
type: b4
|
||||
- id: y_sign
|
||||
type: b1
|
||||
- id: y_value
|
||||
type: b26
|
||||
instances:
|
||||
y_vel:
|
||||
value: 'y_vel_sign ? (y_vel_value * (-1)) : y_vel_value'
|
||||
y_accel:
|
||||
value: 'y_accel_sign ? (y_accel_value * (-1)) : y_accel_value'
|
||||
y:
|
||||
value: 'y_sign ? (y_value * (-1)) : y_value'
|
||||
string_3:
|
||||
seq:
|
||||
- id: p3
|
||||
type: b1
|
||||
- id: gamma_n_sign
|
||||
type: b1
|
||||
- id: gamma_n_value
|
||||
type: b10
|
||||
- id: not_used
|
||||
type: b1
|
||||
- id: p
|
||||
type: b2
|
||||
- id: l_n
|
||||
type: b1
|
||||
- id: z_vel_sign
|
||||
type: b1
|
||||
- id: z_vel_value
|
||||
type: b23
|
||||
- id: z_accel_sign
|
||||
type: b1
|
||||
- id: z_accel_value
|
||||
type: b4
|
||||
- id: z_sign
|
||||
type: b1
|
||||
- id: z_value
|
||||
type: b26
|
||||
instances:
|
||||
gamma_n:
|
||||
value: 'gamma_n_sign ? (gamma_n_value * (-1)) : gamma_n_value'
|
||||
z_vel:
|
||||
value: 'z_vel_sign ? (z_vel_value * (-1)) : z_vel_value'
|
||||
z_accel:
|
||||
value: 'z_accel_sign ? (z_accel_value * (-1)) : z_accel_value'
|
||||
z:
|
||||
value: 'z_sign ? (z_value * (-1)) : z_value'
|
||||
string_4:
|
||||
seq:
|
||||
- id: tau_n_sign
|
||||
type: b1
|
||||
- id: tau_n_value
|
||||
type: b21
|
||||
- id: delta_tau_n_sign
|
||||
type: b1
|
||||
- id: delta_tau_n_value
|
||||
type: b4
|
||||
- id: e_n
|
||||
type: b5
|
||||
- id: not_used_1
|
||||
type: b14
|
||||
- id: p4
|
||||
type: b1
|
||||
- id: f_t
|
||||
type: b4
|
||||
- id: not_used_2
|
||||
type: b3
|
||||
- id: n_t
|
||||
type: b11
|
||||
- id: n
|
||||
type: b5
|
||||
- id: m
|
||||
type: b2
|
||||
instances:
|
||||
tau_n:
|
||||
value: 'tau_n_sign ? (tau_n_value * (-1)) : tau_n_value'
|
||||
delta_tau_n:
|
||||
value: 'delta_tau_n_sign ? (delta_tau_n_value * (-1)) : delta_tau_n_value'
|
||||
string_5:
|
||||
seq:
|
||||
- id: n_a
|
||||
type: b11
|
||||
- id: tau_c
|
||||
type: b32
|
||||
- id: not_used
|
||||
type: b1
|
||||
- id: n_4
|
||||
type: b5
|
||||
- id: tau_gps
|
||||
type: b22
|
||||
- id: l_n
|
||||
type: b1
|
||||
string_non_immediate:
|
||||
seq:
|
||||
- id: data_1
|
||||
type: b64
|
||||
- id: data_2
|
||||
type: b8
|
||||
189
system/ubloxd/gps.ksy
Normal file
189
system/ubloxd/gps.ksy
Normal file
@@ -0,0 +1,189 @@
|
||||
# https://www.gps.gov/technical/icwg/IS-GPS-200E.pdf
|
||||
meta:
|
||||
id: gps
|
||||
endian: be
|
||||
bit-endian: be
|
||||
seq:
|
||||
- id: tlm
|
||||
type: tlm
|
||||
- id: how
|
||||
type: how
|
||||
- id: body
|
||||
type:
|
||||
switch-on: how.subframe_id
|
||||
cases:
|
||||
1: subframe_1
|
||||
2: subframe_2
|
||||
3: subframe_3
|
||||
4: subframe_4
|
||||
types:
|
||||
tlm:
|
||||
seq:
|
||||
- id: preamble
|
||||
contents: [0x8b]
|
||||
- id: tlm
|
||||
type: b14
|
||||
- id: integrity_status
|
||||
type: b1
|
||||
- id: reserved
|
||||
type: b1
|
||||
how:
|
||||
seq:
|
||||
- id: tow_count
|
||||
type: b17
|
||||
- id: alert
|
||||
type: b1
|
||||
- id: anti_spoof
|
||||
type: b1
|
||||
- id: subframe_id
|
||||
type: b3
|
||||
- id: reserved
|
||||
type: b2
|
||||
subframe_1:
|
||||
seq:
|
||||
# Word 3
|
||||
- id: week_no
|
||||
type: b10
|
||||
- id: code
|
||||
type: b2
|
||||
- id: sv_accuracy
|
||||
type: b4
|
||||
- id: sv_health
|
||||
type: b6
|
||||
- id: iodc_msb
|
||||
type: b2
|
||||
# Word 4
|
||||
- id: l2_p_data_flag
|
||||
type: b1
|
||||
- id: reserved1
|
||||
type: b23
|
||||
# Word 5
|
||||
- id: reserved2
|
||||
type: b24
|
||||
# Word 6
|
||||
- id: reserved3
|
||||
type: b24
|
||||
# Word 7
|
||||
- id: reserved4
|
||||
type: b16
|
||||
- id: t_gd
|
||||
type: s1
|
||||
# Word 8
|
||||
- id: iodc_lsb
|
||||
type: u1
|
||||
- id: t_oc
|
||||
type: u2
|
||||
# Word 9
|
||||
- id: af_2
|
||||
type: s1
|
||||
- id: af_1
|
||||
type: s2
|
||||
# Word 10
|
||||
- id: af_0_sign
|
||||
type: b1
|
||||
- id: af_0_value
|
||||
type: b21
|
||||
- id: reserved5
|
||||
type: b2
|
||||
instances:
|
||||
af_0:
|
||||
value: 'af_0_sign ? (af_0_value - (1 << 21)) : af_0_value'
|
||||
subframe_2:
|
||||
seq:
|
||||
# Word 3
|
||||
- id: iode
|
||||
type: u1
|
||||
- id: c_rs
|
||||
type: s2
|
||||
# Word 4 & 5
|
||||
- id: delta_n
|
||||
type: s2
|
||||
- id: m_0
|
||||
type: s4
|
||||
# Word 6 & 7
|
||||
- id: c_uc
|
||||
type: s2
|
||||
- id: e
|
||||
type: s4
|
||||
# Word 8 & 9
|
||||
- id: c_us
|
||||
type: s2
|
||||
- id: sqrt_a
|
||||
type: u4
|
||||
# Word 10
|
||||
- id: t_oe
|
||||
type: u2
|
||||
- id: fit_interval_flag
|
||||
type: b1
|
||||
- id: aoda
|
||||
type: b5
|
||||
- id: reserved
|
||||
type: b2
|
||||
subframe_3:
|
||||
seq:
|
||||
# Word 3 & 4
|
||||
- id: c_ic
|
||||
type: s2
|
||||
- id: omega_0
|
||||
type: s4
|
||||
# Word 5 & 6
|
||||
- id: c_is
|
||||
type: s2
|
||||
- id: i_0
|
||||
type: s4
|
||||
# Word 7 & 8
|
||||
- id: c_rc
|
||||
type: s2
|
||||
- id: omega
|
||||
type: s4
|
||||
# Word 9
|
||||
- id: omega_dot_sign
|
||||
type: b1
|
||||
- id: omega_dot_value
|
||||
type: b23
|
||||
# Word 10
|
||||
- id: iode
|
||||
type: u1
|
||||
- id: idot_sign
|
||||
type: b1
|
||||
- id: idot_value
|
||||
type: b13
|
||||
- id: reserved
|
||||
type: b2
|
||||
instances:
|
||||
omega_dot:
|
||||
value: 'omega_dot_sign ? (omega_dot_value - (1 << 23)) : omega_dot_value'
|
||||
idot:
|
||||
value: 'idot_sign ? (idot_value - (1 << 13)) : idot_value'
|
||||
subframe_4:
|
||||
seq:
|
||||
# Word 3
|
||||
- id: data_id
|
||||
type: b2
|
||||
- id: page_id
|
||||
type: b6
|
||||
- id: body
|
||||
type:
|
||||
switch-on: page_id
|
||||
cases:
|
||||
56: ionosphere_data
|
||||
types:
|
||||
ionosphere_data:
|
||||
seq:
|
||||
- id: a0
|
||||
type: s1
|
||||
- id: a1
|
||||
type: s1
|
||||
- id: a2
|
||||
type: s1
|
||||
- id: a3
|
||||
type: s1
|
||||
- id: b0
|
||||
type: s1
|
||||
- id: b1
|
||||
type: s1
|
||||
- id: b2
|
||||
type: s1
|
||||
- id: b3
|
||||
type: s1
|
||||
|
||||
309
system/ubloxd/pigeond.py
Executable file
309
system/ubloxd/pigeond.py
Executable file
@@ -0,0 +1,309 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
import time
|
||||
import signal
|
||||
import serial
|
||||
import struct
|
||||
import requests
|
||||
import urllib.parse
|
||||
from datetime import datetime, UTC
|
||||
|
||||
from cereal import messaging
|
||||
from openpilot.common.time_helpers import system_time_valid
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
from openpilot.system.hardware import TICI
|
||||
from openpilot.common.gpio import gpio_init, gpio_set
|
||||
from openpilot.system.hardware.tici.pins import GPIO
|
||||
|
||||
UBLOX_TTY = "/dev/ttyHS0"
|
||||
|
||||
UBLOX_ACK = b"\xb5\x62\x05\x01\x02\x00"
|
||||
UBLOX_NACK = b"\xb5\x62\x05\x00\x02\x00"
|
||||
UBLOX_SOS_ACK = b"\xb5\x62\x09\x14\x08\x00\x02\x00\x00\x00\x01\x00\x00\x00"
|
||||
UBLOX_SOS_NACK = b"\xb5\x62\x09\x14\x08\x00\x02\x00\x00\x00\x00\x00\x00\x00"
|
||||
UBLOX_BACKUP_RESTORE_MSG = b"\xb5\x62\x09\x14\x08\x00\x03"
|
||||
UBLOX_ASSIST_ACK = b"\xb5\x62\x13\x60\x08\x00"
|
||||
|
||||
def set_power(enabled: bool) -> None:
|
||||
gpio_init(GPIO.UBLOX_SAFEBOOT_N, True)
|
||||
gpio_init(GPIO.GNSS_PWR_EN, True)
|
||||
gpio_init(GPIO.UBLOX_RST_N, True)
|
||||
|
||||
gpio_set(GPIO.UBLOX_SAFEBOOT_N, True)
|
||||
gpio_set(GPIO.GNSS_PWR_EN, enabled)
|
||||
gpio_set(GPIO.UBLOX_RST_N, enabled)
|
||||
|
||||
def add_ubx_checksum(msg: bytes) -> bytes:
|
||||
A = B = 0
|
||||
for b in msg[2:]:
|
||||
A = (A + b) % 256
|
||||
B = (B + A) % 256
|
||||
return msg + bytes([A, B])
|
||||
|
||||
def get_assistnow_messages(token: str) -> list[bytes]:
|
||||
# make request
|
||||
# TODO: implement adding the last known location
|
||||
r = requests.get("https://online-live2.services.u-blox.com/GetOnlineData.ashx", params=urllib.parse.urlencode({
|
||||
'token': token,
|
||||
'gnss': 'gps,glo',
|
||||
'datatype': 'eph,alm,aux',
|
||||
}, safe=':,'), timeout=5)
|
||||
assert r.status_code == 200, "Got invalid status code"
|
||||
dat = r.content
|
||||
|
||||
# split up messages
|
||||
msgs = []
|
||||
while len(dat) > 0:
|
||||
assert dat[:2] == b"\xB5\x62"
|
||||
msg_len = 6 + (dat[5] << 8 | dat[4]) + 2
|
||||
msgs.append(dat[:msg_len])
|
||||
dat = dat[msg_len:]
|
||||
return msgs
|
||||
|
||||
|
||||
class TTYPigeon:
|
||||
def __init__(self):
|
||||
self.tty = serial.VTIMESerial(UBLOX_TTY, baudrate=9600, timeout=0)
|
||||
|
||||
def send(self, dat: bytes) -> None:
|
||||
self.tty.write(dat)
|
||||
|
||||
def receive(self) -> bytes:
|
||||
dat = b''
|
||||
while len(dat) < 0x1000:
|
||||
d = self.tty.read(0x40)
|
||||
dat += d
|
||||
if len(d) == 0:
|
||||
break
|
||||
return dat
|
||||
|
||||
def set_baud(self, baud: int) -> None:
|
||||
self.tty.baudrate = baud
|
||||
|
||||
def wait_for_ack(self, ack: bytes = UBLOX_ACK, nack: bytes = UBLOX_NACK, timeout: float = 0.5) -> bool:
|
||||
dat = b''
|
||||
st = time.monotonic()
|
||||
while True:
|
||||
dat += self.receive()
|
||||
if ack in dat:
|
||||
cloudlog.debug("Received ACK from ublox")
|
||||
return True
|
||||
elif nack in dat:
|
||||
cloudlog.error("Received NACK from ublox")
|
||||
return False
|
||||
elif time.monotonic() - st > timeout:
|
||||
cloudlog.error("No response from ublox")
|
||||
raise TimeoutError('No response from ublox')
|
||||
time.sleep(0.001)
|
||||
|
||||
def send_with_ack(self, dat: bytes, ack: bytes = UBLOX_ACK, nack: bytes = UBLOX_NACK) -> None:
|
||||
self.send(dat)
|
||||
self.wait_for_ack(ack, nack)
|
||||
|
||||
def wait_for_backup_restore_status(self, timeout: float = 1.) -> int:
|
||||
dat = b''
|
||||
st = time.monotonic()
|
||||
while True:
|
||||
dat += self.receive()
|
||||
position = dat.find(UBLOX_BACKUP_RESTORE_MSG)
|
||||
if position >= 0 and len(dat) >= position + 11:
|
||||
return dat[position + 10]
|
||||
elif time.monotonic() - st > timeout:
|
||||
cloudlog.error("No backup restore response from ublox")
|
||||
raise TimeoutError('No response from ublox')
|
||||
time.sleep(0.001)
|
||||
|
||||
def reset_device(self) -> bool:
|
||||
# deleting the backup does not always work on first try (mostly on second try)
|
||||
for _ in range(5):
|
||||
# device cold start
|
||||
self.send(b"\xb5\x62\x06\x04\x04\x00\xff\xff\x00\x00\x0c\x5d")
|
||||
time.sleep(1) # wait for cold start
|
||||
init_baudrate(self)
|
||||
|
||||
# clear configuration
|
||||
self.send_with_ack(b"\xb5\x62\x06\x09\x0d\x00\x1f\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x17\x71\xd7")
|
||||
|
||||
# clear flash memory (almanac backup)
|
||||
self.send_with_ack(b"\xB5\x62\x09\x14\x04\x00\x01\x00\x00\x00\x22\xf0")
|
||||
|
||||
# try restoring backup to verify it got deleted
|
||||
self.send(b"\xB5\x62\x09\x14\x00\x00\x1D\x60")
|
||||
# 1: failed to restore, 2: could restore, 3: no backup
|
||||
status = self.wait_for_backup_restore_status()
|
||||
if status == 1 or status == 3:
|
||||
return True
|
||||
return False
|
||||
|
||||
def save_almanac(pigeon: TTYPigeon) -> None:
|
||||
# store almanac in flash
|
||||
pigeon.send(b"\xB5\x62\x09\x14\x04\x00\x00\x00\x00\x00\x21\xEC")
|
||||
try:
|
||||
if pigeon.wait_for_ack(ack=UBLOX_SOS_ACK, nack=UBLOX_SOS_NACK):
|
||||
cloudlog.info("Done storing almanac")
|
||||
else:
|
||||
cloudlog.error("Error storing almanac")
|
||||
except TimeoutError:
|
||||
pass
|
||||
|
||||
def init_baudrate(pigeon: TTYPigeon):
|
||||
# ublox default setting on startup is 9600 baudrate
|
||||
pigeon.set_baud(9600)
|
||||
|
||||
# $PUBX,41,1,0007,0003,460800,0*15\r\n
|
||||
pigeon.send(b"\x24\x50\x55\x42\x58\x2C\x34\x31\x2C\x31\x2C\x30\x30\x30\x37\x2C\x30\x30\x30\x33\x2C\x34\x36\x30\x38\x30\x30\x2C\x30\x2A\x31\x35\x0D\x0A")
|
||||
time.sleep(0.1)
|
||||
pigeon.set_baud(460800)
|
||||
|
||||
|
||||
def init_pigeon(pigeon: TTYPigeon) -> bool:
|
||||
# try initializing a few times
|
||||
for _ in range(10):
|
||||
try:
|
||||
|
||||
# setup port config
|
||||
pigeon.send_with_ack(b"\xb5\x62\x06\x00\x14\x00\x03\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x1E\x7F")
|
||||
pigeon.send_with_ack(b"\xb5\x62\x06\x00\x14\x00\x00\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x19\x35")
|
||||
pigeon.send_with_ack(b"\xb5\x62\x06\x00\x14\x00\x01\x00\x00\x00\xC0\x08\x00\x00\x00\x08\x07\x00\x01\x00\x01\x00\x00\x00\x00\x00\xF4\x80")
|
||||
pigeon.send_with_ack(b"\xb5\x62\x06\x00\x14\x00\x04\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1D\x85")
|
||||
pigeon.send_with_ack(b"\xb5\x62\x06\x00\x00\x00\x06\x18")
|
||||
pigeon.send_with_ack(b"\xb5\x62\x06\x00\x01\x00\x01\x08\x22")
|
||||
pigeon.send_with_ack(b"\xb5\x62\x06\x00\x01\x00\x03\x0A\x24")
|
||||
|
||||
# UBX-CFG-RATE (0x06 0x08)
|
||||
pigeon.send_with_ack(b"\xB5\x62\x06\x08\x06\x00\x64\x00\x01\x00\x00\x00\x79\x10")
|
||||
|
||||
# UBX-CFG-NAV5 (0x06 0x24)
|
||||
pigeon.send_with_ack(b"\xB5\x62\x06\x24\x24\x00\x05\x00\x04\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x5A\x63")
|
||||
|
||||
# UBX-CFG-ODO (0x06 0x1E)
|
||||
pigeon.send_with_ack(b"\xB5\x62\x06\x1E\x14\x00\x00\x00\x00\x00\x01\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x3C\x37")
|
||||
pigeon.send_with_ack(b"\xB5\x62\x06\x39\x08\x00\xFF\xAD\x62\xAD\x1E\x63\x00\x00\x83\x0C")
|
||||
pigeon.send_with_ack(b"\xB5\x62\x06\x23\x28\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x56\x24")
|
||||
|
||||
# UBX-CFG-NAV5 (0x06 0x24)
|
||||
pigeon.send_with_ack(b"\xB5\x62\x06\x24\x00\x00\x2A\x84")
|
||||
pigeon.send_with_ack(b"\xB5\x62\x06\x23\x00\x00\x29\x81")
|
||||
pigeon.send_with_ack(b"\xB5\x62\x06\x1E\x00\x00\x24\x72")
|
||||
pigeon.send_with_ack(b"\xB5\x62\x06\x39\x00\x00\x3F\xC3")
|
||||
|
||||
# UBX-CFG-MSG (set message rate)
|
||||
pigeon.send_with_ack(b"\xB5\x62\x06\x01\x03\x00\x01\x07\x01\x13\x51")
|
||||
pigeon.send_with_ack(b"\xB5\x62\x06\x01\x03\x00\x02\x15\x01\x22\x70")
|
||||
pigeon.send_with_ack(b"\xB5\x62\x06\x01\x03\x00\x02\x13\x01\x20\x6C")
|
||||
pigeon.send_with_ack(b"\xB5\x62\x06\x01\x03\x00\x0A\x09\x01\x1E\x70")
|
||||
pigeon.send_with_ack(b"\xB5\x62\x06\x01\x03\x00\x0A\x0B\x01\x20\x74")
|
||||
pigeon.send_with_ack(b"\xB5\x62\x06\x01\x03\x00\x01\x35\x01\x41\xAD")
|
||||
cloudlog.debug("pigeon configured")
|
||||
|
||||
# try restoring almanac backup
|
||||
pigeon.send(b"\xB5\x62\x09\x14\x00\x00\x1D\x60")
|
||||
restore_status = pigeon.wait_for_backup_restore_status()
|
||||
if restore_status == 2:
|
||||
cloudlog.warning("almanac backup restored")
|
||||
elif restore_status == 3:
|
||||
cloudlog.warning("no almanac backup found")
|
||||
else:
|
||||
cloudlog.error(f"failed to restore almanac backup, status: {restore_status}")
|
||||
|
||||
# sending time to ublox
|
||||
if system_time_valid():
|
||||
t_now = datetime.now(UTC).replace(tzinfo=None)
|
||||
cloudlog.warning("Sending current time to ublox")
|
||||
|
||||
# UBX-MGA-INI-TIME_UTC
|
||||
msg = add_ubx_checksum(b"\xB5\x62\x13\x40\x18\x00" + struct.pack("<BBBBHBBBBBxIHxxI",
|
||||
0x10,
|
||||
0x00,
|
||||
0x00,
|
||||
0x80,
|
||||
t_now.year,
|
||||
t_now.month,
|
||||
t_now.day,
|
||||
t_now.hour,
|
||||
t_now.minute,
|
||||
t_now.second,
|
||||
0,
|
||||
30,
|
||||
0
|
||||
))
|
||||
pigeon.send_with_ack(msg, ack=UBLOX_ASSIST_ACK)
|
||||
|
||||
# try getting AssistNow if we have a token
|
||||
token = Params().get('AssistNowToken')
|
||||
if token is not None:
|
||||
try:
|
||||
for msg in get_assistnow_messages(token):
|
||||
pigeon.send_with_ack(msg, ack=UBLOX_ASSIST_ACK)
|
||||
cloudlog.warning("AssistNow messages sent")
|
||||
except Exception:
|
||||
cloudlog.warning("failed to get AssistNow messages")
|
||||
|
||||
cloudlog.warning("Pigeon GPS on!")
|
||||
break
|
||||
except TimeoutError:
|
||||
cloudlog.warning("Initialization failed, trying again!")
|
||||
else:
|
||||
cloudlog.warning("Failed to initialize pigeon")
|
||||
return False
|
||||
return True
|
||||
|
||||
def deinitialize_and_exit(pigeon: TTYPigeon | None):
|
||||
if pigeon is not None:
|
||||
# controlled GNSS stop
|
||||
pigeon.send(b"\xB5\x62\x06\x04\x04\x00\x00\x00\x08\x00\x16\x74")
|
||||
|
||||
# turn off power and exit cleanly
|
||||
set_power(False)
|
||||
sys.exit(0)
|
||||
|
||||
def init(pigeon: TTYPigeon) -> None:
|
||||
# register exit handler
|
||||
signal.signal(signal.SIGINT, lambda sig, frame: deinitialize_and_exit(pigeon))
|
||||
|
||||
# power cycle ublox
|
||||
set_power(False)
|
||||
time.sleep(0.1)
|
||||
set_power(True)
|
||||
time.sleep(0.5)
|
||||
|
||||
init_baudrate(pigeon)
|
||||
init_pigeon(pigeon)
|
||||
|
||||
def run_receiving(duration: int = 0):
|
||||
pm = messaging.PubMaster(['ubloxRaw'])
|
||||
|
||||
pigeon = TTYPigeon()
|
||||
init(pigeon)
|
||||
|
||||
start_time = time.monotonic()
|
||||
last_almanac_save = time.monotonic()
|
||||
while (duration == 0) or (time.monotonic() - start_time < duration):
|
||||
dat = pigeon.receive()
|
||||
if len(dat) > 0:
|
||||
if dat[0] == 0x00:
|
||||
cloudlog.warning("received invalid data from ublox, re-initing!")
|
||||
init(pigeon)
|
||||
continue
|
||||
|
||||
# send out to socket
|
||||
msg = messaging.new_message('ubloxRaw', len(dat), valid=True)
|
||||
msg.ubloxRaw = dat[:]
|
||||
pm.send('ubloxRaw', msg)
|
||||
|
||||
# save almanac every 5 minutes
|
||||
if (time.monotonic() - last_almanac_save) > 60*5:
|
||||
save_almanac(pigeon)
|
||||
last_almanac_save = time.monotonic()
|
||||
else:
|
||||
# prevent locking up a CPU core if ublox disconnects
|
||||
time.sleep(0.001)
|
||||
|
||||
|
||||
def main():
|
||||
assert TICI, "unsupported hardware for pigeond"
|
||||
run_receiving()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
54
system/ubloxd/tests/test_pigeond.py
Normal file
54
system/ubloxd/tests/test_pigeond.py
Normal file
@@ -0,0 +1,54 @@
|
||||
import pytest
|
||||
import time
|
||||
|
||||
import cereal.messaging as messaging
|
||||
from cereal.services import SERVICE_LIST
|
||||
from openpilot.common.gpio import gpio_read
|
||||
from openpilot.selfdrive.test.helpers import with_processes
|
||||
from openpilot.system.manager.process_config import managed_processes
|
||||
from openpilot.system.hardware.tici.pins import GPIO
|
||||
|
||||
|
||||
# TODO: test TTFF when we have good A-GNSS
|
||||
@pytest.mark.tici
|
||||
class TestPigeond:
|
||||
|
||||
def teardown_method(self):
|
||||
managed_processes['pigeond'].stop()
|
||||
|
||||
@with_processes(['pigeond'])
|
||||
def test_frequency(self):
|
||||
sm = messaging.SubMaster(['ubloxRaw'])
|
||||
|
||||
# setup time
|
||||
for _ in range(int(5 * SERVICE_LIST['ubloxRaw'].frequency)):
|
||||
sm.update()
|
||||
|
||||
for _ in range(int(10 * SERVICE_LIST['ubloxRaw'].frequency)):
|
||||
sm.update()
|
||||
assert sm.all_checks()
|
||||
|
||||
def test_startup_time(self):
|
||||
for _ in range(5):
|
||||
sm = messaging.SubMaster(['ubloxRaw'])
|
||||
managed_processes['pigeond'].start()
|
||||
|
||||
start_time = time.monotonic()
|
||||
for __ in range(10):
|
||||
sm.update(1 * 1000)
|
||||
if sm.updated['ubloxRaw']:
|
||||
break
|
||||
assert sm.recv_frame['ubloxRaw'] > 0, "pigeond didn't start outputting messages in time"
|
||||
|
||||
et = time.monotonic() - start_time
|
||||
assert et < 5, f"pigeond took {et:.1f}s to start"
|
||||
managed_processes['pigeond'].stop()
|
||||
|
||||
def test_turns_off_ublox(self):
|
||||
for s in (0.1, 0.5, 1, 5):
|
||||
managed_processes['pigeond'].start()
|
||||
time.sleep(s)
|
||||
managed_processes['pigeond'].stop()
|
||||
|
||||
assert gpio_read(GPIO.UBLOX_RST_N) == 0
|
||||
assert gpio_read(GPIO.GNSS_PWR_EN) == 0
|
||||
519
system/ubloxd/ubloxd.py
Normal file
519
system/ubloxd/ubloxd.py
Normal file
@@ -0,0 +1,519 @@
|
||||
#!/usr/bin/env python3
|
||||
import math
|
||||
import capnp
|
||||
import calendar
|
||||
import numpy as np
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
|
||||
from cereal import log
|
||||
from cereal import messaging
|
||||
from openpilot.system.ubloxd.generated.ubx import Ubx
|
||||
from openpilot.system.ubloxd.generated.gps import Gps
|
||||
from openpilot.system.ubloxd.generated.glonass import Glonass
|
||||
|
||||
|
||||
SECS_IN_MIN = 60
|
||||
SECS_IN_HR = 60 * SECS_IN_MIN
|
||||
SECS_IN_DAY = 24 * SECS_IN_HR
|
||||
SECS_IN_WEEK = 7 * SECS_IN_DAY
|
||||
|
||||
|
||||
class UbxFramer:
|
||||
PREAMBLE1 = 0xB5
|
||||
PREAMBLE2 = 0x62
|
||||
HEADER_SIZE = 6
|
||||
CHECKSUM_SIZE = 2
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.buf = bytearray()
|
||||
self.last_log_time = 0.0
|
||||
|
||||
def reset(self) -> None:
|
||||
self.buf.clear()
|
||||
|
||||
@staticmethod
|
||||
def _checksum_ok(frame: bytes) -> bool:
|
||||
ck_a = 0
|
||||
ck_b = 0
|
||||
for b in frame[2:-2]:
|
||||
ck_a = (ck_a + b) & 0xFF
|
||||
ck_b = (ck_b + ck_a) & 0xFF
|
||||
return ck_a == frame[-2] and ck_b == frame[-1]
|
||||
|
||||
def add_data(self, log_time: float, incoming: bytes) -> list[bytes]:
|
||||
self.last_log_time = log_time
|
||||
out: list[bytes] = []
|
||||
if not incoming:
|
||||
return out
|
||||
self.buf += incoming
|
||||
|
||||
while True:
|
||||
# find preamble
|
||||
if len(self.buf) < 2:
|
||||
break
|
||||
start = self.buf.find(b"\xB5\x62")
|
||||
if start < 0:
|
||||
# no preamble in buffer
|
||||
self.buf.clear()
|
||||
break
|
||||
if start > 0:
|
||||
# drop garbage before preamble
|
||||
self.buf = self.buf[start:]
|
||||
|
||||
if len(self.buf) < self.HEADER_SIZE:
|
||||
break
|
||||
|
||||
length_le = int.from_bytes(self.buf[4:6], 'little', signed=False)
|
||||
total_len = self.HEADER_SIZE + length_le + self.CHECKSUM_SIZE
|
||||
if len(self.buf) < total_len:
|
||||
break
|
||||
|
||||
candidate = bytes(self.buf[:total_len])
|
||||
if self._checksum_ok(candidate):
|
||||
out.append(candidate)
|
||||
# consume this frame
|
||||
self.buf = self.buf[total_len:]
|
||||
else:
|
||||
# drop first byte and retry
|
||||
self.buf = self.buf[1:]
|
||||
|
||||
return out
|
||||
|
||||
|
||||
def _bit(b: int, shift: int) -> bool:
|
||||
return (b & (1 << shift)) != 0
|
||||
|
||||
|
||||
@dataclass
|
||||
class EphemerisCaches:
|
||||
gps_subframes: defaultdict[int, dict[int, bytes]]
|
||||
glonass_strings: defaultdict[int, dict[int, bytes]]
|
||||
glonass_string_times: defaultdict[int, dict[int, float]]
|
||||
glonass_string_superframes: defaultdict[int, dict[int, int]]
|
||||
|
||||
|
||||
class UbloxMsgParser:
|
||||
gpsPi = 3.1415926535898
|
||||
|
||||
# user range accuracy in meters
|
||||
glonass_URA_lookup: dict[int, float] = {
|
||||
0: 1, 1: 2, 2: 2.5, 3: 4, 4: 5, 5: 7,
|
||||
6: 10, 7: 12, 8: 14, 9: 16, 10: 32,
|
||||
11: 64, 12: 128, 13: 256, 14: 512, 15: 1024,
|
||||
}
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.framer = UbxFramer()
|
||||
self.caches = EphemerisCaches(
|
||||
gps_subframes=defaultdict(dict),
|
||||
glonass_strings=defaultdict(dict),
|
||||
glonass_string_times=defaultdict(dict),
|
||||
glonass_string_superframes=defaultdict(dict),
|
||||
)
|
||||
|
||||
# Message generation entry point
|
||||
def parse_frame(self, frame: bytes) -> tuple[str, capnp.lib.capnp._DynamicStructBuilder] | None:
|
||||
# Quick header parse
|
||||
msg_type = int.from_bytes(frame[2:4], 'big')
|
||||
payload = frame[6:-2]
|
||||
if msg_type == 0x0107:
|
||||
body = Ubx.NavPvt.from_bytes(payload)
|
||||
return self._gen_nav_pvt(body)
|
||||
if msg_type == 0x0213:
|
||||
# Manually parse RXM-SFRBX to avoid Kaitai EOF on some frames
|
||||
if len(payload) < 8:
|
||||
return None
|
||||
gnss_id = payload[0]
|
||||
sv_id = payload[1]
|
||||
freq_id = payload[3]
|
||||
num_words = payload[4]
|
||||
exp = 8 + 4 * num_words
|
||||
if exp != len(payload):
|
||||
return None
|
||||
words: list[int] = []
|
||||
off = 8
|
||||
for _ in range(num_words):
|
||||
words.append(int.from_bytes(payload[off:off+4], 'little'))
|
||||
off += 4
|
||||
|
||||
class _SfrbxView:
|
||||
def __init__(self, gid: int, sid: int, fid: int, body: list[int]):
|
||||
self.gnss_id = Ubx.GnssType(gid)
|
||||
self.sv_id = sid
|
||||
self.freq_id = fid
|
||||
self.body = body
|
||||
view = _SfrbxView(gnss_id, sv_id, freq_id, words)
|
||||
return self._gen_rxm_sfrbx(view)
|
||||
if msg_type == 0x0215:
|
||||
body = Ubx.RxmRawx.from_bytes(payload)
|
||||
return self._gen_rxm_rawx(body)
|
||||
if msg_type == 0x0A09:
|
||||
body = Ubx.MonHw.from_bytes(payload)
|
||||
return self._gen_mon_hw(body)
|
||||
if msg_type == 0x0A0B:
|
||||
body = Ubx.MonHw2.from_bytes(payload)
|
||||
return self._gen_mon_hw2(body)
|
||||
if msg_type == 0x0135:
|
||||
body = Ubx.NavSat.from_bytes(payload)
|
||||
return self._gen_nav_sat(body)
|
||||
return None
|
||||
|
||||
# NAV-PVT -> gpsLocationExternal
|
||||
def _gen_nav_pvt(self, msg: Ubx.NavPvt) -> tuple[str, capnp.lib.capnp._DynamicStructBuilder]:
|
||||
dat = messaging.new_message('gpsLocationExternal', valid=True)
|
||||
gps = dat.gpsLocationExternal
|
||||
gps.source = log.GpsLocationData.SensorSource.ublox
|
||||
gps.flags = msg.flags
|
||||
gps.hasFix = (msg.flags % 2) == 1
|
||||
gps.latitude = msg.lat * 1e-07
|
||||
gps.longitude = msg.lon * 1e-07
|
||||
gps.altitude = msg.height * 1e-03
|
||||
gps.speed = msg.g_speed * 1e-03
|
||||
gps.bearingDeg = msg.head_mot * 1e-5
|
||||
gps.horizontalAccuracy = msg.h_acc * 1e-03
|
||||
gps.satelliteCount = msg.num_sv
|
||||
|
||||
# build UTC timestamp millis (NAV-PVT is in UTC)
|
||||
# tolerate invalid or unset date values like C++ timegm
|
||||
try:
|
||||
utc_tt = calendar.timegm((msg.year, msg.month, msg.day, msg.hour, msg.min, msg.sec, 0, 0, 0))
|
||||
except Exception:
|
||||
utc_tt = 0
|
||||
gps.unixTimestampMillis = int(utc_tt * 1e3 + (msg.nano * 1e-6))
|
||||
|
||||
# match C++ float32 rounding semantics exactly
|
||||
gps.vNED = [
|
||||
float(np.float32(msg.vel_n) * np.float32(1e-03)),
|
||||
float(np.float32(msg.vel_e) * np.float32(1e-03)),
|
||||
float(np.float32(msg.vel_d) * np.float32(1e-03)),
|
||||
]
|
||||
gps.verticalAccuracy = msg.v_acc * 1e-03
|
||||
gps.speedAccuracy = msg.s_acc * 1e-03
|
||||
gps.bearingAccuracyDeg = msg.head_acc * 1e-05
|
||||
return ('gpsLocationExternal', dat)
|
||||
|
||||
# RXM-SFRBX dispatch to GPS or GLONASS ephemeris
|
||||
def _gen_rxm_sfrbx(self, msg) -> tuple[str, capnp.lib.capnp._DynamicStructBuilder] | None:
|
||||
if msg.gnss_id == Ubx.GnssType.gps:
|
||||
return self._parse_gps_ephemeris(msg)
|
||||
if msg.gnss_id == Ubx.GnssType.glonass:
|
||||
return self._parse_glonass_ephemeris(msg)
|
||||
return None
|
||||
|
||||
def _parse_gps_ephemeris(self, msg: Ubx.RxmSfrbx) -> tuple[str, capnp.lib.capnp._DynamicStructBuilder] | None:
|
||||
# body is list of 10 words; convert to 30-byte subframe (strip parity/padding)
|
||||
body = msg.body
|
||||
if len(body) != 10:
|
||||
return None
|
||||
subframe_data = bytearray()
|
||||
for word in body:
|
||||
word >>= 6
|
||||
subframe_data.append((word >> 16) & 0xFF)
|
||||
subframe_data.append((word >> 8) & 0xFF)
|
||||
subframe_data.append(word & 0xFF)
|
||||
|
||||
sf = Gps.from_bytes(bytes(subframe_data))
|
||||
subframe_id = sf.how.subframe_id
|
||||
if subframe_id < 1 or subframe_id > 3:
|
||||
return None
|
||||
self.caches.gps_subframes[msg.sv_id][subframe_id] = bytes(subframe_data)
|
||||
|
||||
if len(self.caches.gps_subframes[msg.sv_id]) != 3:
|
||||
return None
|
||||
|
||||
dat = messaging.new_message('ubloxGnss', valid=True)
|
||||
eph = dat.ubloxGnss.init('ephemeris')
|
||||
eph.svId = msg.sv_id
|
||||
|
||||
iode_s2 = 0
|
||||
iode_s3 = 0
|
||||
iodc_lsb = 0
|
||||
week = 0
|
||||
|
||||
# Subframe 1
|
||||
sf1 = Gps.from_bytes(self.caches.gps_subframes[msg.sv_id][1])
|
||||
s1 = sf1.body
|
||||
assert isinstance(s1, Gps.Subframe1)
|
||||
week = s1.week_no
|
||||
week += 1024
|
||||
if week < 1877:
|
||||
week += 1024
|
||||
eph.tgd = s1.t_gd * math.pow(2, -31)
|
||||
eph.toc = s1.t_oc * math.pow(2, 4)
|
||||
eph.af2 = s1.af_2 * math.pow(2, -55)
|
||||
eph.af1 = s1.af_1 * math.pow(2, -43)
|
||||
eph.af0 = s1.af_0 * math.pow(2, -31)
|
||||
eph.svHealth = s1.sv_health
|
||||
eph.towCount = sf1.how.tow_count
|
||||
iodc_lsb = s1.iodc_lsb
|
||||
|
||||
# Subframe 2
|
||||
sf2 = Gps.from_bytes(self.caches.gps_subframes[msg.sv_id][2])
|
||||
s2 = sf2.body
|
||||
assert isinstance(s2, Gps.Subframe2)
|
||||
if s2.t_oe == 0 and sf2.how.tow_count * 6 >= (SECS_IN_WEEK - 2 * SECS_IN_HR):
|
||||
week += 1
|
||||
eph.crs = s2.c_rs * math.pow(2, -5)
|
||||
eph.deltaN = s2.delta_n * math.pow(2, -43) * self.gpsPi
|
||||
eph.m0 = s2.m_0 * math.pow(2, -31) * self.gpsPi
|
||||
eph.cuc = s2.c_uc * math.pow(2, -29)
|
||||
eph.ecc = s2.e * math.pow(2, -33)
|
||||
eph.cus = s2.c_us * math.pow(2, -29)
|
||||
eph.a = math.pow(s2.sqrt_a * math.pow(2, -19), 2.0)
|
||||
eph.toe = s2.t_oe * math.pow(2, 4)
|
||||
iode_s2 = s2.iode
|
||||
|
||||
# Subframe 3
|
||||
sf3 = Gps.from_bytes(self.caches.gps_subframes[msg.sv_id][3])
|
||||
s3 = sf3.body
|
||||
assert isinstance(s3, Gps.Subframe3)
|
||||
eph.cic = s3.c_ic * math.pow(2, -29)
|
||||
eph.omega0 = s3.omega_0 * math.pow(2, -31) * self.gpsPi
|
||||
eph.cis = s3.c_is * math.pow(2, -29)
|
||||
eph.i0 = s3.i_0 * math.pow(2, -31) * self.gpsPi
|
||||
eph.crc = s3.c_rc * math.pow(2, -5)
|
||||
eph.omega = s3.omega * math.pow(2, -31) * self.gpsPi
|
||||
eph.omegaDot = s3.omega_dot * math.pow(2, -43) * self.gpsPi
|
||||
eph.iode = s3.iode
|
||||
eph.iDot = s3.idot * math.pow(2, -43) * self.gpsPi
|
||||
iode_s3 = s3.iode
|
||||
|
||||
eph.toeWeek = week
|
||||
eph.tocWeek = week
|
||||
|
||||
# clear cache for this SV
|
||||
self.caches.gps_subframes[msg.sv_id].clear()
|
||||
if not (iodc_lsb == iode_s2 == iode_s3):
|
||||
return None
|
||||
return ('ubloxGnss', dat)
|
||||
|
||||
def _parse_glonass_ephemeris(self, msg: Ubx.RxmSfrbx) -> tuple[str, capnp.lib.capnp._DynamicStructBuilder] | None:
|
||||
# words are 4 bytes each; Glonass parser expects 16 bytes (string)
|
||||
body = msg.body
|
||||
if len(body) != 4:
|
||||
return None
|
||||
string_bytes = bytearray()
|
||||
for word in body:
|
||||
for i in (3, 2, 1, 0):
|
||||
string_bytes.append((word >> (8 * i)) & 0xFF)
|
||||
|
||||
gl = Glonass.from_bytes(bytes(string_bytes))
|
||||
string_number = gl.string_number
|
||||
if string_number < 1 or string_number > 5 or gl.idle_chip:
|
||||
return None
|
||||
|
||||
# correlate by superframe and timing, similar to C++ logic
|
||||
freq_id = msg.freq_id
|
||||
superframe_unknown = False
|
||||
needs_clear = False
|
||||
for i in range(1, 6):
|
||||
if i not in self.caches.glonass_strings[freq_id]:
|
||||
continue
|
||||
sf_prev = self.caches.glonass_string_superframes[freq_id].get(i, 0)
|
||||
if sf_prev == 0 or gl.superframe_number == 0:
|
||||
superframe_unknown = True
|
||||
elif sf_prev != gl.superframe_number:
|
||||
needs_clear = True
|
||||
if superframe_unknown:
|
||||
prev_time = self.caches.glonass_string_times[freq_id].get(i, 0.0)
|
||||
if abs((prev_time - 2.0 * i) - (self.framer.last_log_time - 2.0 * string_number)) > 10:
|
||||
needs_clear = True
|
||||
|
||||
if needs_clear:
|
||||
self.caches.glonass_strings[freq_id].clear()
|
||||
self.caches.glonass_string_superframes[freq_id].clear()
|
||||
self.caches.glonass_string_times[freq_id].clear()
|
||||
|
||||
self.caches.glonass_strings[freq_id][string_number] = bytes(string_bytes)
|
||||
self.caches.glonass_string_superframes[freq_id][string_number] = gl.superframe_number
|
||||
self.caches.glonass_string_times[freq_id][string_number] = self.framer.last_log_time
|
||||
|
||||
if msg.sv_id == 255:
|
||||
# unknown SV id
|
||||
return None
|
||||
if len(self.caches.glonass_strings[freq_id]) != 5:
|
||||
return None
|
||||
|
||||
dat = messaging.new_message('ubloxGnss', valid=True)
|
||||
eph = dat.ubloxGnss.init('glonassEphemeris')
|
||||
eph.svId = msg.sv_id
|
||||
eph.freqNum = msg.freq_id - 7
|
||||
|
||||
current_day = 0
|
||||
tk = 0
|
||||
|
||||
# string 1
|
||||
try:
|
||||
s1 = Glonass.from_bytes(self.caches.glonass_strings[freq_id][1]).data
|
||||
except Exception:
|
||||
return None
|
||||
assert isinstance(s1, Glonass.String1)
|
||||
eph.p1 = int(s1.p1)
|
||||
tk = int(s1.t_k)
|
||||
eph.tkDEPRECATED = tk
|
||||
eph.xVel = float(s1.x_vel) * math.pow(2, -20)
|
||||
eph.xAccel = float(s1.x_accel) * math.pow(2, -30)
|
||||
eph.x = float(s1.x) * math.pow(2, -11)
|
||||
|
||||
# string 2
|
||||
try:
|
||||
s2 = Glonass.from_bytes(self.caches.glonass_strings[freq_id][2]).data
|
||||
except Exception:
|
||||
return None
|
||||
assert isinstance(s2, Glonass.String2)
|
||||
eph.svHealth = int(s2.b_n >> 2)
|
||||
eph.p2 = int(s2.p2)
|
||||
eph.tb = int(s2.t_b)
|
||||
eph.yVel = float(s2.y_vel) * math.pow(2, -20)
|
||||
eph.yAccel = float(s2.y_accel) * math.pow(2, -30)
|
||||
eph.y = float(s2.y) * math.pow(2, -11)
|
||||
|
||||
# string 3
|
||||
try:
|
||||
s3 = Glonass.from_bytes(self.caches.glonass_strings[freq_id][3]).data
|
||||
except Exception:
|
||||
return None
|
||||
assert isinstance(s3, Glonass.String3)
|
||||
eph.p3 = int(s3.p3)
|
||||
eph.gammaN = float(s3.gamma_n) * math.pow(2, -40)
|
||||
eph.svHealth = int(eph.svHealth | (1 if s3.l_n else 0))
|
||||
eph.zVel = float(s3.z_vel) * math.pow(2, -20)
|
||||
eph.zAccel = float(s3.z_accel) * math.pow(2, -30)
|
||||
eph.z = float(s3.z) * math.pow(2, -11)
|
||||
|
||||
# string 4
|
||||
try:
|
||||
s4 = Glonass.from_bytes(self.caches.glonass_strings[freq_id][4]).data
|
||||
except Exception:
|
||||
return None
|
||||
assert isinstance(s4, Glonass.String4)
|
||||
current_day = int(s4.n_t)
|
||||
eph.nt = current_day
|
||||
eph.tauN = float(s4.tau_n) * math.pow(2, -30)
|
||||
eph.deltaTauN = float(s4.delta_tau_n) * math.pow(2, -30)
|
||||
eph.age = int(s4.e_n)
|
||||
eph.p4 = int(s4.p4)
|
||||
eph.svURA = float(self.glonass_URA_lookup.get(int(s4.f_t), 0.0))
|
||||
# consistency check: SV slot number
|
||||
# if it doesn't match, keep going but note mismatch (no logging here)
|
||||
eph.svType = int(s4.m)
|
||||
|
||||
# string 5
|
||||
try:
|
||||
s5 = Glonass.from_bytes(self.caches.glonass_strings[freq_id][5]).data
|
||||
except Exception:
|
||||
return None
|
||||
assert isinstance(s5, Glonass.String5)
|
||||
eph.n4 = int(s5.n_4)
|
||||
tk_seconds = int(SECS_IN_HR * ((tk >> 7) & 0x1F) + SECS_IN_MIN * ((tk >> 1) & 0x3F) + (tk & 0x1) * 30)
|
||||
eph.tkSeconds = tk_seconds
|
||||
|
||||
self.caches.glonass_strings[freq_id].clear()
|
||||
return ('ubloxGnss', dat)
|
||||
|
||||
def _gen_rxm_rawx(self, msg: Ubx.RxmRawx) -> tuple[str, capnp.lib.capnp._DynamicStructBuilder]:
|
||||
dat = messaging.new_message('ubloxGnss', valid=True)
|
||||
mr = dat.ubloxGnss.init('measurementReport')
|
||||
mr.rcvTow = msg.rcv_tow
|
||||
mr.gpsWeek = msg.week
|
||||
mr.leapSeconds = msg.leap_s
|
||||
|
||||
mb = mr.init('measurements', msg.num_meas)
|
||||
for i, m in enumerate(msg.meas):
|
||||
mb[i].svId = m.sv_id
|
||||
mb[i].pseudorange = m.pr_mes
|
||||
mb[i].carrierCycles = m.cp_mes
|
||||
mb[i].doppler = m.do_mes
|
||||
mb[i].gnssId = int(m.gnss_id.value)
|
||||
mb[i].glonassFrequencyIndex = m.freq_id
|
||||
mb[i].locktime = m.lock_time
|
||||
mb[i].cno = m.cno
|
||||
mb[i].pseudorangeStdev = 0.01 * (math.pow(2, (m.pr_stdev & 15)))
|
||||
mb[i].carrierPhaseStdev = 0.004 * (m.cp_stdev & 15)
|
||||
mb[i].dopplerStdev = 0.002 * (math.pow(2, (m.do_stdev & 15)))
|
||||
|
||||
ts = mb[i].init('trackingStatus')
|
||||
trk = m.trk_stat
|
||||
ts.pseudorangeValid = _bit(trk, 0)
|
||||
ts.carrierPhaseValid = _bit(trk, 1)
|
||||
ts.halfCycleValid = _bit(trk, 2)
|
||||
ts.halfCycleSubtracted = _bit(trk, 3)
|
||||
|
||||
mr.numMeas = msg.num_meas
|
||||
rs = mr.init('receiverStatus')
|
||||
rs.leapSecValid = _bit(msg.rec_stat, 0)
|
||||
rs.clkReset = _bit(msg.rec_stat, 2)
|
||||
return ('ubloxGnss', dat)
|
||||
|
||||
def _gen_nav_sat(self, msg: Ubx.NavSat) -> tuple[str, capnp.lib.capnp._DynamicStructBuilder]:
|
||||
dat = messaging.new_message('ubloxGnss', valid=True)
|
||||
sr = dat.ubloxGnss.init('satReport')
|
||||
sr.iTow = msg.itow
|
||||
svs = sr.init('svs', msg.num_svs)
|
||||
for i, s in enumerate(msg.svs):
|
||||
svs[i].svId = s.sv_id
|
||||
svs[i].gnssId = int(s.gnss_id.value)
|
||||
svs[i].flagsBitfield = s.flags
|
||||
svs[i].cno = s.cno
|
||||
svs[i].elevationDeg = s.elev
|
||||
svs[i].azimuthDeg = s.azim
|
||||
svs[i].pseudorangeResidual = s.pr_res * 0.1
|
||||
return ('ubloxGnss', dat)
|
||||
|
||||
def _gen_mon_hw(self, msg: Ubx.MonHw) -> tuple[str, capnp.lib.capnp._DynamicStructBuilder]:
|
||||
dat = messaging.new_message('ubloxGnss', valid=True)
|
||||
hw = dat.ubloxGnss.init('hwStatus')
|
||||
hw.noisePerMS = msg.noise_per_ms
|
||||
hw.flags = msg.flags
|
||||
hw.agcCnt = msg.agc_cnt
|
||||
hw.aStatus = int(msg.a_status.value)
|
||||
hw.aPower = int(msg.a_power.value)
|
||||
hw.jamInd = msg.jam_ind
|
||||
return ('ubloxGnss', dat)
|
||||
|
||||
def _gen_mon_hw2(self, msg: Ubx.MonHw2) -> tuple[str, capnp.lib.capnp._DynamicStructBuilder]:
|
||||
dat = messaging.new_message('ubloxGnss', valid=True)
|
||||
hw = dat.ubloxGnss.init('hwStatus2')
|
||||
hw.ofsI = msg.ofs_i
|
||||
hw.magI = msg.mag_i
|
||||
hw.ofsQ = msg.ofs_q
|
||||
hw.magQ = msg.mag_q
|
||||
# Map Ubx enum to cereal enum {undefined=0, rom=1, otp=2, configpins=3, flash=4}
|
||||
cfg_map = {
|
||||
Ubx.MonHw2.ConfigSource.rom: 1,
|
||||
Ubx.MonHw2.ConfigSource.otp: 2,
|
||||
Ubx.MonHw2.ConfigSource.config_pins: 3,
|
||||
Ubx.MonHw2.ConfigSource.flash: 4,
|
||||
}
|
||||
hw.cfgSource = cfg_map.get(msg.cfg_source, 0)
|
||||
hw.lowLevCfg = msg.low_lev_cfg
|
||||
hw.postStatus = msg.post_status
|
||||
return ('ubloxGnss', dat)
|
||||
|
||||
|
||||
def main():
|
||||
parser = UbloxMsgParser()
|
||||
pm = messaging.PubMaster(['ubloxGnss', 'gpsLocationExternal'])
|
||||
sock = messaging.sub_sock('ubloxRaw', timeout=100, conflate=False)
|
||||
|
||||
while True:
|
||||
msg = messaging.recv_one(sock)
|
||||
if msg is None:
|
||||
continue
|
||||
|
||||
data = bytes(msg.ubloxRaw)
|
||||
log_time = msg.logMonoTime * 1e-9
|
||||
frames = parser.framer.add_data(log_time, data)
|
||||
for frame in frames:
|
||||
try:
|
||||
res = parser.parse_frame(frame)
|
||||
except Exception:
|
||||
continue
|
||||
if not res:
|
||||
continue
|
||||
service, dat = res
|
||||
pm.send(service, dat)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
293
system/ubloxd/ubx.ksy
Normal file
293
system/ubloxd/ubx.ksy
Normal file
@@ -0,0 +1,293 @@
|
||||
meta:
|
||||
id: ubx
|
||||
endian: le
|
||||
seq:
|
||||
- id: magic
|
||||
contents: [0xb5, 0x62]
|
||||
- id: msg_type
|
||||
type: u2be
|
||||
- id: length
|
||||
type: u2
|
||||
- id: body
|
||||
type:
|
||||
switch-on: msg_type
|
||||
cases:
|
||||
0x0107: nav_pvt
|
||||
0x0213: rxm_sfrbx
|
||||
0x0215: rxm_rawx
|
||||
0x0a09: mon_hw
|
||||
0x0a0b: mon_hw2
|
||||
0x0135: nav_sat
|
||||
instances:
|
||||
checksum:
|
||||
pos: length + 6
|
||||
type: u2
|
||||
|
||||
types:
|
||||
mon_hw:
|
||||
seq:
|
||||
- id: pin_sel
|
||||
type: u4
|
||||
- id: pin_bank
|
||||
type: u4
|
||||
- id: pin_dir
|
||||
type: u4
|
||||
- id: pin_val
|
||||
type: u4
|
||||
- id: noise_per_ms
|
||||
type: u2
|
||||
- id: agc_cnt
|
||||
type: u2
|
||||
- id: a_status
|
||||
type: u1
|
||||
enum: antenna_status
|
||||
- id: a_power
|
||||
type: u1
|
||||
enum: antenna_power
|
||||
- id: flags
|
||||
type: u1
|
||||
- id: reserved1
|
||||
size: 1
|
||||
- id: used_mask
|
||||
type: u4
|
||||
- id: vp
|
||||
size: 17
|
||||
- id: jam_ind
|
||||
type: u1
|
||||
- id: reserved2
|
||||
size: 2
|
||||
- id: pin_irq
|
||||
type: u4
|
||||
- id: pull_h
|
||||
type: u4
|
||||
- id: pull_l
|
||||
type: u4
|
||||
enums:
|
||||
antenna_status:
|
||||
0: init
|
||||
1: dontknow
|
||||
2: ok
|
||||
3: short
|
||||
4: open
|
||||
antenna_power:
|
||||
0: off
|
||||
1: on
|
||||
2: dontknow
|
||||
|
||||
mon_hw2:
|
||||
seq:
|
||||
- id: ofs_i
|
||||
type: s1
|
||||
- id: mag_i
|
||||
type: u1
|
||||
- id: ofs_q
|
||||
type: s1
|
||||
- id: mag_q
|
||||
type: u1
|
||||
- id: cfg_source
|
||||
type: u1
|
||||
enum: config_source
|
||||
- id: reserved1
|
||||
size: 3
|
||||
- id: low_lev_cfg
|
||||
type: u4
|
||||
- id: reserved2
|
||||
size: 8
|
||||
- id: post_status
|
||||
type: u4
|
||||
- id: reserved3
|
||||
size: 4
|
||||
|
||||
enums:
|
||||
config_source:
|
||||
113: rom
|
||||
111: otp
|
||||
112: config_pins
|
||||
102: flash
|
||||
|
||||
rxm_sfrbx:
|
||||
seq:
|
||||
- id: gnss_id
|
||||
type: u1
|
||||
enum: gnss_type
|
||||
- id: sv_id
|
||||
type: u1
|
||||
- id: reserved1
|
||||
size: 1
|
||||
- id: freq_id
|
||||
type: u1
|
||||
- id: num_words
|
||||
type: u1
|
||||
- id: reserved2
|
||||
size: 1
|
||||
- id: version
|
||||
type: u1
|
||||
- id: reserved3
|
||||
size: 1
|
||||
- id: body
|
||||
type: u4
|
||||
repeat: expr
|
||||
repeat-expr: num_words
|
||||
|
||||
rxm_rawx:
|
||||
seq:
|
||||
- id: rcv_tow
|
||||
type: f8
|
||||
- id: week
|
||||
type: u2
|
||||
- id: leap_s
|
||||
type: s1
|
||||
- id: num_meas
|
||||
type: u1
|
||||
- id: rec_stat
|
||||
type: u1
|
||||
- id: reserved1
|
||||
size: 3
|
||||
- id: meas
|
||||
type: measurement
|
||||
size: 32
|
||||
repeat: expr
|
||||
repeat-expr: num_meas
|
||||
types:
|
||||
measurement:
|
||||
seq:
|
||||
- id: pr_mes
|
||||
type: f8
|
||||
- id: cp_mes
|
||||
type: f8
|
||||
- id: do_mes
|
||||
type: f4
|
||||
- id: gnss_id
|
||||
type: u1
|
||||
enum: gnss_type
|
||||
- id: sv_id
|
||||
type: u1
|
||||
- id: reserved2
|
||||
size: 1
|
||||
- id: freq_id
|
||||
type: u1
|
||||
- id: lock_time
|
||||
type: u2
|
||||
- id: cno
|
||||
type: u1
|
||||
- id: pr_stdev
|
||||
type: u1
|
||||
- id: cp_stdev
|
||||
type: u1
|
||||
- id: do_stdev
|
||||
type: u1
|
||||
- id: trk_stat
|
||||
type: u1
|
||||
- id: reserved3
|
||||
size: 1
|
||||
nav_sat:
|
||||
seq:
|
||||
- id: itow
|
||||
type: u4
|
||||
- id: version
|
||||
type: u1
|
||||
- id: num_svs
|
||||
type: u1
|
||||
- id: reserved
|
||||
size: 2
|
||||
- id: svs
|
||||
type: nav
|
||||
size: 12
|
||||
repeat: expr
|
||||
repeat-expr: num_svs
|
||||
types:
|
||||
nav:
|
||||
seq:
|
||||
- id: gnss_id
|
||||
type: u1
|
||||
enum: gnss_type
|
||||
- id: sv_id
|
||||
type: u1
|
||||
- id: cno
|
||||
type: u1
|
||||
- id: elev
|
||||
type: s1
|
||||
- id: azim
|
||||
type: s2
|
||||
- id: pr_res
|
||||
type: s2
|
||||
- id: flags
|
||||
type: u4
|
||||
|
||||
nav_pvt:
|
||||
seq:
|
||||
- id: i_tow
|
||||
type: u4
|
||||
- id: year
|
||||
type: u2
|
||||
- id: month
|
||||
type: u1
|
||||
- id: day
|
||||
type: u1
|
||||
- id: hour
|
||||
type: u1
|
||||
- id: min
|
||||
type: u1
|
||||
- id: sec
|
||||
type: u1
|
||||
- id: valid
|
||||
type: u1
|
||||
- id: t_acc
|
||||
type: u4
|
||||
- id: nano
|
||||
type: s4
|
||||
- id: fix_type
|
||||
type: u1
|
||||
- id: flags
|
||||
type: u1
|
||||
- id: flags2
|
||||
type: u1
|
||||
- id: num_sv
|
||||
type: u1
|
||||
- id: lon
|
||||
type: s4
|
||||
- id: lat
|
||||
type: s4
|
||||
- id: height
|
||||
type: s4
|
||||
- id: h_msl
|
||||
type: s4
|
||||
- id: h_acc
|
||||
type: u4
|
||||
- id: v_acc
|
||||
type: u4
|
||||
- id: vel_n
|
||||
type: s4
|
||||
- id: vel_e
|
||||
type: s4
|
||||
- id: vel_d
|
||||
type: s4
|
||||
- id: g_speed
|
||||
type: s4
|
||||
- id: head_mot
|
||||
type: s4
|
||||
- id: s_acc
|
||||
type: s4
|
||||
- id: head_acc
|
||||
type: u4
|
||||
- id: p_dop
|
||||
type: u2
|
||||
- id: flags3
|
||||
type: u1
|
||||
- id: reserved1
|
||||
size: 5
|
||||
- id: head_veh
|
||||
type: s4
|
||||
- id: mag_dec
|
||||
type: s2
|
||||
- id: mag_acc
|
||||
type: u2
|
||||
enums:
|
||||
gnss_type:
|
||||
0: gps
|
||||
1: sbas
|
||||
2: galileo
|
||||
3: beidou
|
||||
4: imes
|
||||
5: qzss
|
||||
6: glonass
|
||||
Reference in New Issue
Block a user