From 29bf1fb9c3cd522be10e94f33fa511d4d79a79b0 Mon Sep 17 00:00:00 2001 From: Jordan Yates Date: Wed, 23 Jul 2025 11:19:33 +1000 Subject: [PATCH 1/3] generated: regenerate Regenerate definitions from the embedded repo. Signed-off-by: Jordan Yates --- src/infuse_iot/generated/kv_definitions.py | 13 +++ src/infuse_iot/generated/rpc_definitions.py | 4 +- src/infuse_iot/generated/tdf_definitions.py | 90 +++++++++++++++++++++ 3 files changed, 105 insertions(+), 2 deletions(-) diff --git a/src/infuse_iot/generated/kv_definitions.py b/src/infuse_iot/generated/kv_definitions.py index 1cd25e6..14e682d 100644 --- a/src/infuse_iot/generated/kv_definitions.py +++ b/src/infuse_iot/generated/kv_definitions.py @@ -160,6 +160,18 @@ class wifi_psk(VLACompatLittleEndianStruct): vla_field = ("psk", structs.kv_string) _pack_ = 1 + class wifi_channels(VLACompatLittleEndianStruct): + """WiFi band and channel configuration""" + + NAME = "WIFI_CHANNELS" + BASE_ID = 22 + RANGE = 1 + _fields_ = [ + ("band", ctypes.c_uint8), + ] + vla_field = ("channels", 0 * ctypes.c_uint8) + _pack_ = 1 + class ntp_server_url(VLACompatLittleEndianStruct): """URL of the NTP server to use for time synchronisation""" @@ -370,6 +382,7 @@ class secure_storage_reserved(VLACompatLittleEndianStruct): 10: fixed_location, 20: wifi_ssid, 21: wifi_psk, + 22: wifi_channels, 30: ntp_server_url, 31: epacket_udp_url, 32: epacket_udp_port, diff --git a/src/infuse_iot/generated/rpc_definitions.py b/src/infuse_iot/generated/rpc_definitions.py index 0f25d24..2bd9efa 100644 --- a/src/infuse_iot/generated/rpc_definitions.py +++ b/src/infuse_iot/generated/rpc_definitions.py @@ -90,7 +90,7 @@ class rpc_struct_wifi_state(VLACompatLittleEndianStruct): _fields_ = [ ("state", ctypes.c_uint8), ("ssid", 32 * ctypes.c_char), - ("bssid", 6 * ctypes.c_char), + ("bssid", 6 * ctypes.c_uint8), ("band", ctypes.c_uint8), ("channel", ctypes.c_uint8), ("iface_mode", ctypes.c_uint8), @@ -133,7 +133,7 @@ class rpc_struct_wifi_scan_result(VLACompatLittleEndianStruct): ("channel", ctypes.c_uint8), ("security", ctypes.c_uint8), ("rssi", ctypes.c_int8), - ("bssid", 6 * ctypes.c_char), + ("bssid", 6 * ctypes.c_uint8), ("ssid_len", ctypes.c_uint8), ] vla_field = ("ssid", 0 * ctypes.c_char) diff --git a/src/infuse_iot/generated/tdf_definitions.py b/src/infuse_iot/generated/tdf_definitions.py index c9b673b..0594de3 100644 --- a/src/infuse_iot/generated/tdf_definitions.py +++ b/src/infuse_iot/generated/tdf_definitions.py @@ -196,6 +196,48 @@ class tdf_struct_eui48(TdfStructBase): def val(self): return int.from_bytes(self._val, byteorder="little") + class tdf_struct_wifi_network_params(TdfStructBase): + """WiFi network parameters""" + + _fields_ = [ + ("_bssid", 6 * ctypes.c_uint8), + ("band", ctypes.c_uint8), + ("channel", ctypes.c_uint8), + ("iface_mode", ctypes.c_uint8), + ("link_mode", ctypes.c_uint8), + ("security", ctypes.c_uint8), + ("rssi", ctypes.c_int8), + ("beacon_interval", ctypes.c_uint16), + ("twt_capable", ctypes.c_uint8), + ] + _pack_ = 1 + _postfix_ = { + "bssid": "", + "band": "", + "channel": "", + "iface_mode": "", + "link_mode": "", + "security": "", + "rssi": "", + "beacon_interval": "", + "twt_capable": "", + } + _display_fmt_ = { + "bssid": "0x{:012x}", + "band": "{}", + "channel": "{}", + "iface_mode": "{}", + "link_mode": "{}", + "security": "{}", + "rssi": "{}", + "beacon_interval": "{}", + "twt_capable": "{}", + } + + @property + def bssid(self): + return int.from_bytes(self._bssid, byteorder="big") + class readings: class announce(TdfReadingBase): @@ -1334,6 +1376,51 @@ class idx_array_period(TdfReadingBase): "period": "{}", } + class wifi_connected(TdfReadingBase): + """WiFi network is now connected""" + + name = "WIFI_CONNECTED" + _fields_ = [ + ("network", structs.tdf_struct_wifi_network_params), + ] + _pack_ = 1 + _postfix_ = { + "network": "", + } + _display_fmt_ = { + "network": "{}", + } + + class wifi_connection_failed(TdfReadingBase): + """Failed to connect to a WiFi network""" + + name = "WIFI_CONNECTION_FAILED" + _fields_ = [ + ("reason", ctypes.c_uint8), + ] + _pack_ = 1 + _postfix_ = { + "reason": "", + } + _display_fmt_ = { + "reason": "{}", + } + + class wifi_disconnected(TdfReadingBase): + """Wi-Fi network is now disconnected""" + + name = "WIFI_DISCONNECTED" + _fields_ = [ + ("reason", ctypes.c_uint8), + ] + _pack_ = 1 + _postfix_ = { + "reason": "", + } + _display_fmt_ = { + "reason": "{}", + } + class array_type(TdfReadingBase): """Example array type""" @@ -1395,5 +1482,8 @@ class array_type(TdfReadingBase): 45: readings.lora_tx, 46: readings.idx_array_freq, 47: readings.idx_array_period, + 48: readings.wifi_connected, + 49: readings.wifi_connection_failed, + 50: readings.wifi_disconnected, 100: readings.array_type, } From 805eb1a0143a682e11650c6d7c4fdee4ce3ad71a Mon Sep 17 00:00:00 2001 From: Jordan Yates Date: Wed, 23 Jul 2025 11:20:33 +1000 Subject: [PATCH 2/3] rpc_wrappers: wifi_configure: optional band/channel Add the option to configure the WiFi band/channel for connections. Signed-off-by: Jordan Yates --- src/infuse_iot/rpc_wrappers/wifi_configure.py | 47 +++++++++++++++++-- src/infuse_iot/zephyr/wifi.py | 4 ++ 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/src/infuse_iot/rpc_wrappers/wifi_configure.py b/src/infuse_iot/rpc_wrappers/wifi_configure.py index 089dcec..fd35bff 100644 --- a/src/infuse_iot/rpc_wrappers/wifi_configure.py +++ b/src/infuse_iot/rpc_wrappers/wifi_configure.py @@ -3,8 +3,9 @@ import ctypes import infuse_iot.generated.rpc_definitions as defs +import infuse_iot.zephyr.wifi as wifi from infuse_iot.commands import InfuseRpcCommand -from infuse_iot.util.ctypes import VLACompatLittleEndianStruct +from infuse_iot.util.ctypes import UINT8_MAX, VLACompatLittleEndianStruct, bytes_to_uint8 from infuse_iot.zephyr.errno import errno @@ -27,16 +28,49 @@ class kv_store_value(ctypes.LittleEndianStructure): _fields_ = [ ("id", ctypes.c_uint16), ("len", ctypes.c_uint16), - ("data", ctypes.c_char * len(value_bytes)), + ("data", ctypes.c_ubyte * len(value_bytes)), ] _pack_ = 1 - return kv_store_value(id, len(value_bytes), value_bytes) + return kv_store_value(id, len(value_bytes), bytes_to_uint8(value_bytes)) @classmethod def add_parser(cls, parser): parser.add_argument("--ssid", "-s", type=str, help="Network name") parser.add_argument("--psk", "-p", type=str, help="Network password") + band_parser = parser.add_mutually_exclusive_group() + band_parser.add_argument( + "--band-unknown", + action="store_const", + dest="band", + const=UINT8_MAX, + default=UINT8_MAX, + help="Unknown frequency band", + ) + band_parser.add_argument( + "--band-2G4", + action="store_const", + dest="band", + const=wifi.FrequencyBand.BAND_2_4_GHZ, + help="2.4GHz frequency band", + ) + band_parser.add_argument( + "--band-5G", + action="store_const", + dest="band", + const=wifi.FrequencyBand.BAND_5_GHZ, + help="5GHz frequency band", + ) + band_parser.add_argument( + "--band-6G", + action="store_const", + dest="band", + const=wifi.FrequencyBand.BAND_6_GHZ, + help="6GHz frequency band", + ) + parser.add_argument( + "--channel", "-c", type=int, default=wifi.FrequencyChannel.CHANNEL_ANY, help="Network channel index" + ) def __init__(self, args): self.args = args @@ -44,12 +78,14 @@ def __init__(self, args): def request_struct(self): ssid_bytes = self.args.ssid.encode("utf-8") + b"\x00" psk_bytes = self.args.psk.encode("utf-8") + b"\x00" + chan_bytes = self.args.band.to_bytes(1, "little") + self.args.channel.to_bytes(1, "little") ssid_struct = self.kv_store_value_factory(20, len(ssid_bytes).to_bytes(1, "little") + ssid_bytes) psk_struct = self.kv_store_value_factory(21, len(psk_bytes).to_bytes(1, "little") + psk_bytes) + chan_struct = self.kv_store_value_factory(22, chan_bytes) - request_bytes = bytes(ssid_struct) + bytes(psk_struct) - return bytes(self.request(2)) + request_bytes + request_bytes = bytes(ssid_struct) + bytes(psk_struct) + bytes(chan_struct) + return bytes(self.request(3)) + request_bytes def handle_response(self, return_code, response): if return_code != 0: @@ -66,3 +102,4 @@ def print_status(name, rc): print_status("SSID", response.rc[0]) print_status("PSK", response.rc[1]) + print_status("Channel", response.rc[2]) diff --git a/src/infuse_iot/zephyr/wifi.py b/src/infuse_iot/zephyr/wifi.py index 02bd99e..20a9cfb 100644 --- a/src/infuse_iot/zephyr/wifi.py +++ b/src/infuse_iot/zephyr/wifi.py @@ -40,6 +40,10 @@ def __str__(self): return pretty_names[self.value] +class FrequencyChannel(enum.IntEnum): + CHANNEL_ANY = 255 + + class LinkMode(enum.IntEnum): WIFI_802_11 = 0 WIFI_802_11b = 1 From 8c2246b613d7e5b5a8be4c9619b46283653c22c6 Mon Sep 17 00:00:00 2001 From: Jordan Yates Date: Fri, 25 Jul 2025 09:16:47 +1000 Subject: [PATCH 3/3] tests: tdf: update binary file Update the test binary to contain more TDF encoding types. Signed-off-by: Jordan Yates --- tests/tdf/tdf_example.bin | Bin 5120 -> 16384 bytes tests/tdf/test_tdf.py | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/tdf/tdf_example.bin b/tests/tdf/tdf_example.bin index bf627c4ddaea863171ec80810be07e83e15cd360..b595204b6b9e9618580d0e5464fc2c1ea870a1be 100644 GIT binary patch literal 16384 zcmeI0Pe@cz6vnT|>CMzkQ96qB$NP{Fi;|(lB&94Of-o$yU7+Bm)J+t%oJG;DF=!FA za273ER8}ip)wN%#T31aek3Z~0=$sJ&*(XD?5;bdWwCJS2gn@$e8}Z0Z7)9yack* zo#3UA74By)fb7$sI0!kUKXNf-wflifAjkB3UJf~;$9W}Whx?98A;mYme5LZIR^eYZQ9&}&w2FNBi##N9F?hCGl z?9zi=1G&$A&YK|*xX-u_^06M^Es%4PZ(hp(8^?q)e41CYc*g(M;jEv_iPj~v_>X3B z!v8|s|BG9sBZJ($y5r6Gf9F9K-S<45o(TDT;S-0HdSXZYVWlqG;cHW>-H!WqRJAMR z?{t)MBZONoQVr}ymO+d`f{U2Qzj8LZfnNPZN$bPS-d}oBvm`@qHX{gdrx1lye z7wqR@XJ}b+;}^AC-5n7^PhM~6xS@%})1K5HNao@AN!wFP!(-@V;x~G{bNSkK|28-4 zf3p+!zuVX%;r|TCE8h7IaxR>wj&0B_P54uw42BIlNqK=jrj@ zPIeyEI_psxwhhQ{->|RO;xRJy4_Ow+$dE&B@_#;55%)jjl$NQiOvNF`t^cl^EUhBg zoUtkr{s)e#wCz-^C*h1$lg=ulxlqM|zF$<4@PEZEl?8iXJG0ni`%XT-=axeV2mv7= z1cZPP5CTF#2nYcoAOwVf5D)@FKnMr{As_^VfDjM@LO=)z0U;m+gn$qb0zyCt2mv7= K1cbo968Hv~S;K1p literal 5120 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 T>MW@Lbg}D>`2RorZx8$z7i