diff --git a/src/infuse_iot/commands.py b/src/infuse_iot/commands.py index ed87c38..95ee41c 100644 --- a/src/infuse_iot/commands.py +++ b/src/infuse_iot/commands.py @@ -44,7 +44,8 @@ def DESCRIPTION(self) -> str: class InfuseRpcCommand: - RPC_DATA = False + RPC_DATA_SEND = False + RPC_DATA_RECEIVE = False @classmethod def add_parser(cls, parser: argparse.ArgumentParser): @@ -65,6 +66,9 @@ def data_payload(self) -> bytes: """Payload to send with RPC_DATA""" raise NotImplementedError + def data_recv_cb(self, offset: int, data: bytes) -> None: + """Data received callback""" + def data_progress_cb(self, offset: int) -> None: """Progress callback""" diff --git a/src/infuse_iot/generated/rpc_definitions.py b/src/infuse_iot/generated/rpc_definitions.py index 671dad3..0fe609f 100644 --- a/src/infuse_iot/generated/rpc_definitions.py +++ b/src/infuse_iot/generated/rpc_definitions.py @@ -437,13 +437,16 @@ class request(ctypes.LittleEndianStructure): class response(ctypes.LittleEndianStructure): _fields_ = [ + ("bytes_logged", ctypes.c_uint64), ("logical_blocks", ctypes.c_uint32), ("physical_blocks", ctypes.c_uint32), + ("boot_block", ctypes.c_uint32), ("current_block", ctypes.c_uint32), ("earliest_block", ctypes.c_uint32), ("block_size", ctypes.c_uint16), ("block_overhead", ctypes.c_uint16), ("erase_unit", ctypes.c_uint16), + ("uptime", ctypes.c_uint32), ] _pack_ = 1 diff --git a/src/infuse_iot/generated/tdf_base.py b/src/infuse_iot/generated/tdf_base.py index e2cab11..f5e478a 100644 --- a/src/infuse_iot/generated/tdf_base.py +++ b/src/infuse_iot/generated/tdf_base.py @@ -34,7 +34,7 @@ def val_fmt(self) -> str: class TdfStructBase(ctypes.LittleEndianStructure): def iter_fields( self, - field, + field: str, ) -> Generator[TdfField, None, None]: for subfield in self._fields_: sf_name = _public_name(subfield) @@ -49,13 +49,11 @@ def iter_fields( class TdfReadingBase(ctypes.LittleEndianStructure): - def iter_fields( - self, - ) -> Generator[TdfField, None, None]: + def iter_fields(self, nested_iter: bool = True) -> Generator[TdfField, None, None]: for field in self._fields_: f_name = _public_name(field) val = getattr(self, f_name) - if isinstance(val, ctypes.LittleEndianStructure): + if nested_iter and isinstance(val, ctypes.LittleEndianStructure): yield from val.iter_fields(f_name) else: if isinstance(val, ctypes.Array): diff --git a/src/infuse_iot/generated/tdf_definitions.py b/src/infuse_iot/generated/tdf_definitions.py index b8273fb..bed2cf7 100644 --- a/src/infuse_iot/generated/tdf_definitions.py +++ b/src/infuse_iot/generated/tdf_definitions.py @@ -123,6 +123,27 @@ class tdf_struct_lte_cell_id_global(TdfStructBase): "tac": "{}", } + class tdf_struct_bt_addr_le(TdfStructBase): + """Bluetooth address type (bt_addr_le_t)""" + + _fields_ = [ + ("type", ctypes.c_uint8), + ("_val", 6 * ctypes.c_uint8), + ] + _pack_ = 1 + _postfix_ = { + "type": "", + "val": "", + } + _display_fmt_ = { + "type": "{}", + "val": "0x{:012x}", + } + + @property + def val(self): + return int.from_bytes(self._val, byteorder='little') + class readings: class announce(TdfReadingBase): @@ -533,12 +554,12 @@ class ubx_nav_pvt(TdfReadingBase): "hour": "{}", "min": "{}", "sec": "{}", - "valid": "{}", + "valid": "0x{:02x}", "t_acc": "{}", "nano": "{}", "fix_type": "{}", - "flags": "{}", - "flags2": "{}", + "flags": "0x{:02x}", + "flags2": "0x{:02x}", "num_sv": "{}", "lon": "{}", "lat": "{}", @@ -554,7 +575,7 @@ class ubx_nav_pvt(TdfReadingBase): "s_acc": "{}", "head_acc": "{}", "p_dop": "{}", - "flags3": "{}", + "flags3": "0x{:04x}", "reserved0": "{}", "head_veh": "{}", "mag_dec": "{}", @@ -786,6 +807,60 @@ class gnss_fix_info(TdfReadingBase): "num_sv": "{}", } + class bluetooth_connection(TdfReadingBase): + """Bluetooth connection state change""" + + name = "BLUETOOTH_CONNECTION" + _fields_ = [ + ("address", structs.tdf_struct_bt_addr_le), + ("connected", ctypes.c_uint8), + ] + _pack_ = 1 + _postfix_ = { + "address": "", + "connected": "", + } + _display_fmt_ = { + "address": "{}", + "connected": "{}", + } + + class bluetooth_rssi(TdfReadingBase): + """Received signal strength of Bluetooth device""" + + name = "BLUETOOTH_RSSI" + _fields_ = [ + ("address", structs.tdf_struct_bt_addr_le), + ("rssi", ctypes.c_int8), + ] + _pack_ = 1 + _postfix_ = { + "address": "", + "rssi": "dBm", + } + _display_fmt_ = { + "address": "{}", + "rssi": "{}", + } + + class bluetooth_data_throughput(TdfReadingBase): + """Data throughput of Bluetooth link""" + + name = "BLUETOOTH_DATA_THROUGHPUT" + _fields_ = [ + ("address", structs.tdf_struct_bt_addr_le), + ("throughput", ctypes.c_int32), + ] + _pack_ = 1 + _postfix_ = { + "address": "", + "throughput": "B/sec", + } + _display_fmt_ = { + "address": "{}", + "throughput": "{}", + } + class array_type(TdfReadingBase): """Example array type""" @@ -828,5 +903,8 @@ class array_type(TdfReadingBase): 26: readings.runtime_error, 27: readings.charger_en_control, 28: readings.gnss_fix_info, + 29: readings.bluetooth_connection, + 30: readings.bluetooth_rssi, + 31: readings.bluetooth_data_throughput, 100: readings.array_type, } diff --git a/src/infuse_iot/rpc_wrappers/application_info.py b/src/infuse_iot/rpc_wrappers/application_info.py index 74bef6d..5b9fa45 100644 --- a/src/infuse_iot/rpc_wrappers/application_info.py +++ b/src/infuse_iot/rpc_wrappers/application_info.py @@ -1,5 +1,7 @@ #!/usr/bin/env python3 +import os + import infuse_iot.generated.rpc_definitions as defs from infuse_iot.commands import InfuseRpcCommand @@ -17,7 +19,7 @@ def request_struct(self): def handle_response(self, return_code, response): if return_code != 0: - print(f"Failed to query current time ({return_code})") + print(f"Failed to query current time ({os.strerror(-return_code)})") return r = response diff --git a/src/infuse_iot/rpc_wrappers/bt_connect_infuse.py b/src/infuse_iot/rpc_wrappers/bt_connect_infuse.py index d990718..1a6f808 100644 --- a/src/infuse_iot/rpc_wrappers/bt_connect_infuse.py +++ b/src/infuse_iot/rpc_wrappers/bt_connect_infuse.py @@ -1,5 +1,7 @@ #!/usr/bin/env python3 +import os + import infuse_iot.generated.rpc_definitions as defs from infuse_iot.commands import InfuseRpcCommand from infuse_iot.generated.rpc_definitions import ( @@ -57,7 +59,7 @@ def request_struct(self) -> defs.bt_connect_infuse.request: def handle_response(self, return_code, response): if return_code < 0: - print(f"Failed to connect ({return_code})") + print(f"Failed to connect ({os.strerror(-return_code)})") return if return_code == 1: diff --git a/src/infuse_iot/rpc_wrappers/bt_disconnect.py b/src/infuse_iot/rpc_wrappers/bt_disconnect.py index 1f6926d..7afe345 100644 --- a/src/infuse_iot/rpc_wrappers/bt_disconnect.py +++ b/src/infuse_iot/rpc_wrappers/bt_disconnect.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 +import os import infuse_iot.generated.rpc_definitions as defs from infuse_iot.commands import InfuseRpcCommand @@ -39,7 +40,7 @@ def request_struct(self): def handle_response(self, return_code, response): if return_code != 0: - print(f"Failed to disconnect ({return_code})") + print(f"Failed to disconnect ({os.strerror(-return_code)})") return else: print("Disconnected") diff --git a/src/infuse_iot/rpc_wrappers/coap_download.py b/src/infuse_iot/rpc_wrappers/coap_download.py index 3752a00..01066a7 100644 --- a/src/infuse_iot/rpc_wrappers/coap_download.py +++ b/src/infuse_iot/rpc_wrappers/coap_download.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import ctypes +import os import infuse_iot.generated.rpc_definitions as defs from infuse_iot.commands import InfuseRpcCommand @@ -85,7 +86,7 @@ class request(ctypes.LittleEndianStructure): def handle_response(self, return_code, response): if return_code != 0: - print(f"Failed to download file ({return_code})") + print(f"Failed to download file ({os.strerror(-return_code)})") return else: print("File downloaded") diff --git a/src/infuse_iot/rpc_wrappers/data_logger_state.py b/src/infuse_iot/rpc_wrappers/data_logger_state.py new file mode 100644 index 0000000..ec65431 --- /dev/null +++ b/src/infuse_iot/rpc_wrappers/data_logger_state.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 + +import os + +import infuse_iot.generated.rpc_definitions as defs +from infuse_iot.commands import InfuseRpcCommand + + +class data_logger_state(InfuseRpcCommand, defs.data_logger_state): + @classmethod + def add_parser(cls, parser): + logger = parser.add_mutually_exclusive_group(required=True) + logger.add_argument("--onboard", action="store_true", help="Onboard flash logger") + logger.add_argument("--removable", action="store_true", help="Removable flash logger (SD)") + + def __init__(self, args): + if args.onboard: + self.logger = defs.rpc_enum_data_logger.FLASH_ONBOARD + elif args.removable: + self.logger = defs.rpc_enum_data_logger.FLASH_REMOVABLE + else: + raise NotImplementedError + + def request_struct(self): + return self.request(self.logger) + + def handle_response(self, return_code, response): + if return_code != 0: + print(f"Failed to query current time ({os.strerror(-return_code)})") + return + + def sizeof_fmt(num, suffix="B"): + for unit in ("", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"): + if abs(num) < 1024.0: + return f"{num:3.2f} {unit}{suffix}" + num /= 1024.0 + return f"{num:.2f} Yi{suffix}" + + r = response + total_logged = r.current_block * r.block_size + percent = 100 * r.current_block / r.logical_blocks + block_rate = (r.current_block - r.boot_block) / r.uptime + byte_rate = r.bytes_logged / r.uptime + + print(f"{self.logger.name}") + print(f"\t Logged: {sizeof_fmt(total_logged)}") + print(f"\t Blocks: {r.current_block}/{r.logical_blocks} ({percent:.0f}%)") + print(f"\t Block Rate: {block_rate:.2f} blocks/sec") + print(f"\t Byte Rate: {sizeof_fmt(byte_rate)}/sec") diff --git a/src/infuse_iot/rpc_wrappers/fault.py b/src/infuse_iot/rpc_wrappers/fault.py index f201d89..f3b78d0 100644 --- a/src/infuse_iot/rpc_wrappers/fault.py +++ b/src/infuse_iot/rpc_wrappers/fault.py @@ -1,5 +1,7 @@ #!/usr/bin/env python3 +import os + import infuse_iot.generated.rpc_definitions as defs from infuse_iot.commands import InfuseRpcCommand @@ -65,4 +67,4 @@ def request_struct(self): return self.request(self._fault_type, 0) def handle_response(self, return_code, _): - print(f"Failed to trigger exception ({return_code})") + print(f"Failed to trigger exception ({os.strerror(-return_code)})") diff --git a/src/infuse_iot/rpc_wrappers/file_write_basic.py b/src/infuse_iot/rpc_wrappers/file_write_basic.py index f6eb557..691fce5 100644 --- a/src/infuse_iot/rpc_wrappers/file_write_basic.py +++ b/src/infuse_iot/rpc_wrappers/file_write_basic.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import binascii +import os from rich.progress import ( DownloadColumn, @@ -14,7 +15,7 @@ class file_write_basic(InfuseRpcCommand, defs.file_write_basic): - RPC_DATA = True + RPC_DATA_SEND = True @classmethod def add_parser(cls, parser): @@ -95,7 +96,7 @@ def handle_response(self, return_code, response): self.progress.stop() if return_code != 0: - print(f"Failed to write file ({return_code})") + print(f"Failed to write file ({os.strerror(-return_code)})") return print("File written") print(f"\tLength: {response.recv_len}") diff --git a/src/infuse_iot/rpc_wrappers/kv_bt_peer.py b/src/infuse_iot/rpc_wrappers/kv_bt_peer.py new file mode 100644 index 0000000..9d14b2c --- /dev/null +++ b/src/infuse_iot/rpc_wrappers/kv_bt_peer.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 + +import ctypes +import os + +import infuse_iot.generated.rpc_definitions as defs +from infuse_iot.commands import InfuseRpcCommand +from infuse_iot.util.argparse import BtLeAddress +from infuse_iot.util.ctypes import bytes_to_uint8 + + +class kv_bt_peer(InfuseRpcCommand, defs.kv_write): + HELP = "Configure the peer Bluetooth address" + DESCRIPTION = "Configure the peer Bluetooth address" + + class request(ctypes.LittleEndianStructure): + _fields_ = [ + ("num", ctypes.c_uint8), + ] + _pack_ = 1 + + class response(InfuseRpcCommand.VariableSizeResponse): + base_fields = [] + var_name = "rc" + var_type = ctypes.c_int16 + + @staticmethod + def kv_store_value_factory(id, value_bytes): + class kv_store_value(ctypes.LittleEndianStructure): + _fields_ = [ + ("id", ctypes.c_uint16), + ("len", ctypes.c_uint16), + ("data", ctypes.c_ubyte * len(value_bytes)), + ] + _pack_ = 1 + + return kv_store_value(id, len(value_bytes), bytes_to_uint8(value_bytes)) + + @classmethod + def add_parser(cls, parser): + addr_group = parser.add_mutually_exclusive_group(required=True) + addr_group.add_argument("--public", type=BtLeAddress, help="Public Bluetooth address") + addr_group.add_argument("--random", type=BtLeAddress, help="Random Bluetooth address") + addr_group.add_argument("--delete", action="store_true", help="Delete peer device") + + def __init__(self, args): + if args.public: + addr = bytes(BtLeAddress.to_ctype(defs.rpc_enum_bt_le_addr_type.PUBLIC, args.public)) + elif args.random: + addr = bytes(BtLeAddress.to_ctype(defs.rpc_enum_bt_le_addr_type.RANDOM, args.random)) + elif args.delete: + addr = b"" + else: + raise NotImplementedError + self.addr = addr + + def request_struct(self): + addr_struct = self.kv_store_value_factory(50, self.addr) + request_bytes = bytes(addr_struct) + return bytes(self.request(1)) + request_bytes + + def handle_response(self, return_code, response): + if return_code != 0: + print(f"Invalid data buffer ({os.strerror(-return_code)})") + return + + def print_status(name, rc): + if rc < 0: + print(f"{name} failed to write ({os.strerror(-rc)})") + elif rc == 0: + print(f"{name} already matched") + else: + print(f"{name} updated") + + print_status("Peer address", response.rc[0]) diff --git a/src/infuse_iot/rpc_wrappers/kv_read.py b/src/infuse_iot/rpc_wrappers/kv_read.py index 87c0d6a..4aa7d8f 100644 --- a/src/infuse_iot/rpc_wrappers/kv_read.py +++ b/src/infuse_iot/rpc_wrappers/kv_read.py @@ -2,6 +2,7 @@ import ctypes import errno +import os import infuse_iot.generated.rpc_definitions as defs from infuse_iot.commands import InfuseRpcCommand @@ -59,7 +60,7 @@ def request_struct(self): def handle_response(self, return_code, response): if return_code != 0: - print(f"Invalid data buffer ({errno.errorcode[-return_code]})") + print(f"Invalid data buffer ({os.strerror(-return_code)})") return for r in response: diff --git a/src/infuse_iot/rpc_wrappers/last_reboot.py b/src/infuse_iot/rpc_wrappers/last_reboot.py index b66e1a6..e38525b 100644 --- a/src/infuse_iot/rpc_wrappers/last_reboot.py +++ b/src/infuse_iot/rpc_wrappers/last_reboot.py @@ -1,5 +1,7 @@ #!/usr/bin/env python3 +import os + import infuse_iot.generated.rpc_definitions as defs from infuse_iot.commands import InfuseRpcCommand @@ -17,7 +19,7 @@ def request_struct(self): def handle_response(self, return_code, response): if return_code != 0: - print(f"Failed to query reboot info ({return_code})") + print(f"Failed to query reboot info ({os.strerror(-return_code)})") return from infuse_iot.time import InfuseTime, InfuseTimeSource diff --git a/src/infuse_iot/rpc_wrappers/lte_at_cmd.py b/src/infuse_iot/rpc_wrappers/lte_at_cmd.py index b8df6ce..95d034b 100644 --- a/src/infuse_iot/rpc_wrappers/lte_at_cmd.py +++ b/src/infuse_iot/rpc_wrappers/lte_at_cmd.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 import ctypes -import errno +import os import infuse_iot.generated.rpc_definitions as defs from infuse_iot.commands import InfuseRpcCommand @@ -27,7 +27,7 @@ def request_struct(self): def handle_response(self, return_code, response): if return_code != 0: - print(f"Failed to run command ({errno.errorcode[-return_code]})") + print(f"Failed to run command ({os.strerror(-return_code)})") return decoded = response.rsp.decode("utf-8").strip() diff --git a/src/infuse_iot/rpc_wrappers/lte_modem_info.py b/src/infuse_iot/rpc_wrappers/lte_modem_info.py index a3a7482..f66023c 100644 --- a/src/infuse_iot/rpc_wrappers/lte_modem_info.py +++ b/src/infuse_iot/rpc_wrappers/lte_modem_info.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import ctypes +import os import infuse_iot.generated.rpc_definitions as defs from infuse_iot.commands import InfuseRpcCommand @@ -31,7 +32,7 @@ def request_struct(self): def handle_response(self, return_code, response): if return_code != 0: - print(f"Failed to query modem info ({return_code})") + print(f"Failed to query modem info ({os.strerror(-return_code)})") return unknown = b"_unknown" diff --git a/src/infuse_iot/rpc_wrappers/lte_pdp_ctx.py b/src/infuse_iot/rpc_wrappers/lte_pdp_ctx.py index f50e07f..a3f5d55 100644 --- a/src/infuse_iot/rpc_wrappers/lte_pdp_ctx.py +++ b/src/infuse_iot/rpc_wrappers/lte_pdp_ctx.py @@ -2,14 +2,15 @@ import ctypes import enum +import os import infuse_iot.generated.rpc_definitions as defs from infuse_iot.commands import InfuseRpcCommand class lte_pdp_ctx(InfuseRpcCommand, defs.kv_write): - HELP = "Set the WiFi network SSID and PSK" - DESCRIPTION = "Set the WiFi network SSID and PSK" + HELP = "Set the LTE PDP context (IP family & APN)" + DESCRIPTION = "Set the LTE PDP context (IP family & APN)" class request(ctypes.LittleEndianStructure): _fields_ = [ @@ -59,7 +60,7 @@ def request_struct(self): def handle_response(self, return_code, response): if return_code != 0: - print(f"Invalid data buffer ({return_code})") + print(f"Invalid data buffer ({os.strerror(-return_code)})") return def print_status(name, rc): diff --git a/src/infuse_iot/rpc_wrappers/lte_state.py b/src/infuse_iot/rpc_wrappers/lte_state.py index df0cf6a..c0d2db6 100644 --- a/src/infuse_iot/rpc_wrappers/lte_state.py +++ b/src/infuse_iot/rpc_wrappers/lte_state.py @@ -2,6 +2,7 @@ import ctypes import ipaddress +import os import infuse_iot.generated.rpc_definitions as defs from infuse_iot.commands import InfuseRpcCommand @@ -81,7 +82,7 @@ def request_struct(self): def handle_response(self, return_code, response): if return_code != 0: - print(f"Failed to query current time ({return_code})") + print(f"Failed to query current time ({os.strerror(-return_code)})") return common = response.common diff --git a/src/infuse_iot/rpc_wrappers/reboot.py b/src/infuse_iot/rpc_wrappers/reboot.py index d003183..a54bfe3 100644 --- a/src/infuse_iot/rpc_wrappers/reboot.py +++ b/src/infuse_iot/rpc_wrappers/reboot.py @@ -1,5 +1,7 @@ #!/usr/bin/env python3 +import os + import infuse_iot.generated.rpc_definitions as defs from infuse_iot.commands import InfuseRpcCommand @@ -19,4 +21,4 @@ def handle_response(self, return_code, response): if return_code == 0: print(f"Rebooting in {response.delay_ms} ms") else: - print(f"Failed to trigger reboot ({return_code})") + print(f"Failed to trigger reboot ({os.strerror(-return_code)})") diff --git a/src/infuse_iot/rpc_wrappers/security_state.py b/src/infuse_iot/rpc_wrappers/security_state.py index c770e39..137ff32 100644 --- a/src/infuse_iot/rpc_wrappers/security_state.py +++ b/src/infuse_iot/rpc_wrappers/security_state.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import ctypes +import os import random from cryptography.hazmat.primitives import hashes, serialization @@ -83,7 +84,7 @@ def _decrypt_response(self, response): def handle_response(self, return_code, response): if return_code != 0: - print(f"Failed to query current time ({return_code})") + print(f"Failed to query current time ({os.strerror(-return_code)})") return # Decrypt identity information diff --git a/src/infuse_iot/rpc_wrappers/time_get.py b/src/infuse_iot/rpc_wrappers/time_get.py index ae75420..1f5b4bf 100644 --- a/src/infuse_iot/rpc_wrappers/time_get.py +++ b/src/infuse_iot/rpc_wrappers/time_get.py @@ -1,5 +1,7 @@ #!/usr/bin/env python3 +import os + import infuse_iot.generated.rpc_definitions as defs from infuse_iot.commands import InfuseRpcCommand @@ -17,7 +19,7 @@ def request_struct(self): def handle_response(self, return_code, response): if return_code != 0: - print(f"Failed to query current time ({return_code})") + print(f"Failed to query current time ({os.strerror(-return_code)})") return import time diff --git a/src/infuse_iot/rpc_wrappers/time_set.py b/src/infuse_iot/rpc_wrappers/time_set.py index 78b8ac5..c5e3532 100644 --- a/src/infuse_iot/rpc_wrappers/time_set.py +++ b/src/infuse_iot/rpc_wrappers/time_set.py @@ -1,5 +1,7 @@ #!/usr/bin/env python3 +import os + import infuse_iot.generated.rpc_definitions as defs from infuse_iot.commands import InfuseRpcCommand @@ -21,7 +23,7 @@ def request_struct(self): def handle_response(self, return_code, response): if return_code != 0: - print(f"Failed to set current time ({return_code})") + print(f"Failed to set current time ({os.strerror(-return_code)})") return else: print("Set current time on device") diff --git a/src/infuse_iot/rpc_wrappers/wifi_configure.py b/src/infuse_iot/rpc_wrappers/wifi_configure.py index 8f45ee6..e3cad2f 100644 --- a/src/infuse_iot/rpc_wrappers/wifi_configure.py +++ b/src/infuse_iot/rpc_wrappers/wifi_configure.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import ctypes +import os import infuse_iot.generated.rpc_definitions as defs from infuse_iot.commands import InfuseRpcCommand @@ -53,7 +54,7 @@ def request_struct(self): def handle_response(self, return_code, response): if return_code != 0: - print(f"Invalid data buffer ({return_code})") + print(f"Invalid data buffer ({os.strerror(-return_code)})") return def print_status(name, rc): diff --git a/src/infuse_iot/rpc_wrappers/wifi_scan.py b/src/infuse_iot/rpc_wrappers/wifi_scan.py index 4fafce4..ec2e664 100644 --- a/src/infuse_iot/rpc_wrappers/wifi_scan.py +++ b/src/infuse_iot/rpc_wrappers/wifi_scan.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import ctypes +import os import tabulate @@ -59,7 +60,7 @@ def request_struct(self): def handle_response(self, return_code, response): if return_code != 0: - print(f"Failed to query current time ({return_code})") + print(f"Failed to query current time ({os.strerror(-return_code)})") return table = [] diff --git a/src/infuse_iot/rpc_wrappers/wifi_state.py b/src/infuse_iot/rpc_wrappers/wifi_state.py index f8a7333..9420193 100644 --- a/src/infuse_iot/rpc_wrappers/wifi_state.py +++ b/src/infuse_iot/rpc_wrappers/wifi_state.py @@ -2,6 +2,7 @@ import ctypes import ipaddress +import os import infuse_iot.generated.rpc_definitions as defs from infuse_iot.commands import InfuseRpcCommand @@ -90,7 +91,7 @@ def request_struct(self): def handle_response(self, return_code, response): if return_code != 0: - print(f"Failed to query current time ({return_code})") + print(f"Failed to query current time ({os.strerror(-return_code)})") return common = response.common diff --git a/src/infuse_iot/rpc_wrappers/zbus_channel_state.py b/src/infuse_iot/rpc_wrappers/zbus_channel_state.py index 3c96bba..14565e3 100644 --- a/src/infuse_iot/rpc_wrappers/zbus_channel_state.py +++ b/src/infuse_iot/rpc_wrappers/zbus_channel_state.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import ctypes +import os import tabulate @@ -27,6 +28,10 @@ class AmbeintEnvChannel(ctypes.LittleEndianStructure): id = 0x43210001 data = defs.readings.ambient_temp_pres_hum + class LocationChannel(ctypes.LittleEndianStructure): + id = 0x43210004 + data = defs.readings.gcs_wgs84_llha + @classmethod def add_parser(cls, parser): group = parser.add_mutually_exclusive_group(required=True) @@ -44,6 +49,13 @@ def add_parser(cls, parser): const=cls.AmbeintEnvChannel, help="Ambient environmental channel", ) + group.add_argument( + "--location", + dest="channel", + action="store_const", + const=cls.LocationChannel, + help="Location channel", + ) def __init__(self, args): self._channel = args.channel @@ -53,7 +65,7 @@ def request_struct(self): def handle_response(self, return_code, response): if return_code != 0: - print(f"Failed to query channel ({return_code})") + print(f"Failed to query channel ({os.strerror(-return_code)})") return from infuse_iot.time import InfuseTime diff --git a/src/infuse_iot/time.py b/src/infuse_iot/time.py index 3499ad6..8f78bb1 100644 --- a/src/infuse_iot/time.py +++ b/src/infuse_iot/time.py @@ -4,24 +4,21 @@ import enum -class InfuseTimeSource(enum.IntEnum): - NONE = 0 - GNSS = 1 - NTP = 2 - RPC = 3 - RECOVERED = 0x80 +class InfuseTimeSource: + class base(enum.IntEnum): + NONE = 0 + GNSS = 1 + NTP = 2 + RPC = 3 + + def __init__(self, value: int): + self.recovered: bool = value & 0x80 != 0 + self.source = self.base(value & 0x7F) def __str__(self) -> str: - postfix = "" - v = self.value - if v & self.RECOVERED: - postfix = " (recovered after reboot)" - v ^= self.RECOVERED - flags = InfuseTimeSource(v) - if flags.name: - return flags.name + postfix - else: - return "Unknown" + postfix + postfix = " (recovered after reboot)" if self.recovered else "" + base = self.source.name if self.source.name is not None else "Unknown" + return base + postfix class InfuseTime: diff --git a/src/infuse_iot/tools/localhost.py b/src/infuse_iot/tools/localhost.py index 57077b3..a016cdc 100644 --- a/src/infuse_iot/tools/localhost.py +++ b/src/infuse_iot/tools/localhost.py @@ -18,6 +18,8 @@ import infuse_iot.epacket.interface as interface from infuse_iot.commands import InfuseCommand from infuse_iot.common import InfuseType +from infuse_iot.generated.tdf_base import TdfStructBase +from infuse_iot.generated.tdf_definitions import structs from infuse_iot.socket_comms import ( ClientNotificationEpacketReceived, LocalClient, @@ -94,7 +96,13 @@ async def websocket_handler(self, request: BaseRequest): ], } ] - for tdf_name in sorted(self._columns): + # Put ANNOUNCE TDF first, if it exists + if "ANNOUNCE" in self._columns: + sorted_tdfs = ["ANNOUNCE"] + [v for v in sorted(self._columns) if v != "ANNOUNCE"] + else: + sorted_tdfs = sorted(self._columns) + + for tdf_name in sorted_tdfs: columns.append( { "title": tdf_name, @@ -131,7 +139,15 @@ def column_title(struct, name): return name for field in tdf.field_information(): - if "subfields" in field: + if field["type"] == structs.tdf_struct_mcuboot_img_sem_ver: + # Special case version struct to make reading versions easier + s = { + "title": column_title(tdf, field["name"]), + "field": f"{tdf.name}.{field['name']}", + "headerVertical": "flip", + "hozAlign": "right", + } + elif "subfields" in field: sub: list[dict[str, Any]] = [] for subfield in field["subfields"]: sub.append( @@ -184,11 +200,16 @@ def recv_thread(self): if t.name not in self._data[source.infuse_id]: self._data[source.infuse_id][t.name] = {} - for field in t.iter_fields(): - if field.subfield: - if field.field not in self._data[source.infuse_id][t.name]: - self._data[source.infuse_id][t.name][field.field] = {} - self._data[source.infuse_id][t.name][field.field][field.subfield] = field.val_fmt() + for field in t.iter_fields(nested_iter=False): + if isinstance(field.val, structs.tdf_struct_mcuboot_img_sem_ver): + # Special case version struct to make reading versions easier + val = f"{field.val.major}.{field.val.minor}.{field.val.revision}+{field.val.build_num:08x}" + self._data[source.infuse_id][t.name][field.field] = val + elif isinstance(field.val, TdfStructBase): + for s in field.val.iter_fields(field.field): + if s.field not in self._data[source.infuse_id][t.name]: + self._data[source.infuse_id][t.name][s.field] = {} + self._data[source.infuse_id][t.name][s.field][s.subfield] = s.val_fmt() else: self._data[source.infuse_id][t.name][field.field] = field.val_fmt() self._data_lock.release() diff --git a/src/infuse_iot/tools/localhost/index.html b/src/infuse_iot/tools/localhost/index.html index 57cc849..4174098 100644 --- a/src/infuse_iot/tools/localhost/index.html +++ b/src/infuse_iot/tools/localhost/index.html @@ -52,6 +52,7 @@