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, } 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 diff --git a/tests/tdf/tdf_example.bin b/tests/tdf/tdf_example.bin index bf627c4..b595204 100644 Binary files a/tests/tdf/tdf_example.bin and b/tests/tdf/tdf_example.bin differ diff --git a/tests/tdf/test_tdf.py b/tests/tdf/test_tdf.py index a88716e..3106181 100644 --- a/tests/tdf/test_tdf.py +++ b/tests/tdf/test_tdf.py @@ -20,12 +20,12 @@ def test_tdf(): for block in test_blocks: assert len(block) % 512 == 0 # Iterate over each TDF in the block - for tdf in decoder.decode(block): + for tdf in decoder.decode(block[2:]): assert isinstance(tdf, TDF.Reading) total_tdfs += 1 # Number of TDFs on the example block should never change - assert total_tdfs == 53 + assert total_tdfs == 269 def test_buffers():