From a81d9740acca4cabbb8b8c1ba62ff391e03528cd Mon Sep 17 00:00:00 2001 From: Adam Shapiro Date: Wed, 5 Jun 2024 12:16:38 -0400 Subject: [PATCH] Moved VersionInfoMessage, DeviceIDMessage, and EventNotificationMessage to device.h. These are informative status messages, not commands/control messages. --- .../fusion_engine_client/messages/control.py | 263 ----------------- .../fusion_engine_client/messages/device.py | 266 ++++++++++++++++- .../fusion_engine/messages/control.h | 260 ----------------- src/point_one/fusion_engine/messages/device.h | 270 +++++++++++++++++- 4 files changed, 530 insertions(+), 529 deletions(-) diff --git a/python/fusion_engine_client/messages/control.py b/python/fusion_engine_client/messages/control.py index c8b49a0f..d2a3c3cf 100644 --- a/python/fusion_engine_client/messages/control.py +++ b/python/fusion_engine_client/messages/control.py @@ -1,11 +1,8 @@ -import string import struct from typing import Sequence from construct import (Struct, Int64ul, Int16ul, Int8ul, Padding, this, Bytes, PaddedString) -from ..utils.construct_utils import AutoEnum, construct_message_to_string -from ..utils.enum_utils import IntEnum from .defs import * @@ -380,266 +377,6 @@ def _get_known_mask_name(cls, mask) -> str: return None -class VersionInfoMessage(MessagePayload): - """! - @brief Software and hardware version information. - """ - MESSAGE_TYPE = MessageType.VERSION_INFO - MESSAGE_VERSION = 0 - - VersionInfoMessageConstruct = Struct( - "system_time_ns" / Int64ul, - "fw_version_length" / Int8ul, - "engine_version_length" / Int8ul, - "os_version_length" / Int8ul, - "rx_version_length" / Int8ul, - Padding(4), - "fw_version_str" / PaddedString(this.fw_version_length, 'utf8'), - "engine_version_str" / PaddedString(this.engine_version_length, 'utf8'), - "os_version_str" / PaddedString(this.os_version_length, 'utf8'), - "rx_version_str" / PaddedString(this.rx_version_length, 'utf8'), - ) - - def __init__(self): - self.system_time_ns = 0 - self.fw_version_str = "" - self.engine_version_str = "" - self.os_version_str = "" - self.rx_version_str = "" - - def pack(self, buffer: bytes = None, offset: int = 0, return_buffer: bool = True) -> (bytes, int): - values = dict(self.__dict__) - values['fw_version_length'] = len(self.fw_version_str) - values['engine_version_length'] = len(self.engine_version_str) - values['os_version_length'] = len(self.os_version_str) - values['rx_version_length'] = len(self.rx_version_str) - packed_data = self.VersionInfoMessageConstruct.build(values) - return PackedDataToBuffer(packed_data, buffer, offset, return_buffer) - - def unpack(self, buffer: bytes, offset: int = 0, message_version: int = MessagePayload._UNSPECIFIED_VERSION) -> int: - parsed = self.VersionInfoMessageConstruct.parse(buffer[offset:]) - self.__dict__.update(parsed) - return parsed._io.tell() - - def __repr__(self): - result = super().__repr__()[:-1] - result += f', fw={self.fw_version_str}, engine={self.engine_version_str}, os={self.os_version_str} ' \ - f'rx={self.rx_version_str}]' - return result - - def __str__(self): - string = f'Version Info @ %s\n' % system_time_to_str(self.system_time_ns) - string += f' Firmware: {self.fw_version_str}\n' - string += f' FusionEngine: {self.engine_version_str}\n' - string += f' OS: {self.os_version_str}\n' - string += f' GNSS receiver: {self.rx_version_str}' - return string - - def calcsize(self) -> int: - return len(self.pack()) - - -class DeviceType(IntEnum): - UNKNOWN = 0 - ATLAS = 1 - LG69T_AM = 2 - LG69T_AP = 3 - LG69T_AH = 4 - NEXAR_BEAM2K = 5, - SSR_LG69T = 6, - SSR_DESKTOP = 7, - - -class DeviceIDMessage(MessagePayload): - """! - @brief Device identifiers. - """ - MESSAGE_TYPE = MessageType.DEVICE_ID - MESSAGE_VERSION = 0 - _PRINTABLE_CHARS = bytes(string.printable, 'ascii') - - DeviceIDMessageConstruct = Struct( - "system_time_ns" / Int64ul, - "device_type" / AutoEnum(Int8ul, DeviceType), - "hw_id_length" / Int8ul, - "user_id_length" / Int8ul, - "receiver_id_length" / Int8ul, - Padding(4), - "hw_id_data" / Bytes(this.hw_id_length), - "user_id_data" / Bytes(this.user_id_length), - "receiver_id_data" / Bytes(this.receiver_id_length), - ) - - def __init__(self): - self.system_time_ns = 0 - self.device_type = DeviceType.UNKNOWN - self.hw_id_data = b"" - self.user_id_data = b"" - self.receiver_id_data = b"" - - def pack(self, buffer: bytes = None, offset: int = 0, return_buffer: bool = True) -> (bytes, int): - values = dict(self.__dict__) - values['hw_id_length'] = len(self.hw_id_length) - values['user_id_length'] = len(self.user_id_length) - values['receiver_id_length'] = len(self.receiver_id_length) - packed_data = self.DeviceIDMessageConstruct.build(values) - return PackedDataToBuffer(packed_data, buffer, offset, return_buffer) - - def unpack(self, buffer: bytes, offset: int = 0, message_version: int = MessagePayload._UNSPECIFIED_VERSION) -> int: - parsed = self.DeviceIDMessageConstruct.parse(buffer[offset:]) - self.__dict__.update(parsed) - return parsed._io.tell() - - @staticmethod - def _get_str(msg: bytes) -> str: - is_printable = all(b in DeviceIDMessage._PRINTABLE_CHARS for b in msg) - if is_printable: - return msg.decode('ascii') - else: - return '[' + ' '.join(f'{b:02X}' for b in msg) + ']' - - def __repr__(self): - result = super().__repr__()[:-1] - result += f'type={self.device_type}, hw={self._get_str(self.hw_id_data)}, user={self._get_str(self.user_id_data)},\ - rx={self._get_str(self.receiver_id_data)}' - return result - - def __str__(self): - string = f'Device ID Info @ %s\n' % system_time_to_str(self.system_time_ns) - string += f' Device Type: {self.device_type}\n' - string += f' HW ID: {self._get_str(self.hw_id_data)}\n' - string += f' User ID: {self._get_str(self.user_id_data)}\n' - string += f' Receiver ID: {self._get_str(self.receiver_id_data)}' - return string - - def calcsize(self) -> int: - return len(self.pack()) - - -class EventType(IntEnum): - LOG = 0 - RESET = 1 - CONFIG_CHANGE = 2 - COMMAND = 3 - COMMAND_RESPONSE = 4 - - -class EventNotificationMessage(MessagePayload): - """! - @brief Notification of a system event for logging purposes. - """ - MESSAGE_TYPE = MessageType.EVENT_NOTIFICATION - MESSAGE_VERSION = 0 - - EventNotificationConstruct = Struct( - "event_type" / AutoEnum(Int8ul, EventType), - Padding(3), - "system_time_ns" / Int64ul, - "event_flags" / Int64ul, - "event_description_len_bytes" / Int16ul, - Padding(2), - "event_description" / Bytes(this.event_description_len_bytes), - ) - - def __init__(self): - self.event_type = EventType.LOG - self.system_time_ns = 0 - self.event_flags = 0 - self.event_description = bytes() - - def pack(self, buffer: bytes = None, offset: int = 0, return_buffer: bool = True) -> (bytes, int): - values = dict(self.__dict__) - values['event_description_len_bytes'] = len(self.event_description) - if isinstance(self.event_description, str): - values['event_description'] = self.event_description.encode('utf-8') - packed_data = self.EventNotificationConstruct.build(values) - return PackedDataToBuffer(packed_data, buffer, offset, return_buffer) - - def unpack(self, buffer: bytes, offset: int = 0, message_version: int = MessagePayload._UNSPECIFIED_VERSION) -> int: - parsed = self.EventNotificationConstruct.parse(buffer[offset:]) - self.__dict__.update(parsed) - - # For logged FusionEngine commands/responses, the device intentionally offsets the preamble by 0x0101 from - # 0x2E31 ('.1') to 0x2F32 ('/2'). That way, the encapsulated messages within the event messages don't get - # parsed, but we can still identify them. We'll undo that offset here so the content in self.event_description - # reflects the original command/response. - if (self.event_type == EventType.COMMAND or self.event_type == EventType.COMMAND_RESPONSE) and \ - len(self.event_description) >= 2 and (self.event_description[:2] == b'/2'): - self.event_description = bytearray(self.event_description) - self.event_description[0] -= 1 - self.event_description[1] -= 1 - - return parsed._io.tell() - - def __repr__(self): - result = super().__repr__()[:-1] - result += f', type={self.event_type}, flags=0x{self.event_flags:X}' - if self.event_type == EventType.COMMAND or self.event_type == EventType.COMMAND_RESPONSE: - result += f', data={len(self.event_description)} B' - else: - result += f', description={self.event_description}' - result += ']' - return result - - def __str__(self): - return construct_message_to_string( - message=self, construct=self.EventNotificationConstruct, - title=f'Event Notification @ %s' % system_time_to_str(self.system_time_ns), - fields=['event_type', 'event_flags', 'event_description'], - value_to_string={ - 'event_flags': lambda x: '0x%016X' % x, - 'event_description': lambda x: self.event_description_to_string(), - }) - - def calcsize(self) -> int: - return len(self.pack()) - - def event_description_to_string(self, max_bytes=None): - # For commands and responses, the payload should contain the binary FusionEngine message. Try to decode the - # message type. - if self.event_type == EventType.COMMAND or self.event_type == EventType.COMMAND_RESPONSE: - if len(self.event_description) >= MessageHeader.calcsize(): - header = MessageHeader() - header.unpack(self.event_description, validate_crc=False, warn_on_unrecognized=False) - message_repr = f'[{header.message_type.to_string(include_value=True)}]' - - message_cls = MessagePayload.get_message_class(header.message_type) - if message_cls is not None: - try: - message = message_cls() - message.unpack(buffer=self.event_description, offset=header.calcsize()) - message_repr = repr(message) - except ValueError as e: - pass - else: - message_repr = '' - - return "%s\n%s" % (message_repr, - self._populate_data_byte_string(self.event_description, max_bytes=max_bytes)) - elif isinstance(self.event_description, str): - return self.event_description - else: - try: - return self.event_description.decode('utf-8') - except UnicodeDecodeError: - return repr(self.event_description) - - @classmethod - def to_numpy(cls, messages: Sequence['EventNotificationMessage']): - result = { - 'system_time': np.array([m.system_time_ns * 1e-9 for m in messages]), - 'event_type': np.array([int(m.event_type) for m in messages], dtype=int), - 'event_flags': np.array([int(m.event_flags) for m in messages], dtype=np.uint64), - } - return result - - @classmethod - def _populate_data_byte_string(cls, data: bytes, max_bytes: int = None): - data_truncated = data if max_bytes is None else data[:max_bytes] - suffix = '' if len(data_truncated) == len(data) else '...' - return f'Data ({len(data)} B): {" ".join("%02X" % b for b in data_truncated)}{suffix}' - - class ShutdownRequest(MessagePayload): """! @brief Perform a device shutdown. diff --git a/python/fusion_engine_client/messages/device.py b/python/fusion_engine_client/messages/device.py index 34d0abc6..84a79ccd 100644 --- a/python/fusion_engine_client/messages/device.py +++ b/python/fusion_engine_client/messages/device.py @@ -1,12 +1,274 @@ import math +import string -from construct import (Struct, Int16sl, Padding) +from construct import Struct, this, Padding, PaddedString, Bytes, Int64ul, Int16ul, Int16sl import numpy as np -from ..utils.construct_utils import FixedPointAdapter, construct_message_to_string +from ..utils.construct_utils import AutoEnum, FixedPointAdapter, construct_message_to_string +from ..utils.enum_utils import IntEnum from .defs import * +class VersionInfoMessage(MessagePayload): + """! + @brief Software and hardware version information. + """ + MESSAGE_TYPE = MessageType.VERSION_INFO + MESSAGE_VERSION = 0 + + VersionInfoMessageConstruct = Struct( + "system_time_ns" / Int64ul, + "fw_version_length" / Int8ul, + "engine_version_length" / Int8ul, + "os_version_length" / Int8ul, + "rx_version_length" / Int8ul, + Padding(4), + "fw_version_str" / PaddedString(this.fw_version_length, 'utf8'), + "engine_version_str" / PaddedString(this.engine_version_length, 'utf8'), + "os_version_str" / PaddedString(this.os_version_length, 'utf8'), + "rx_version_str" / PaddedString(this.rx_version_length, 'utf8'), + ) + + def __init__(self): + self.system_time_ns = 0 + self.fw_version_str = "" + self.engine_version_str = "" + self.os_version_str = "" + self.rx_version_str = "" + + def pack(self, buffer: bytes = None, offset: int = 0, return_buffer: bool = True) -> (bytes, int): + values = dict(self.__dict__) + values['fw_version_length'] = len(self.fw_version_str) + values['engine_version_length'] = len(self.engine_version_str) + values['os_version_length'] = len(self.os_version_str) + values['rx_version_length'] = len(self.rx_version_str) + packed_data = self.VersionInfoMessageConstruct.build(values) + return PackedDataToBuffer(packed_data, buffer, offset, return_buffer) + + def unpack(self, buffer: bytes, offset: int = 0, message_version: int = MessagePayload._UNSPECIFIED_VERSION) -> int: + parsed = self.VersionInfoMessageConstruct.parse(buffer[offset:]) + self.__dict__.update(parsed) + return parsed._io.tell() + + def __repr__(self): + result = super().__repr__()[:-1] + result += f', fw={self.fw_version_str}, engine={self.engine_version_str}, os={self.os_version_str} ' \ + f'rx={self.rx_version_str}]' + return result + + def __str__(self): + string = f'Version Info @ %s\n' % system_time_to_str(self.system_time_ns) + string += f' Firmware: {self.fw_version_str}\n' + string += f' FusionEngine: {self.engine_version_str}\n' + string += f' OS: {self.os_version_str}\n' + string += f' GNSS receiver: {self.rx_version_str}' + return string + + def calcsize(self) -> int: + return len(self.pack()) + + +class DeviceType(IntEnum): + UNKNOWN = 0 + ATLAS = 1 + LG69T_AM = 2 + LG69T_AP = 3 + LG69T_AH = 4 + NEXAR_BEAM2K = 5, + SSR_LG69T = 6, + SSR_DESKTOP = 7, + + +class DeviceIDMessage(MessagePayload): + """! + @brief Device identifiers. + """ + MESSAGE_TYPE = MessageType.DEVICE_ID + MESSAGE_VERSION = 0 + _PRINTABLE_CHARS = bytes(string.printable, 'ascii') + + DeviceIDMessageConstruct = Struct( + "system_time_ns" / Int64ul, + "device_type" / AutoEnum(Int8ul, DeviceType), + "hw_id_length" / Int8ul, + "user_id_length" / Int8ul, + "receiver_id_length" / Int8ul, + Padding(4), + "hw_id_data" / Bytes(this.hw_id_length), + "user_id_data" / Bytes(this.user_id_length), + "receiver_id_data" / Bytes(this.receiver_id_length), + ) + + def __init__(self): + self.system_time_ns = 0 + self.device_type = DeviceType.UNKNOWN + self.hw_id_data = b"" + self.user_id_data = b"" + self.receiver_id_data = b"" + + def pack(self, buffer: bytes = None, offset: int = 0, return_buffer: bool = True) -> (bytes, int): + values = dict(self.__dict__) + values['hw_id_length'] = len(self.hw_id_length) + values['user_id_length'] = len(self.user_id_length) + values['receiver_id_length'] = len(self.receiver_id_length) + packed_data = self.DeviceIDMessageConstruct.build(values) + return PackedDataToBuffer(packed_data, buffer, offset, return_buffer) + + def unpack(self, buffer: bytes, offset: int = 0, message_version: int = MessagePayload._UNSPECIFIED_VERSION) -> int: + parsed = self.DeviceIDMessageConstruct.parse(buffer[offset:]) + self.__dict__.update(parsed) + return parsed._io.tell() + + @staticmethod + def _get_str(msg: bytes) -> str: + is_printable = all(b in DeviceIDMessage._PRINTABLE_CHARS for b in msg) + if is_printable: + return msg.decode('ascii') + else: + return '[' + ' '.join(f'{b:02X}' for b in msg) + ']' + + def __repr__(self): + result = super().__repr__()[:-1] + result += f'type={self.device_type}, hw={self._get_str(self.hw_id_data)}, user={self._get_str(self.user_id_data)},\ + rx={self._get_str(self.receiver_id_data)}' + return result + + def __str__(self): + string = f'Device ID Info @ %s\n' % system_time_to_str(self.system_time_ns) + string += f' Device Type: {self.device_type}\n' + string += f' HW ID: {self._get_str(self.hw_id_data)}\n' + string += f' User ID: {self._get_str(self.user_id_data)}\n' + string += f' Receiver ID: {self._get_str(self.receiver_id_data)}' + return string + + def calcsize(self) -> int: + return len(self.pack()) + + +class EventType(IntEnum): + LOG = 0 + RESET = 1 + CONFIG_CHANGE = 2 + COMMAND = 3 + COMMAND_RESPONSE = 4 + + +class EventNotificationMessage(MessagePayload): + """! + @brief Notification of a system event for logging purposes. + """ + MESSAGE_TYPE = MessageType.EVENT_NOTIFICATION + MESSAGE_VERSION = 0 + + EventNotificationConstruct = Struct( + "event_type" / AutoEnum(Int8ul, EventType), + Padding(3), + "system_time_ns" / Int64ul, + "event_flags" / Int64ul, + "event_description_len_bytes" / Int16ul, + Padding(2), + "event_description" / Bytes(this.event_description_len_bytes), + ) + + def __init__(self): + self.event_type = EventType.LOG + self.system_time_ns = 0 + self.event_flags = 0 + self.event_description = bytes() + + def pack(self, buffer: bytes = None, offset: int = 0, return_buffer: bool = True) -> (bytes, int): + values = dict(self.__dict__) + values['event_description_len_bytes'] = len(self.event_description) + if isinstance(self.event_description, str): + values['event_description'] = self.event_description.encode('utf-8') + packed_data = self.EventNotificationConstruct.build(values) + return PackedDataToBuffer(packed_data, buffer, offset, return_buffer) + + def unpack(self, buffer: bytes, offset: int = 0, message_version: int = MessagePayload._UNSPECIFIED_VERSION) -> int: + parsed = self.EventNotificationConstruct.parse(buffer[offset:]) + self.__dict__.update(parsed) + + # For logged FusionEngine commands/responses, the device intentionally offsets the preamble by 0x0101 from + # 0x2E31 ('.1') to 0x2F32 ('/2'). That way, the encapsulated messages within the event messages don't get + # parsed, but we can still identify them. We'll undo that offset here so the content in self.event_description + # reflects the original command/response. + if (self.event_type == EventType.COMMAND or self.event_type == EventType.COMMAND_RESPONSE) and \ + len(self.event_description) >= 2 and (self.event_description[:2] == b'/2'): + self.event_description = bytearray(self.event_description) + self.event_description[0] -= 1 + self.event_description[1] -= 1 + + return parsed._io.tell() + + def __repr__(self): + result = super().__repr__()[:-1] + result += f', type={self.event_type}, flags=0x{self.event_flags:X}' + if self.event_type == EventType.COMMAND or self.event_type == EventType.COMMAND_RESPONSE: + result += f', data={len(self.event_description)} B' + else: + result += f', description={self.event_description}' + result += ']' + return result + + def __str__(self): + return construct_message_to_string( + message=self, construct=self.EventNotificationConstruct, + title=f'Event Notification @ %s' % system_time_to_str(self.system_time_ns), + fields=['event_type', 'event_flags', 'event_description'], + value_to_string={ + 'event_flags': lambda x: '0x%016X' % x, + 'event_description': lambda x: self.event_description_to_string(), + }) + + def calcsize(self) -> int: + return len(self.pack()) + + def event_description_to_string(self, max_bytes=None): + # For commands and responses, the payload should contain the binary FusionEngine message. Try to decode the + # message type. + if self.event_type == EventType.COMMAND or self.event_type == EventType.COMMAND_RESPONSE: + if len(self.event_description) >= MessageHeader.calcsize(): + header = MessageHeader() + header.unpack(self.event_description, validate_crc=False, warn_on_unrecognized=False) + message_repr = f'[{header.message_type.to_string(include_value=True)}]' + + message_cls = MessagePayload.get_message_class(header.message_type) + if message_cls is not None: + try: + message = message_cls() + message.unpack(buffer=self.event_description, offset=header.calcsize()) + message_repr = repr(message) + except ValueError as e: + pass + else: + message_repr = '' + + return "%s\n%s" % (message_repr, + self._populate_data_byte_string(self.event_description, max_bytes=max_bytes)) + elif isinstance(self.event_description, str): + return self.event_description + else: + try: + return self.event_description.decode('utf-8') + except UnicodeDecodeError: + return repr(self.event_description) + + @classmethod + def to_numpy(cls, messages: Sequence['EventNotificationMessage']): + result = { + 'system_time': np.array([m.system_time_ns * 1e-9 for m in messages]), + 'event_type': np.array([int(m.event_type) for m in messages], dtype=int), + 'event_flags': np.array([int(m.event_flags) for m in messages], dtype=np.uint64), + } + return result + + @classmethod + def _populate_data_byte_string(cls, data: bytes, max_bytes: int = None): + data_truncated = data if max_bytes is None else data[:max_bytes] + suffix = '' if len(data_truncated) == len(data) else '...' + return f'Data ({len(data)} B): {" ".join("%02X" % b for b in data_truncated)}{suffix}' + + class SystemStatusMessage(MessagePayload): """! @brief System status message. diff --git a/src/point_one/fusion_engine/messages/control.h b/src/point_one/fusion_engine/messages/control.h index f4674459..9d5ab6c5 100644 --- a/src/point_one/fusion_engine/messages/control.h +++ b/src/point_one/fusion_engine/messages/control.h @@ -310,266 +310,6 @@ struct P1_ALIGNAS(4) ResetRequest : public MessagePayload { uint32_t reset_mask = 0; }; -/** - * @brief Software version information, (@ref - * MessageType::VERSION_INFO, version 1.0). - * @ingroup config_and_ctrl_messages - * - * This message contains version strings for each of the following, where - * available: - * - Firmware - The current version of the platform software/firmware being used - * - Engine - The version of Point One FusionEngine being used - * - OS - The version of the operating system/kernel/bootloader being used - * - GNSS Receiver - The version of firmware being used by the device's GNSS - * receiver - * - * The message payload specifies the length of each string (in bytes). It is - * followed by each of the listed version strings consecutively. The strings are - * _not_ null terminated. - * - * ``` - * {MessageHeader, VersionInfoMessage, "Firmware Version", "Engine Version", - * "OS Version", "Receiver Version"} - * ``` - */ -struct P1_ALIGNAS(4) VersionInfoMessage : public MessagePayload { - static constexpr MessageType MESSAGE_TYPE = MessageType::VERSION_INFO; - static constexpr uint8_t MESSAGE_VERSION = 0; - - /** The current system timestamp (in ns).*/ - int64_t system_time_ns = 0; - - /** The length of the firmware version string (in bytes). */ - uint8_t fw_version_length = 0; - - /** The length of the FusionEngine version string (in bytes). */ - uint8_t engine_version_length = 0; - - /** The length of the OS version string (in bytes). */ - uint8_t os_version_length = 0; - - /** The length of the GNSS receiver version string (in bytes). */ - uint8_t rx_version_length = 0; - - uint8_t reserved[4] = {0}; - - /** - * The beginning of the firmware version string. - * - * All other version strings follow immediately after this one in the data - * buffer. For example, the FusionEngine version string can be obtained as - * follows: - * ```cpp - * std::string engine_version_str( - * fw_version_str + message.fw_version_length, - * message.engine_version_length); - * ``` - */ - //char fw_version_str[0]; -}; - -/** - * @brief Identifies a FusionEngine device. - * @ingroup config_and_ctrl_messages - */ -enum class DeviceType : uint8_t { - /** Unable to map device to a defined entry. */ - UNKNOWN = 0, - /** Point One Atlas. */ - ATLAS = 1, - /** Quectel LG69T-AM system. */ - LG69T_AM = 2, - /** Quectel LG69T-AP system. */ - LG69T_AP = 3, - /** Quectel LG69T-AH system. */ - LG69T_AH = 4, - /** Nexar Beam2K system. */ - NEXAR_BEAM2K = 5, - /** Point One SSR client running on an LG69T platform. */ - SSR_LG69T = 6, - /** Point One SSR client running on a desktop platform. */ - SSR_DESKTOP = 7, -}; - -/** - * @brief Get a human-friendly string name for the specified @ref DeviceType. - * @ingroup config_and_ctrl_messages - * - * @param val The enum to get the string name for. - * - * @return The corresponding string name. - */ -P1_CONSTEXPR_FUNC const char* to_string(DeviceType val) { - switch (val) { - case DeviceType::UNKNOWN: - return "Unknown"; - case DeviceType::ATLAS: - return "ATLAS"; - case DeviceType::LG69T_AM: - return "LG69T_AM"; - case DeviceType::LG69T_AP: - return "LG69T_AP"; - case DeviceType::LG69T_AH: - return "LG69T_AH"; - case DeviceType::NEXAR_BEAM2K: - return "NEXAR_BEAM2K"; - case DeviceType::SSR_LG69T: - return "SSR_LG69T"; - case DeviceType::SSR_DESKTOP: - return "SSR_DESKTOP"; - } - return "Unrecognized"; -} - -/** - * @brief @ref DeviceType stream operator. - * @ingroup measurement_messages - */ -inline p1_ostream& operator<<(p1_ostream& stream, DeviceType val) { - stream << to_string(val) << " (" << (int)val << ")"; - return stream; -} - -/** - * @brief Device identifier information (@ref MessageType::DEVICE_ID, version - * 1.0). - * @ingroup config_and_ctrl_messages - * - * This message contains ID data for each of the following, where available: - * - HW - A unique ROM identifier pulled from the device HW (for example, a CPU - * serial number) - * - User - A value set by the user to identify a device - * - Receiver - A unique ROM identifier pulled from the GNSS receiver - * - * The message payload specifies the length of each string (in bytes). It is - * followed by each of the listed IDs consecutively. The values are _not_ null - * terminated and the way each field is populated (strings or binary) depends on - * the type of device, indicated by @ref device_type. - * - * ``` - * {MessageHeader, VersionInfoMessage, "HW ID", "User ID", "Receiver ID"} - * ``` - */ -struct P1_ALIGNAS(4) DeviceIDMessage : public MessagePayload { - static constexpr MessageType MESSAGE_TYPE = MessageType::DEVICE_ID; - static constexpr uint8_t MESSAGE_VERSION = 0; - - /** The current system timestamp (in ns).*/ - int64_t system_time_ns = 0; - - /** The type of device this message originated from.*/ - DeviceType device_type = DeviceType::UNKNOWN; - - /** The length of the HW ID (in bytes). */ - uint8_t hw_id_length = 0; - - /** The length of the user specified ID (in bytes). */ - uint8_t user_id_length = 0; - - /** The length of the GNSS receiver ID (in bytes). */ - uint8_t receiver_id_length = 0; - - uint8_t reserved[4] = {0}; - - /** - * The beginning of the hw ID data. - * - * All other ID strings follow immediately after this one in the data buffer. - * For example, the user ID can be obtained as follows: - * ```cpp - * std::vector user_id_data( - * hw_id_data + message.hw_id_length, - * hw_id_data + message.hw_id_length + message.user_id_length); - * ``` - */ - //uint8_t hw_id_data[0]; -}; - -/** - * @brief Notification of a system event for logging purposes (@ref - * MessageType::EVENT_NOTIFICATION, version 1.0). - * @ingroup config_and_ctrl_messages - */ -struct P1_ALIGNAS(4) EventNotificationMessage : public MessagePayload { - enum class EventType : uint8_t { - /** - * Event containing a logged message string from the device. - */ - LOG = 0, - /** - * Event indicating a device reset occurred. The event flags will be set to - * the requested reset bitmask, if applicable (see @ref ResetRequest). The - * payload will contain a string describing the cause of the reset. - */ - RESET = 1, - /** - * Notification that the user configuration has been changed. Intended for - * diagnostic purposes. - */ - CONFIG_CHANGE = 2, - /** - * Notification that the user performed a command (e.g., configuration - * request, fault injection enable/disable). - */ - COMMAND = 3, - /** - * Record containing the response to a user command. Response events are not - * output on the interface on which the command was received; that interface - * will receive the response itself. - */ - COMMAND_RESPONSE = 4, - }; - - static P1_CONSTEXPR_FUNC const char* to_string(EventType type) { - switch (type) { - case EventType::LOG: - return "Log"; - - case EventType::RESET: - return "Reset"; - - case EventType::CONFIG_CHANGE: - return "Config Change"; - - case EventType::COMMAND: - return "Command"; - - case EventType::COMMAND_RESPONSE: - return "Command Response"; - - default: - return "Unknown"; - } - } - - static constexpr MessageType MESSAGE_TYPE = MessageType::EVENT_NOTIFICATION; - static constexpr uint8_t MESSAGE_VERSION = 0; - - /** The type of event that occurred. */ - EventType type = EventType::LOG; - - uint8_t reserved1[3] = {0}; - - /** The current system timestamp (in ns).*/ - int64_t system_time_ns = 0; - - /** A bitmask of flags associated with the event. */ - uint64_t event_flags = 0; - - /** The number of bytes in the event description string. */ - uint16_t event_description_len_bytes = 0; - - uint8_t reserved2[2] = {0}; - - /** - * This is a dummy entry to provide a pointer to this offset. - * - * This is used for populating string describing the event, or other binary - * content where applicable. - */ - //char* event_description[0]; -}; - /** * @brief Perform a device shutdown (@ref * MessageType::SHUTDOWN_REQUEST, version 1.0). diff --git a/src/point_one/fusion_engine/messages/device.h b/src/point_one/fusion_engine/messages/device.h index f9871e98..ed519fb7 100644 --- a/src/point_one/fusion_engine/messages/device.h +++ b/src/point_one/fusion_engine/messages/device.h @@ -18,13 +18,275 @@ namespace messages { // within the definitions. See the "Message Packing" section of the README. #pragma pack(push, 1) -/** - * @defgroup device_status System Status Message Definitions - * @brief Output messages containing device-specific status information. +/**************************************************************************/ /** + * @defgroup device_status Device Status/Information Messages + * @brief Messages for indicating high-level device status (notifications, + * software version, etc.). * @ingroup messages + * @ingroup config_and_ctrl_messages + * + * See also @ref messages and @ref config_and_ctrl_messages. + ******************************************************************************/ + +/** + * @brief Software version information, (@ref + * MessageType::VERSION_INFO, version 1.0). + * @ingroup device_status + * + * This message contains version strings for each of the following, where + * available: + * - Firmware - The current version of the platform software/firmware being used + * - Engine - The version of Point One FusionEngine being used + * - OS - The version of the operating system/kernel/bootloader being used + * - GNSS Receiver - The version of firmware being used by the device's GNSS + * receiver + * + * The message payload specifies the length of each string (in bytes). It is + * followed by each of the listed version strings consecutively. The strings are + * _not_ null terminated. + * + * ``` + * {MessageHeader, VersionInfoMessage, "Firmware Version", "Engine Version", + * "OS Version", "Receiver Version"} + * ``` + */ +struct P1_ALIGNAS(4) VersionInfoMessage : public MessagePayload { + static constexpr MessageType MESSAGE_TYPE = MessageType::VERSION_INFO; + static constexpr uint8_t MESSAGE_VERSION = 0; + + /** The current system timestamp (in ns).*/ + int64_t system_time_ns = 0; + + /** The length of the firmware version string (in bytes). */ + uint8_t fw_version_length = 0; + + /** The length of the FusionEngine version string (in bytes). */ + uint8_t engine_version_length = 0; + + /** The length of the OS version string (in bytes). */ + uint8_t os_version_length = 0; + + /** The length of the GNSS receiver version string (in bytes). */ + uint8_t rx_version_length = 0; + + uint8_t reserved[4] = {0}; + + /** + * The beginning of the firmware version string. + * + * All other version strings follow immediately after this one in the data + * buffer. For example, the FusionEngine version string can be obtained as + * follows: + * ```cpp + * std::string engine_version_str( + * fw_version_str + message.fw_version_length, + * message.engine_version_length); + * ``` + */ + //char fw_version_str[0]; +}; + +/** + * @brief Identifies a FusionEngine device. + * @ingroup device_status + */ +enum class DeviceType : uint8_t { + /** Unable to map device to a defined entry. */ + UNKNOWN = 0, + /** Point One Atlas. */ + ATLAS = 1, + /** Quectel LG69T-AM system. */ + LG69T_AM = 2, + /** Quectel LG69T-AP system. */ + LG69T_AP = 3, + /** Quectel LG69T-AH system. */ + LG69T_AH = 4, + /** Nexar Beam2K system. */ + NEXAR_BEAM2K = 5, + /** Point One SSR client running on an LG69T platform. */ + SSR_LG69T = 6, + /** Point One SSR client running on a desktop platform. */ + SSR_DESKTOP = 7, +}; + +/** + * @brief Get a human-friendly string name for the specified @ref DeviceType. + * @ingroup device_status + * + * @param val The enum to get the string name for. + * + * @return The corresponding string name. + */ +P1_CONSTEXPR_FUNC const char* to_string(DeviceType val) { + switch (val) { + case DeviceType::UNKNOWN: + return "Unknown"; + case DeviceType::ATLAS: + return "ATLAS"; + case DeviceType::LG69T_AM: + return "LG69T_AM"; + case DeviceType::LG69T_AP: + return "LG69T_AP"; + case DeviceType::LG69T_AH: + return "LG69T_AH"; + case DeviceType::NEXAR_BEAM2K: + return "NEXAR_BEAM2K"; + case DeviceType::SSR_LG69T: + return "SSR_LG69T"; + case DeviceType::SSR_DESKTOP: + return "SSR_DESKTOP"; + } + return "Unrecognized"; +} + +/** + * @brief @ref DeviceType stream operator. + * @ingroup measurement_messages + */ +inline p1_ostream& operator<<(p1_ostream& stream, DeviceType val) { + stream << to_string(val) << " (" << (int)val << ")"; + return stream; +} + +/** + * @brief Device identifier information (@ref MessageType::DEVICE_ID, version + * 1.0). + * @ingroup device_status + * + * This message contains ID data for each of the following, where available: + * - HW - A unique ROM identifier pulled from the device HW (for example, a CPU + * serial number) + * - User - A value set by the user to identify a device + * - Receiver - A unique ROM identifier pulled from the GNSS receiver * - * See also @ref messages. + * The message payload specifies the length of each string (in bytes). It is + * followed by each of the listed IDs consecutively. The values are _not_ null + * terminated and the way each field is populated (strings or binary) depends on + * the type of device, indicated by @ref device_type. + * + * ``` + * {MessageHeader, VersionInfoMessage, "HW ID", "User ID", "Receiver ID"} + * ``` + */ +struct P1_ALIGNAS(4) DeviceIDMessage : public MessagePayload { + static constexpr MessageType MESSAGE_TYPE = MessageType::DEVICE_ID; + static constexpr uint8_t MESSAGE_VERSION = 0; + + /** The current system timestamp (in ns).*/ + int64_t system_time_ns = 0; + + /** The type of device this message originated from.*/ + DeviceType device_type = DeviceType::UNKNOWN; + + /** The length of the HW ID (in bytes). */ + uint8_t hw_id_length = 0; + + /** The length of the user specified ID (in bytes). */ + uint8_t user_id_length = 0; + + /** The length of the GNSS receiver ID (in bytes). */ + uint8_t receiver_id_length = 0; + + uint8_t reserved[4] = {0}; + + /** + * The beginning of the hw ID data. + * + * All other ID strings follow immediately after this one in the data buffer. + * For example, the user ID can be obtained as follows: + * ```cpp + * std::vector user_id_data( + * hw_id_data + message.hw_id_length, + * hw_id_data + message.hw_id_length + message.user_id_length); + * ``` + */ + //uint8_t hw_id_data[0]; +}; + +/** + * @brief Notification of a system event for logging purposes (@ref + * MessageType::EVENT_NOTIFICATION, version 1.0). + * @ingroup device_status */ +struct P1_ALIGNAS(4) EventNotificationMessage : public MessagePayload { + enum class EventType : uint8_t { + /** + * Event containing a logged message string from the device. + */ + LOG = 0, + /** + * Event indicating a device reset occurred. The event flags will be set to + * the requested reset bitmask, if applicable (see @ref ResetRequest). The + * payload will contain a string describing the cause of the reset. + */ + RESET = 1, + /** + * Notification that the user configuration has been changed. Intended for + * diagnostic purposes. + */ + CONFIG_CHANGE = 2, + /** + * Notification that the user performed a command (e.g., configuration + * request, fault injection enable/disable). + */ + COMMAND = 3, + /** + * Record containing the response to a user command. Response events are not + * output on the interface on which the command was received; that interface + * will receive the response itself. + */ + COMMAND_RESPONSE = 4, + }; + + static P1_CONSTEXPR_FUNC const char* to_string(EventType type) { + switch (type) { + case EventType::LOG: + return "Log"; + + case EventType::RESET: + return "Reset"; + + case EventType::CONFIG_CHANGE: + return "Config Change"; + + case EventType::COMMAND: + return "Command"; + + case EventType::COMMAND_RESPONSE: + return "Command Response"; + + default: + return "Unknown"; + } + } + + static constexpr MessageType MESSAGE_TYPE = MessageType::EVENT_NOTIFICATION; + static constexpr uint8_t MESSAGE_VERSION = 0; + + /** The type of event that occurred. */ + EventType type = EventType::LOG; + + uint8_t reserved1[3] = {0}; + + /** The current system timestamp (in ns).*/ + int64_t system_time_ns = 0; + + /** A bitmask of flags associated with the event. */ + uint64_t event_flags = 0; + + /** The number of bytes in the event description string. */ + uint16_t event_description_len_bytes = 0; + + uint8_t reserved2[2] = {0}; + + /** + * This is a dummy entry to provide a pointer to this offset. + * + * This is used for populating string describing the event, or other binary + * content where applicable. + */ + //char* event_description[0]; +}; /** * @brief System status information (@ref