diff --git a/src/infuse_iot/generated/kv_definitions.py b/src/infuse_iot/generated/kv_definitions.py index a6c7024..d00f3c3 100644 --- a/src/infuse_iot/generated/kv_definitions.py +++ b/src/infuse_iot/generated/kv_definitions.py @@ -97,6 +97,15 @@ class kv_algorithm_tilt_args(VLACompatLittleEndianStruct): ] _pack_ = 1 + class kv_algorithm_movement_threshold_args(VLACompatLittleEndianStruct): + """Arguments for 'Shot Triggered' algorithm""" + + _fields_ = [ + ("moving_for", ctypes.c_uint32), + ("threshold_ug", ctypes.c_uint32), + ] + _pack_ = 1 + class slots: class reboots(VLACompatLittleEndianStruct): @@ -458,6 +467,18 @@ class alg_tilt_args(VLACompatLittleEndianStruct): ] _pack_ = 1 + class alg_movement_threshold_args(VLACompatLittleEndianStruct): + """Configuration for the 'Movement Threshold' algorithm""" + + NAME = "ALG_MOVEMENT_THRESHOLD_ARGS" + BASE_ID = 202 + RANGE = 1 + _fields_ = [ + ("logging", structs.kv_algorithm_logging), + ("args", structs.kv_algorithm_movement_threshold_args), + ] + _pack_ = 1 + class task_schedules_default_id(VLACompatLittleEndianStruct): """Unique identifier for default schedule set""" @@ -547,6 +568,7 @@ class secure_storage_reserved(VLACompatLittleEndianStruct): 115: geofence, 200: alg_stationary_windowed_args, 201: alg_tilt_args, + 202: alg_movement_threshold_args, 1000: task_schedules_default_id, 1001: task_schedules, 1002: task_schedules, diff --git a/src/infuse_iot/generated/rpc_definitions.py b/src/infuse_iot/generated/rpc_definitions.py index 1059810..2e97d42 100644 --- a/src/infuse_iot/generated/rpc_definitions.py +++ b/src/infuse_iot/generated/rpc_definitions.py @@ -790,6 +790,36 @@ class response(VLACompatLittleEndianStruct): _pack_ = 1 +class data_logger_state_v2(RPCDefinitionBase): + """Get state of a data logger (Larger erase unit)""" + + NAME = "data_logger_state_v2" + HELP = "Get state of a data logger (Larger erase unit)" + DESCRIPTION = "Get state of a data logger (Larger erase unit)" + COMMAND_ID = 24 + + class request(VLACompatLittleEndianStruct): + _fields_ = [ + ("logger", ctypes.c_uint8), + ] + _pack_ = 1 + + class response(VLACompatLittleEndianStruct): + _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_uint32), + ("uptime", ctypes.c_uint32), + ] + _pack_ = 1 + + class coap_download(RPCDefinitionBase): """Download a file from a COAP server (Infuse-IoT DTLS protected)""" @@ -853,6 +883,35 @@ class response(VLACompatLittleEndianStruct): _pack_ = 1 +class coap_download_v2(RPCDefinitionBase): + """Download a file from a COAP server (Infuse-IoT DTLS protected)""" + + NAME = "coap_download_v2" + HELP = "Download a file from a COAP server (Infuse-IoT DTLS protected)" + DESCRIPTION = "Download a file from a COAP server (Infuse-IoT DTLS protected)" + COMMAND_ID = 32 + + class request(VLACompatLittleEndianStruct): + _fields_ = [ + ("server_address", 48 * ctypes.c_char), + ("server_port", ctypes.c_uint16), + ("block_timeout_ms", ctypes.c_uint16), + ("block_size", ctypes.c_uint16), + ("action", ctypes.c_uint8), + ("resource_len", ctypes.c_uint32), + ("resource_crc", ctypes.c_uint32), + ] + vla_field = ("resource", 0 * ctypes.c_char) + _pack_ = 1 + + class response(VLACompatLittleEndianStruct): + _fields_ = [ + ("resource_len", ctypes.c_uint32), + ("resource_crc", ctypes.c_uint32), + ] + _pack_ = 1 + + class file_write_basic(RPCDefinitionBase): """Write a file to the device""" @@ -1045,6 +1104,28 @@ class response(VLACompatLittleEndianStruct): _pack_ = 1 +class ubx_assist_now_ztp_creds(RPCDefinitionBase): + """Retrieve U-blox AssistNow Zero Touch Provisioning credentials""" + + NAME = "ubx_assist_now_ztp_creds" + HELP = "Retrieve U-blox AssistNow Zero Touch Provisioning credentials" + DESCRIPTION = "Retrieve U-blox AssistNow Zero Touch Provisioning credentials" + COMMAND_ID = 70 + + class request(VLACompatLittleEndianStruct): + _fields_ = [ + ("mon_ver_offset", ctypes.c_uint8), + ] + _pack_ = 1 + + class response(VLACompatLittleEndianStruct): + _fields_ = [ + ("ubx_sec_uniqid", 18 * ctypes.c_uint8), + ] + vla_field = ("ubx_mon_ver", 0 * ctypes.c_uint8) + _pack_ = 1 + + class security_state(RPCDefinitionBase): """Query current security state and validate identity""" @@ -1175,8 +1256,10 @@ class response(VLACompatLittleEndianStruct): lte_state.COMMAND_ID: lte_state, data_logger_read_available.COMMAND_ID: data_logger_read_available, lte_state_v2.COMMAND_ID: lte_state_v2, + data_logger_state_v2.COMMAND_ID: data_logger_state_v2, coap_download.COMMAND_ID: coap_download, zperf_upload.COMMAND_ID: zperf_upload, + coap_download_v2.COMMAND_ID: coap_download_v2, file_write_basic.COMMAND_ID: file_write_basic, annotate.COMMAND_ID: annotate, bt_connect_infuse.COMMAND_ID: bt_connect_infuse, @@ -1185,6 +1268,7 @@ class response(VLACompatLittleEndianStruct): bt_file_copy_coap.COMMAND_ID: bt_file_copy_coap, bt_mcumgr_reboot.COMMAND_ID: bt_mcumgr_reboot, gravity_reference_update.COMMAND_ID: gravity_reference_update, + ubx_assist_now_ztp_creds.COMMAND_ID: ubx_assist_now_ztp_creds, security_state.COMMAND_ID: security_state, security_key_update.COMMAND_ID: security_key_update, data_sender.COMMAND_ID: data_sender, @@ -1240,8 +1324,10 @@ class response(VLACompatLittleEndianStruct): "lte_state", "data_logger_read_available", "lte_state_v2", + "data_logger_state_v2", "coap_download", "zperf_upload", + "coap_download_v2", "file_write_basic", "annotate", "bt_connect_infuse", @@ -1250,6 +1336,7 @@ class response(VLACompatLittleEndianStruct): "bt_file_copy_coap", "bt_mcumgr_reboot", "gravity_reference_update", + "ubx_assist_now_ztp_creds", "security_state", "security_key_update", "data_sender", diff --git a/src/infuse_iot/rpc_client.py b/src/infuse_iot/rpc_client.py index e3945d4..d5ca801 100644 --- a/src/infuse_iot/rpc_client.py +++ b/src/infuse_iot/rpc_client.py @@ -96,7 +96,7 @@ def _run_data_send_core( rsp_decoder: Callable[[bytes], ctypes.LittleEndianStructure], ) -> tuple[rpc.ResponseHeader, ctypes.LittleEndianStructure | None]: self._request_id += 1 - ack_period = 1 + ack_period = 2 header = rpc.RequestHeader(self._request_id, cmd_id) # type: ignore data_hdr = rpc.RequestDataHeader(total_size, ack_period) diff --git a/src/infuse_iot/rpc_wrappers/wifi_configure.py b/src/infuse_iot/rpc_wrappers/wifi_configure.py index 7b20f05..684b4fe 100644 --- a/src/infuse_iot/rpc_wrappers/wifi_configure.py +++ b/src/infuse_iot/rpc_wrappers/wifi_configure.py @@ -60,18 +60,24 @@ def add_parser(cls, parser): parser.add_argument( "--channel", "-c", type=int, default=wifi.FrequencyChannel.CHANNEL_ANY, help="Network channel index" ) + parser.add_argument("--delete", action="store_true", help="Delete configured network") def __init__(self, args): self.args = 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") + if self.args.delete: + ssid_struct = kv_write.kv_store_value_factory(20, b"") + psk_struct = kv_write.kv_store_value_factory(21, b"") + chan_struct = kv_write.kv_store_value_factory(22, b"") + else: + 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 = kv_write.kv_store_value_factory(20, len(ssid_bytes).to_bytes(1, "little") + ssid_bytes) - psk_struct = kv_write.kv_store_value_factory(21, len(psk_bytes).to_bytes(1, "little") + psk_bytes) - chan_struct = kv_write.kv_store_value_factory(22, chan_bytes) + ssid_struct = kv_write.kv_store_value_factory(20, len(ssid_bytes).to_bytes(1, "little") + ssid_bytes) + psk_struct = kv_write.kv_store_value_factory(21, len(psk_bytes).to_bytes(1, "little") + psk_bytes) + chan_struct = kv_write.kv_store_value_factory(22, chan_bytes) request_bytes = bytes(ssid_struct) + bytes(psk_struct) + bytes(chan_struct) return bytes(self.request(3)) + request_bytes @@ -82,12 +88,20 @@ def handle_response(self, return_code, response): return def print_status(name, rc): - if rc < 0: - print(f"{name} failed to write") - elif rc == 0: - print(f"{name} already matched") + if self.args.delete: + if rc == 0: + print(f"{name} deleted") + elif rc == -errno.ENOENT: + print(f"{name} did not exist") + else: + print(f"{name} failed to delete ({errno(-rc).name})") else: - print(f"{name} updated") + if rc < 0: + print(f"{name} failed to write") + elif rc == 0: + print(f"{name} already matched") + else: + print(f"{name} updated") print_status("SSID", response.rc[0]) print_status("PSK", response.rc[1]) diff --git a/src/infuse_iot/tools/provision.py b/src/infuse_iot/tools/provision.py index 79ab9e8..c925007 100644 --- a/src/infuse_iot/tools/provision.py +++ b/src/infuse_iot/tools/provision.py @@ -8,6 +8,7 @@ import ctypes import sys from http import HTTPStatus +from uuid import UUID from infuse_iot.api_client import Client from infuse_iot.api_client.api.board import get_board_by_id, get_boards @@ -16,7 +17,7 @@ get_device_by_soc_and_mcu_id, ) from infuse_iot.api_client.api.organisation import get_all_organisations -from infuse_iot.api_client.models import Board, DeviceMetadata, Error, NewDevice +from infuse_iot.api_client.models import Board, Device, DeviceMetadata, Error, NewDevice from infuse_iot.commands import InfuseCommand from infuse_iot.credentials import get_api_key from infuse_iot.util.console import choose_one @@ -66,8 +67,14 @@ def add_parser(cls, parser): def __init__(self, args): self._vendor = args.vendor self._snr = args.snr - self._board = args.board - self._org = args.organisation + try: + self._board = UUID(args.board) if args.board else None + except ValueError: + sys.exit(f"Board ID: '{args.board}' is not a valid UUID") + try: + self._org = UUID(args.organisation) if args.organisation else None + except ValueError: + sys.exit(f"Organisation ID: '{args.organisation}' is not a valid UUID") self._id = args.id self._dry_run = args.dry_run self._metadata = {} @@ -138,6 +145,9 @@ def run(self): ) if response.status_code == HTTPStatus.OK: # Device found, fall through + assert isinstance(response.parsed, Device) + self._org = response.parsed.organisation_id + self._board = response.parsed.board_id pass elif response.status_code == HTTPStatus.NOT_FOUND: # Create new device here @@ -153,8 +163,6 @@ def run(self): err = "Failed to query device after creation:\n" err += f"\t<{response.status_code}> {response.content.decode('utf-8')}" sys.exit(err) - print("To provision more devices like this:") - print(f"\t infuse provision --organisation {self._org} --board {self._board}") else: err = "Failed to query device information:\n" err += f"\t<{response.status_code}> {response.content.decode('utf-8')}" @@ -179,3 +187,7 @@ def run(self): print(f"HW ID 0x{hardware_id:016x} now provisioned as 0x{desired.device_id:016x}") interface.close() + + example_cmd = f"infuse provision --organisation {self._org} --board {self._board} --{self._vendor}" + print("To provision more devices like this:") + print(f"\t {example_cmd}")