From ca4c18d71af58270664a931f924901c329802436 Mon Sep 17 00:00:00 2001 From: Jordan Yates Date: Tue, 17 Jun 2025 17:57:10 +1000 Subject: [PATCH 1/5] generated: regenerate definitions Regenerate base definitions. Signed-off-by: Jordan Yates --- src/infuse_iot/generated/kv_definitions.py | 17 +++++++++ src/infuse_iot/generated/rpc_definitions.py | 29 ++++++++++++++++ src/infuse_iot/generated/tdf_definitions.py | 38 +++++++++++++++++++++ 3 files changed, 84 insertions(+) diff --git a/src/infuse_iot/generated/kv_definitions.py b/src/infuse_iot/generated/kv_definitions.py index bfb345c..22c7b33 100644 --- a/src/infuse_iot/generated/kv_definitions.py +++ b/src/infuse_iot/generated/kv_definitions.py @@ -266,6 +266,22 @@ class bluetooth_peer(VLACompatLittleEndianStruct): ] _pack_ = 1 + class lora_config(VLACompatLittleEndianStruct): + """LoRa modem configuration""" + + NAME = "LORA_CONFIG" + BASE_ID = 51 + RANGE = 1 + _fields_ = [ + ("frequency", ctypes.c_uint32), + ("bandwidth", ctypes.c_uint8), + ("spreading_factor", ctypes.c_uint8), + ("coding_rate", ctypes.c_uint8), + ("preamble_len", ctypes.c_uint16), + ("tx_power", ctypes.c_int8), + ] + _pack_ = 1 + class gravity_reference(VLACompatLittleEndianStruct): """Reference gravity vector for tilt calculations""" @@ -352,6 +368,7 @@ class secure_storage_reserved(VLACompatLittleEndianStruct): 45: lte_pdp_config, 46: lte_networking_modes, 50: bluetooth_peer, + 51: lora_config, 60: gravity_reference, 100: geofence, 101: geofence, diff --git a/src/infuse_iot/generated/rpc_definitions.py b/src/infuse_iot/generated/rpc_definitions.py index ed49c0f..0f25d24 100644 --- a/src/infuse_iot/generated/rpc_definitions.py +++ b/src/infuse_iot/generated/rpc_definitions.py @@ -173,6 +173,18 @@ class rpc_struct_sockaddr(VLACompatLittleEndianStruct): _pack_ = 1 +class rpc_struct_heap_info(VLACompatLittleEndianStruct): + """struct k_heap information""" + + _fields_ = [ + ("addr", ctypes.c_uint32), + ("free_bytes", ctypes.c_uint32), + ("allocated_bytes", ctypes.c_uint32), + ("max_allocated_bytes", ctypes.c_uint32), + ] + _pack_ = 1 + + class rpc_enum_bt_le_addr_type(enum.IntEnum): """Bluetooth LE address type""" @@ -599,6 +611,23 @@ class response(VLACompatLittleEndianStruct): _pack_ = 1 +class heap_stats: + """Query stats of heaps""" + + HELP = "Query stats of heaps" + DESCRIPTION = "Query stats of heaps" + COMMAND_ID = 19 + + class request(VLACompatLittleEndianStruct): + _fields_ = [] + _pack_ = 1 + + class response(VLACompatLittleEndianStruct): + _fields_ = [] + vla_field = ("stats", 0 * rpc_struct_heap_info) + _pack_ = 1 + + class lte_at_cmd: """Run AT command against LTE modem""" diff --git a/src/infuse_iot/generated/tdf_definitions.py b/src/infuse_iot/generated/tdf_definitions.py index a94e9d6..3a4951f 100644 --- a/src/infuse_iot/generated/tdf_definitions.py +++ b/src/infuse_iot/generated/tdf_definitions.py @@ -1262,6 +1262,42 @@ class annotation(TdfReadingBase): "event": "{}", } + class lora_rx(TdfReadingBase): + """Received LoRa packet""" + + name = "LORA_RX" + _fields_ = [ + ("snr", ctypes.c_int8), + ("rssi", ctypes.c_int16), + ("payload", 0 * ctypes.c_uint8), + ] + _pack_ = 1 + _postfix_ = { + "snr": "", + "rssi": "", + "payload": "", + } + _display_fmt_ = { + "snr": "{}", + "rssi": "{}", + "payload": "{}", + } + + class lora_tx(TdfReadingBase): + """Transmitted LoRa packet""" + + name = "LORA_TX" + _fields_ = [ + ("payload", 0 * ctypes.c_uint8), + ] + _pack_ = 1 + _postfix_ = { + "payload": "", + } + _display_fmt_ = { + "payload": "{}", + } + class array_type(TdfReadingBase): """Example array type""" @@ -1319,5 +1355,7 @@ class array_type(TdfReadingBase): 41: readings.adc_raw_16, 42: readings.adc_raw_32, 43: readings.annotation, + 44: readings.lora_rx, + 45: readings.lora_tx, 100: readings.array_type, } From 523ba6d04b0fd3c6600ffe2657ddac5481119977 Mon Sep 17 00:00:00 2001 From: Jordan Yates Date: Tue, 17 Jun 2025 17:57:36 +1000 Subject: [PATCH 2/5] tools: bt_log: handle TDFs with no time Fix crashes when `bt_log` encounters a TDF with no time. Signed-off-by: Jordan Yates --- src/infuse_iot/tools/bt_log.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/infuse_iot/tools/bt_log.py b/src/infuse_iot/tools/bt_log.py index 5de7d66..b0c15c8 100644 --- a/src/infuse_iot/tools/bt_log.py +++ b/src/infuse_iot/tools/bt_log.py @@ -60,10 +60,11 @@ def run(self): if evt.epacket.ptype == InfuseType.TDF: for tdf in self._decoder.decode(evt.epacket.payload): t = tdf.data[-1] + t_str = f"{tdf.time:.3f}" if tdf.time else "N/A" if len(tdf.data) > 1: - print(f"{tdf.time:.3f} TDF: {t.name}[{len(tdf.data)}]") + print(f"{t_str} TDF: {t.name}[{len(tdf.data)}]") else: - print(f"{tdf.time:.3f} TDF: {t.name}") + print(f"{t_str} TDF: {t.name}") except KeyboardInterrupt: print(f"Disconnecting from {self._id:016x}") From e0bf9eadd9b57e2a71027d6ab6b486e5bbb4d75f Mon Sep 17 00:00:00 2001 From: Jordan Yates Date: Tue, 17 Jun 2025 17:58:38 +1000 Subject: [PATCH 3/5] tools: gateway: fix premature disconnects Update the `gateway` script to fix disconnecting a Bluetooth connection if 2 tools are using the connection and 1 of them requests a disconnect. Signed-off-by: Jordan Yates --- src/infuse_iot/tools/gateway.py | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/infuse_iot/tools/gateway.py b/src/infuse_iot/tools/gateway.py index 7c4d81a..69f2cad 100644 --- a/src/infuse_iot/tools/gateway.py +++ b/src/infuse_iot/tools/gateway.py @@ -241,6 +241,7 @@ def __init__( common: CommonThreadState, ): self._common = common + self._connected: dict[int, int] = {} self._queue: queue.Queue = queue.Queue() super().__init__(self._iter) @@ -297,6 +298,10 @@ def _bt_connect_cb(self, pkt: PacketReceived, rc: int, response: bytes): if rc < 0: rsp = ClientNotificationConnectionFailed(infuse_id) else: + if infuse_id in self._connected: + self._connected[infuse_id] += 1 + else: + self._connected[infuse_id] = 1 rsp = ClientNotificationConnectionCreated(infuse_id, 244 - ctypes.sizeof(CtypeBtGattFrame) - 16) self._common.server.broadcast(rsp) @@ -314,12 +319,17 @@ def _handle_conn_request(self, req: GatewayRequestConnectionRequest): return subs = 0 + bt_char = defs.rpc_enum_infuse_bt_characteristic if req.data_types & req.DataType.COMMAND: - subs |= defs.rpc_enum_infuse_bt_characteristic.COMMAND + subs |= bt_char.COMMAND if req.data_types & req.DataType.DATA: - subs |= defs.rpc_enum_infuse_bt_characteristic.DATA + subs |= bt_char.DATA if req.data_types & req.DataType.LOGGING: - subs |= defs.rpc_enum_infuse_bt_characteristic.LOGGING + subs |= bt_char.LOGGING + + # Multiple connection users, subscribe all + if req.infuse_id in self._connected: + subs = bt_char.COMMAND | bt_char.DATA | bt_char.LOGGING connect_args = defs.bt_connect_infuse.request( state.bt_addr.to_rpc_struct(), @@ -347,6 +357,14 @@ def _handle_conn_release(self, req: GatewayRequestConnectionRelease): # Unknown device, nothing to do return + if req.infuse_id in self._connected: + # Decrement reference count + self._connected[req.infuse_id] -= 1 + if self._connected[req.infuse_id] > 0: + # Someone is still using the connection + return + self._connected.pop(req.infuse_id) + disconnect_args = defs.bt_disconnect.request(state.bt_addr.to_rpc_struct()) cmd = self._common.rpc.generate(defs.bt_disconnect.COMMAND_ID, bytes(disconnect_args), Auth.DEVICE, None) encrypted = cmd.to_serial(self._common.ddb) From f06f88f943e4683121812a244b9e3aef97e3b8e2 Mon Sep 17 00:00:00 2001 From: Jordan Yates Date: Tue, 17 Jun 2025 17:58:07 +1000 Subject: [PATCH 4/5] rpc_wrappers: data_logger_state: display earliest block If the earliest block is not 0, display the value. Signed-off-by: Jordan Yates --- src/infuse_iot/rpc_wrappers/data_logger_state.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/infuse_iot/rpc_wrappers/data_logger_state.py b/src/infuse_iot/rpc_wrappers/data_logger_state.py index 7c26124..9f302ba 100644 --- a/src/infuse_iot/rpc_wrappers/data_logger_state.py +++ b/src/infuse_iot/rpc_wrappers/data_logger_state.py @@ -49,6 +49,8 @@ def sizeof_fmt(num, suffix="B"): 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}%)") + if r.earliest_block > 0: + print(f"\t Earliest: {r.earliest_block}") if byte_rate == 0.0: print("\t Block Rate: N/A") print("\t Byte Rate: N/A") From 604766da9650fc5c3e333bac5c6d0dc6179c8ca0 Mon Sep 17 00:00:00 2001 From: Jordan Yates Date: Tue, 17 Jun 2025 18:00:36 +1000 Subject: [PATCH 5/5] rpc_wrappers: heap_stats: added Add a wrapper for the `heap_stats` command. Signed-off-by: Jordan Yates --- src/infuse_iot/rpc_wrappers/heap_stats.py | 45 +++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src/infuse_iot/rpc_wrappers/heap_stats.py diff --git a/src/infuse_iot/rpc_wrappers/heap_stats.py b/src/infuse_iot/rpc_wrappers/heap_stats.py new file mode 100644 index 0000000..95a887a --- /dev/null +++ b/src/infuse_iot/rpc_wrappers/heap_stats.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 + +import tabulate + +import infuse_iot.generated.rpc_definitions as defs +from infuse_iot.commands import InfuseRpcCommand +from infuse_iot.zephyr.errno import errno + + +class heap_stats(InfuseRpcCommand, defs.heap_stats): + @classmethod + def add_parser(cls, parser): + pass + + def __init__(self, args): + self.args = args + + def request_struct(self): + return self.request() + + def request_json(self): + return {} + + def handle_response(self, return_code, response): + if return_code != 0: + print(f"Failed to query heap stats ({errno.strerror(-return_code)})") + return + + stats = [] + for heap in response.stats: + total = heap.free_bytes + heap.allocated_bytes + current_percent = 100 * (heap.allocated_bytes / total) + max_percent = 100 * (heap.max_allocated_bytes / total) + stats.append( + [ + f"0x{heap.addr:08x}", + total, + heap.allocated_bytes, + f"{current_percent:4.1f}%", + heap.max_allocated_bytes, + f"{max_percent:4.1f}%", + ] + ) + + print(tabulate.tabulate(stats, headers=["Address", "Total Size", "Current Usage", "", "Max Usage", ""]))