def set_data(self, parsed_data): """ Set GPS data dictionary from UBX sentences. """ try: if parsed_data.identity == "NAV-PVT": self.gpsdata[ "date"] = f"{parsed_data.day:02}/{parsed_data.month:02}/{parsed_data.year}" self.gpsdata[ "time"] = f"{parsed_data.hour:02}:{parsed_data.min:02}:{parsed_data.second:02}" self.gpsdata["latitude"] = parsed_data.lat self.gpsdata["longitude"] = parsed_data.lon self.gpsdata["elevation"] = parsed_data.height / 1000 self.gpsdata["speed"] = parsed_data.gSpeed self.gpsdata["track"] = parsed_data.headVeh self.gpsdata["fix"] = parsed_data.fixType self.gpsdata["pDOP"] = parsed_data.pDOP if parsed_data.identity == "NAV-POSLLH": self.gpsdata["latitude"] = parsed_data.lat self.gpsdata["longitude"] = parsed_data.lon self.gpsdata["elevation"] = parsed_data.height / 1000 if parsed_data.identity == "NAV-DOP": self.gpsdata["pdop"] = parsed_data.pDOP self.gpsdata["hdop"] = parsed_data.hDOP self.gpsdata["vdop"] = parsed_data.vDOP if parsed_data.identity == "NAV-SAT": self.gpsdata["siv"] = parsed_data.numSvs except ube.UBXMessageError() as err: print(err) self._stopevent.set()
def _do_attributes(self, **kwargs): """Populate UBXMessage from named attribute keywords. Where a named attribute is absent, set to a nominal value (zeros or blanks). :param **kwargs: """ offset = 0 try: if len(kwargs) == 0: # if no kwargs, assume null payload self._payload = None else: pdict = self._get_dict() # get appropriate payload dict for key in pdict.keys(): # set each attribute in dict (offset, att) = self._set_attribute(offset, pdict, key, **kwargs) self._do_len_checksum() except (AttributeError, OverflowError, struct.error, TypeError, ValueError) as err: raise ube.UBXTypeError((f"Incorrect type for attribute '{key}' " f"in {self.mode2str(self._mode)} message " f"class {self.identity}")) from err except ube.UBXTypeError as err: raise ube.UBXTypeError((f"Undefined attribute type '{att}' " f"in {self.mode2str(self._mode)} message " f"class {self.identity}")) from err except KeyError as err: raise ube.UBXMessageError( (f"Undefined {self.mode2str(self._mode)} " f"message class={self._ubxClass}, " f"id={self._ubxID}")) from err
def _get_esfmeas_version(self, **kwargs) -> dict: """ Select appropriate payload definition version for ESF-MEAS message by checking bit 3 (calibTtagValid) in the'flags' attribute. :param **kwargs: payload key value pairs :return dictionary representing payload definition :rtype dict :raise UBXMessageError """ # pylint: disable=no-self-use if "flags" in kwargs: flags = kwargs["flags"] elif "payload" in kwargs: flags = kwargs["payload"][4:6] else: raise ube.UBXMessageError( "ESF-MEAS message definitions must include flags or payload keyword" ) flags = int(flags.hex(), 16) # int calibTtagValid = flags >> 3 & 1 # test bit 3 if calibTtagValid: pdict = ubg.UBX_PAYLOADS_GET["ESF-MEAS-CT"] else: pdict = ubg.UBX_PAYLOADS_GET["ESF-MEAS"] return pdict
def config_poll(layer: int, position: int, keys: list) -> object: """ Construct CFG-VALGET message from an array of configuration database keys, which can be in int (keyID) or str (keyname) format. :param int layer: memory layer (0=RAM, 1=BBR, 2=Flash, 7 = Default) :param int position: number of keys to skip before returning result :param list keys: array of up to 64 keys as int (keyID) or str (keyname) :return: UBXMessage CFG-VALGET :rtype: UBXMessage :raises: UBXMessageError """ num = len(keys) if num > 64: raise ube.UBXMessageError( f"Number of configuration keys {num} exceeds maximum of 64" ) version = UBXMessage.val2bytes(0, ubt.U1) layer = UBXMessage.val2bytes(layer, ubt.U1) position = UBXMessage.val2bytes(position, ubt.U2) payload = version + layer + position lis = b"" for key in keys: if isinstance(key, str): # if keyname as a string (key, _) = UBXMessage.cfgname2key(key) # lookup keyID keyb = UBXMessage.val2bytes(key, ubt.U4) lis = lis + keyb return UBXMessage("CFG", "CFG-VALGET", ubt.POLL, payload=payload + lis)
def config_del(layers: int, transaction: int, keys: list) -> object: """ Construct CFG-VALDEL message from an array of configuration database keys, which can be in int (keyID) or str (keyname) format. :param int layers: memory layer(s) (2=BBR, 4=Flash) :param int transaction: 0=no txn, 1=start txn, 2=continue txn, 3=apply txn :param list keys: array of up to 64 keys as int (keyID) or string (keyname) :return: UBXMessage CFG-VALDEL :rtype: UBXMessage :raises: UBXMessageError """ num = len(keys) if num > 64: raise ube.UBXMessageError( f"Number of configuration keys {num} exceeds maximum of 64" ) version = UBXMessage.val2bytes(0 if transaction == 0 else 1, ubt.U1) layers = UBXMessage.val2bytes(layers, ubt.U1) transaction = UBXMessage.val2bytes(transaction, ubt.U1) payload = version + layers + transaction + b"\x00" lis = b"" for key in keys: if isinstance(key, str): # if keyname as a string (key, _) = UBXMessage.cfgname2key(key) # lookup keyID keyb = UBXMessage.val2bytes(key, ubt.U4) lis = lis + keyb return UBXMessage("CFG", "CFG-VALDEL", ubt.SET, payload=payload + lis)
def _get_timvcocal_version(self, **kwargs) -> dict: """ Select appropriate TIM-VCOCAL SET payload definition by checking the payload length. :param kwargs: optional payload key/value pairs :return: dictionary representing payload definition :rtype: dict :raises: UBXMessageError """ # pylint: disable=no-self-use lpd = 1 typ = 0 if "type" in kwargs: typ = kwargs["type"] elif "payload" in kwargs: lpd = len(kwargs["payload"]) else: raise ube.UBXMessageError( "TIM-VCOCAL SET message definitions must include type or payload keyword" ) if lpd == 1 and typ == 0: pdict = ubs.UBX_PAYLOADS_SET["TIM-VCOCAL-V0"] # stop cal else: pdict = ubs.UBX_PAYLOADS_SET["TIM-VCOCAL"] # cal return pdict
def _set_cfgval_attributes(self, offset: int, **kwargs): """ Parse CFG-VALGET payload to set of configuration key value pairs. :param int offset: payload offset :param **kwargs: payload key value pairs :raise UBXMessageError """ KEYLEN = 4 if "payload" in kwargs: self._payload = kwargs["payload"] else: raise ube.UBXMessageError( "CFG-VALGET message definitions must include payload keyword") cfglen = len(self._payload[offset:]) i = 0 while offset < cfglen: if i == KEYLEN: key = int.from_bytes(self._payload[offset:offset + KEYLEN], "little", signed=False) (keyname, att) = self.cfgkey2name(key) atts = attsiz(att) valb = self._payload[offset + KEYLEN:offset + KEYLEN + atts] val = self.bytes2val(valb, att) setattr(self, keyname, val) i = 0 offset += KEYLEN + atts else: i += 1
def _get_rxmpmreq_version(self, **kwargs) -> dict: """ Select appropriate RXM-PMREQ payload definition by checking the 'version' keyword or payload length. :param kwargs: optional payload key/value pairs :return: dictionary representing payload definition :rtype: dict :raises: UBXMessageError """ # pylint: disable=no-self-use lpd = 0 if "version" in kwargs: # assume longer version lpd = 16 elif "payload" in kwargs: lpd = len(kwargs["payload"]) else: raise ube.UBXMessageError( "RXM-PMREQ message definitions must include version or payload keyword" ) if lpd == 16: pdict = ubs.UBX_PAYLOADS_SET["RXM-PMREQ"] # long else: pdict = ubs.UBX_PAYLOADS_SET["RXM-PMREQ-S"] # short return pdict
def _get_rxmrlm_version(self, **kwargs) -> dict: """ Select appropriate RXM-PMP payload definition by checking value of 'type' attribute (2nd byte of payload). :param kwargs: optional payload key/value pairs :return: dictionary representing payload definition :rtype: dict :raises: UBXMessageError """ # pylint: disable=no-self-use if "type" in kwargs: typ = self.val2bytes(kwargs["type"], ubt.U1) elif "payload" in kwargs: typ = kwargs["payload"][1:2] else: raise ube.UBXMessageError( "RXM-RLM message definitions must include type or payload keyword" ) if typ == b"\x01": pdict = ubg.UBX_PAYLOADS_GET["RXM-RLM-S"] # short else: pdict = ubg.UBX_PAYLOADS_GET["RXM-RLM-L"] # long return pdict
def _get_rxmpmp_version(self, **kwargs) -> dict: """ Select appropriate RXM-PMP payload definition by checking value of 'version' attribute (1st byte of payload). :param kwargs: optional payload key/value pairs :return: dictionary representing payload definition :rtype: dict :raises: UBXMessageError """ # pylint: disable=no-self-use if "version" in kwargs: ver = self.val2bytes(kwargs["version"], ubt.U1) elif "payload" in kwargs: ver = kwargs["payload"][0:1] else: raise ube.UBXMessageError( "RXM-PMP message definitions must include version or payload keyword" ) if ver == b"\x00": pdict = ubg.UBX_PAYLOADS_GET["RXM-PMP-V0"] else: pdict = ubg.UBX_PAYLOADS_GET["RXM-PMP-V1"] return pdict
def _get_cfgnmea_version(self, **kwargs) -> dict: """ Select appropriate payload definition version for older generations of CFG-NMEA message by checking payload length. :param kwargs: optional payload key/value pairs :return: dictionary representing payload definition :rtype: dict :raises: UBXMessageError """ # pylint: disable=no-self-use if "payload" in kwargs: lpd = len(kwargs["payload"]) else: raise ube.UBXMessageError( "CFG-NMEA message definitions must include payload keyword" ) if lpd == 4: pdict = ubg.UBX_PAYLOADS_GET["CFG-NMEAvX"] elif lpd == 12: pdict = ubg.UBX_PAYLOADS_GET["CFG-NMEAv0"] else: pdict = ubg.UBX_PAYLOADS_GET["CFG-NMEA"] return pdict
def _get_mga_version(self, mode: int, **kwargs) -> dict: """ Select appropriate MGA payload definition by checking value of 'type' attribute (1st byte of payload). :param str mode: mode (0=GET, 1=SET, 2=POLL) :param kwargs: optional payload key/value pairs :return: dictionary representing payload definition :rtype: dict :raises: UBXMessageError """ if "type" in kwargs: typ = self.val2bytes(kwargs["type"], ubt.U1) elif "payload" in kwargs: typ = kwargs["payload"][0:1] else: raise ube.UBXMessageError( "MGA message definitions must include type or payload keyword" ) identity = ubt.UBX_MSGIDS[self._ubxClass + self._ubxID + typ] if mode == ubt.SET: pdict = ubs.UBX_PAYLOADS_SET[identity] else: pdict = ubg.UBX_PAYLOADS_GET[identity] return pdict
def _get_esfmeas_version(self, **kwargs) -> dict: """ Select appropriate payload definition version for ESF-MEAS message by checking bit 3 (calibTtagValid) in the'flags' attribute. :param kwargs: optional payload key/value pairs :return: dictionary representing payload definition :rtype: dict :raises: UBXMessageError """ # pylint: disable=no-self-use if "flags" in kwargs: flags = kwargs["flags"] elif "payload" in kwargs: flags = kwargs["payload"][4:6] else: raise ube.UBXMessageError( "ESF-MEAS message definitions must include flags or payload keyword" ) calibTtagValid = get_bits(flags, 0b00001000) # get bit 3 in flags if calibTtagValid: pdict = ubg.UBX_PAYLOADS_GET["ESF-MEAS-CT"] else: pdict = ubg.UBX_PAYLOADS_GET["ESF-MEAS"] return pdict
def __setattr__(self, name, value): """ Override setattr to make object immutable after instantiation """ if self._immutable: raise ube.UBXMessageError( f"Object is immutable. Updates to {name} not permitted after initialisation." ) super().__setattr__(name, value)
def key_from_val(dictionary: dict, value) -> str: """Helper method - get dictionary key corresponding to (unique) value. :param dictionary: dict: :param value: :return str: """ val = None for key, val in dictionary.items(): if val == value: return key raise ube.UBXMessageError(f"Undefined message type {value}")
def cfgname2key(name: str) -> tuple: """ Return hexadecimal key and data type for given configuration database key name. :param str name: config database key name as string :return tuple of (key: int, type: str) :rtype tuple: (int, str) :raise UBXMessageError """ try: return ubcdb.UBX_CONFIG_DATABASE[name] except KeyError as err: raise ube.UBXMessageError( f"Undefined configuration database key {name}") from err
def __init__(self, ubxClass, ubxID, msgmode: int, **kwargs): """Constructor. If no keyword parms are passed, the payload is taken to be empty. If 'payload' is passed as a keyword parm, this is taken to contain the complete payload as a sequence of bytes; any other keyword parms are ignored. Otherwise, any named attributes will be assigned the value given, all others will be assigned a nominal value according to type. :param object msgClass: message class as str, int or byte :param object msgID: message ID as str, int or byte :param int msgmode: message mode (0=GET, 1=SET, 2=POLL) :param kwargs: optional payload key/value pairs :raises: UBXMessageError """ # object is mutable during initialisation only super().__setattr__("_immutable", False) self._mode = msgmode self._payload = b"" self._length = b"" self._checksum = b"" self._parsebf = kwargs.get("parsebitfield", True) # parsing bitfields Y/N? self._decodenavdata = kwargs.get( "decodenavdata", True ) # decode RXM-SFRBX nav data Y/N? TODO make default False in final if msgmode not in (0, 1, 2): raise ube.UBXMessageError(f"Invalid msgmode {msgmode} - must be 0, 1 or 2.") # accommodate different formats of msgClass and msgID if isinstance(ubxClass, str) and isinstance( ubxID, str ): # string e.g. 'CFG', 'CFG-PRT' (self._ubxClass, self._ubxID) = UBXMessage.msgstr2bytes(ubxClass, ubxID) elif isinstance(ubxClass, int) and isinstance(ubxID, int): # int e.g. 6, 1 (self._ubxClass, self._ubxID) = UBXMessage.msgclass2bytes(ubxClass, ubxID) else: # bytes e.g. b'\x06', b'\x01' self._ubxClass = ubxClass self._ubxID = ubxID self._do_attributes(**kwargs) self._immutable = True # once initialised, object is immutable
def msgstr2bytes(msgClass: str, msgID: str) -> bytes: """Convert plain text UBX message class to bytes e.g. 'CFG-MSG' to b'/x06/x01'. :param msgClass: str: :param msgID: str: """ try: clsid = UBXMessage.key_from_val(ubt.UBX_CLASSES, msgClass) msgid = UBXMessage.key_from_val(ubt.UBX_MSGIDS, msgID)[1:] return (clsid, msgid) except ube.UBXMessageError as err: raise ube.UBXMessageError( f"Undefined message, class {msgClass}, id {msgID}") from err
def cfgkey2name(keyID: int) -> tuple: """ Return key name and data type for given configuration database hexadecimal key. :param int keyID: config key as integer e.g. 0x20930001 :return: tuple of (keyname, type) :rtype: tuple: (str, str) :raises: UBXMessageError """ val = None for key, val in ubcdb.UBX_CONFIG_DATABASE.items(): (kid, typ) = val if keyID == kid: return (key, typ) raise ube.UBXMessageError(f"Undefined configuration database key {hex(keyID)}")
def __init__(self, ubxClass, ubxID, mode: int, **kwargs): """Constructor. If no keyword parms are passed, the payload is taken to be empty. If 'payload' is passed as a keyword parm, this is taken to contain the complete payload as a sequence of bytes; any other keyword parms are ignored. Otherwise, any named attributes will be assigned the value given, all others will be assigned a nominal value according to type. :param object msgClass: str, int or byte: :param object msgID: str, int or byte: :param int mode: SET, GET or POLL :param kwargs: payload key value pairs :raise UBXMessageError """ # object is mutable during initialisation only super().__setattr__("_immutable", False) self._mode = mode self._payload = b"" self._length = b"" self._checksum = b"" if mode not in (0, 1, 2): raise ube.UBXMessageError( f"Invalid mode {mode} - must be 0, 1 or 2") # accommodate different formats of msgClass and msgID if isinstance(ubxClass, str) and isinstance( ubxID, str): # string e.g. 'CFG', 'CFG-PRT' (self._ubxClass, self._ubxID) = UBXMessage.msgstr2bytes(ubxClass, ubxID) elif isinstance(ubxClass, int) and isinstance(ubxID, int): # int e.g. 6, 1 (self._ubxClass, self._ubxID) = UBXMessage.msgclass2bytes(ubxClass, ubxID) else: # bytes e.g. b'\x06', b'\x01' self._ubxClass = ubxClass self._ubxID = ubxID self._do_attributes(**kwargs) self._immutable = True # once initialised, object is immutable
def msgstr2bytes(msgClass: str, msgID: str) -> bytes: """ Convert plain text UBX message class to bytes. :param str msgClass: message class as str e.g. 'CFG' :param str msgID: message ID as str e.g. 'CFG-MSG' :return: message class as bytes e.g. b'/x06/x01' :rtype: bytes :raises: UBXMessageError """ try: clsid = key_from_val(ubt.UBX_CLASSES, msgClass) msgid = key_from_val(ubt.UBX_MSGIDS, msgID)[1:2] return (clsid, msgid) except KeyError as err: raise ube.UBXMessageError( f"Undefined message, class {msgClass}, id {msgID}") from err
def config_set(layers: int, transaction: int, cfgData: list) -> object: """ Construct CFG-VALSET message from an array of configuration database (key, value) tuples. Keys can be in int (keyID) or str (keyname) format. :param int layers: memory layer(s) (1=RAM, 2=BBR, 4=Flash) :param int transaction: 0=no txn, 1=start txn, 2=continue txn, 3=apply txn :param list cfgData: list of up to 64 tuples (key, value) :return: UBXMessage CFG-VALSET :rtype: UBXMessage :raises: UBXMessageError """ num = len(cfgData) if num > 64: raise ube.UBXMessageError( f"Number of configuration tuples {num} exceeds maximum of 64" ) version = UBXMessage.val2bytes(0 if transaction == 0 else 1, ubt.U1) layers = UBXMessage.val2bytes(layers, ubt.U1) transaction = UBXMessage.val2bytes(transaction, ubt.U1) payload = version + layers + transaction + b"\x00" lis = b"" for cfgItem in cfgData: att = "" (key, val) = cfgItem if isinstance(key, str): # if key is a string (keyname) (key, att) = UBXMessage.cfgname2key( key ) # lookup keyID & attribute type else: (_, att) = UBXMessage.cfgkey2name(key) # lookup attribute type keyb = UBXMessage.val2bytes(key, ubt.U4) valb = UBXMessage.val2bytes(val, att) lis = lis + keyb + valb return UBXMessage("CFG", "CFG-VALSET", ubt.SET, payload=payload + lis)
def identity(self) -> str: """ Returns identity in plain text form e.g. 'CFG-MSG'. :return message identity :rtype str :raise UBXMessageError """ try: # all MGA messages except MGA-DBD need to be identified by the # 'type' attribute - the first byte of the payload if self._ubxClass == b"\x13" and self._ubxID != b"\x80": umsg_name = ubt.UBX_MSGIDS[self._ubxClass + self._ubxID + self._payload[0:1]] else: umsg_name = ubt.UBX_MSGIDS[self._ubxClass + self._ubxID] except KeyError as err: raise ube.UBXMessageError( f"Unknown UBX message type class {self._ubxClass} id {self._ubxID}" ) from err return umsg_name
def identity(self) -> str: """Message identity getter. Returns identity in plain text form e.g. 'CFG-MSG'. :return identity: str: """ # pylint: disable=line-too-long try: # all MGA messages except MGA-DBD need to be identified by the # 'type' attribute - the first byte of the payload if self._ubxClass == b'\x13' and self._ubxID != b'\x80': umsg_name = ubt.UBX_MSGIDS[self._ubxClass + self._ubxID + self._payload[0:1]] else: umsg_name = ubt.UBX_MSGIDS[self._ubxClass + self._ubxID] except KeyError as err: raise ube.UBXMessageError( (f"Message type {self._ubxClass + self._ubxID}" f" not defined")) from err return umsg_name
def _get_aopstatus_version(self, **kwargs) -> dict: """ Select appropriate payload definition version for older generations of NAV-AOPSTATUS message by checking payload length. :param kwargs: optional payload key/value pairs :return: dictionary representing payload definition :rtype: dict :raises: UBXMessageError """ # pylint: disable=no-self-use if "payload" in kwargs: lpd = len(kwargs["payload"]) else: raise ube.UBXMessageError( "NAV-AOPSTATUS message definitions must include payload keyword" ) if lpd == 20: pdict = ubg.UBX_PAYLOADS_GET["NAV-AOPSTATUS-L"] else: pdict = ubg.UBX_PAYLOADS_GET["NAV-AOPSTATUS"] return pdict