Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/infuse_iot/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ def DESCRIPTION(self) -> str:

class InfuseRpcCommand:
RPC_DATA_SEND = False
RPC_DATA_SEND_CHUNKED = False
RPC_DATA_RECEIVE = False

@classmethod
Expand All @@ -71,6 +72,10 @@ def data_payload(self) -> bytes:
"""Payload to send with RPC_DATA"""
raise NotImplementedError

def data_payload_chunked(self) -> list[bytes]:
"""Payloads to send with RPC_DATA"""
raise NotImplementedError

def data_payload_recv_len(self) -> int:
"""Length of payload to receive with RPC_DATA"""
return 0xFFFFFFFF
Expand Down
4 changes: 3 additions & 1 deletion src/infuse_iot/generated/rpc_definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -986,7 +986,9 @@ class data_receiver:
COMMAND_ID = 32766

class request(VLACompatLittleEndianStruct):
_fields_ = []
_fields_ = [
("unaligned_input", ctypes.c_uint8),
]
_pack_ = 1

class response(VLACompatLittleEndianStruct):
Expand Down
56 changes: 40 additions & 16 deletions src/infuse_iot/rpc_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,19 +84,21 @@ def _wait_rpc_rsp(self) -> PacketReceived:
continue
return rsp.epacket

def run_data_send_cmd(
def _run_data_send_core(
self,
cmd_id: int,
auth: Auth,
params: bytes,
data: bytes,
data: list[bytes],
total_size: int,
packet_idx: bool,
progress_cb: Callable[[int], None] | None,
rsp_decoder: Callable[[bytes], ctypes.LittleEndianStructure],
) -> tuple[rpc.ResponseHeader, ctypes.LittleEndianStructure | None]:
self._request_id += 1
ack_period = 1
header = rpc.RequestHeader(self._request_id, cmd_id) # type: ignore
data_hdr = rpc.RequestDataHeader(len(data), ack_period)
data_hdr = rpc.RequestDataHeader(total_size, ack_period)

request_packet = bytes(header) + bytes(data_hdr) + params
pkt = PacketOutput(
Expand All @@ -113,18 +115,12 @@ def run_data_send_cmd(
if recv.ptype == InfuseType.RPC_RSP:
return self._finalise_command(recv, rsp_decoder)

# Send data payloads with maximum interface size
# Send data payloads chunked as requested
ack_cnt = -ack_period
offset = 0
size = self._max_payload - ctypes.sizeof(rpc.DataHeader)
# Round payload down to multiple of 4 bytes
size -= size % 4
while len(data) > 0:
size = min(size, len(data))
payload = data[:size]

hdr = rpc.DataHeader(self._request_id, offset)
pkt_bytes = bytes(hdr) + payload
for chunk_id, chunk in enumerate(data):
hdr = rpc.DataHeader(self._request_id, chunk_id if packet_idx else offset)
pkt_bytes = bytes(hdr) + chunk
pkt = PacketOutput(
self._id,
auth,
Expand All @@ -141,14 +137,42 @@ def run_data_send_cmd(
return self._finalise_command(recv, rsp_decoder)
ack_cnt = 0

offset += size
data = data[size:]
offset += len(chunk)
if progress_cb:
progress_cb(offset)
progress_cb(chunk_id + 1 if packet_idx else offset)

recv = self._wait_rpc_rsp()
return self._finalise_command(recv, rsp_decoder)

def run_data_send_cmd(
self,
cmd_id: int,
auth: Auth,
params: bytes,
data: bytes,
progress_cb: Callable[[int], None] | None,
rsp_decoder: Callable[[bytes], ctypes.LittleEndianStructure],
) -> tuple[rpc.ResponseHeader, ctypes.LittleEndianStructure | None]:
# Maxmimum payload size of interface
size = self._max_payload - ctypes.sizeof(rpc.DataHeader)
# Round payload down to multiple of 4 bytes
size -= size % 4
# itertools.batched once Python 3.12 is the minimum version
chunks = [data[i : i + size] for i in range(0, len(data), size)]
# Run with pre-computed chunks
return self._run_data_send_core(cmd_id, auth, params, chunks, len(data), False, progress_cb, rsp_decoder)

def run_data_send_cmd_chunked(
self,
cmd_id: int,
auth: Auth,
params: bytes,
data: list[bytes],
progress_cb: Callable[[int], None] | None,
rsp_decoder: Callable[[bytes], ctypes.LittleEndianStructure],
) -> tuple[rpc.ResponseHeader, ctypes.LittleEndianStructure | None]:
return self._run_data_send_core(cmd_id, auth, params, data, len(data), True, progress_cb, rsp_decoder)

def run_data_recv_cmd(
self,
cmd_id: int,
Expand Down
11 changes: 7 additions & 4 deletions src/infuse_iot/rpc_wrappers/lte_at_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,12 @@ def request_json(self):
return {"cmd": self.args.cmd}

def handle_response(self, return_code, response):
if response:
# Print returned strings even on failure
response_bytes = bytes(response.rsp)
if len(response_bytes):
decoded = bytes(response.rsp).decode("utf-8").strip()
print(decoded)
# Notification that command failed
if return_code != 0:
print(f"Failed to run command ({errno.strerror(-return_code)})")
return
decoded = bytes(response.rsp).decode("utf-8").strip()

print(decoded)
22 changes: 13 additions & 9 deletions src/infuse_iot/tools/ota_upgrade.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,12 @@ def __init__(self, args):
elif args.single:
# Find the associated release
diff_folder = args.single.parent
release_folder = diff_folder.parent
if diff_folder.name != "diffs":
raise argparse.ArgumentTypeError(f"{args.single} is not in a diff folder")
# Try the next level up
diff_folder = diff_folder.parent
if diff_folder.name != "diffs":
raise argparse.ArgumentTypeError(f"{args.single} is not in a diff (sub)folder")
release_folder = diff_folder.parent
self._release = ValidRelease(str(release_folder))
self._single_diff = args.single
else:
Expand Down Expand Up @@ -251,13 +254,6 @@ def run(self):

# Do we have a valid diff?
diff_file = self._release.dir / "diffs" / f"{v_str}.bin"
if self._single_diff and self._single_diff != diff_file:
# Not the file we've copied to the gateway flash
self._missing_diffs.add(v_str)
self._handled.append(source.infuse_id)
self._no_diff += 1
self.state_update(live, "Scanning")
continue

if not diff_file.exists():
# Is this a single diff from a different application we know about?
Expand All @@ -269,6 +265,14 @@ def run(self):
self.state_update(live, "Scanning")
continue

if self._single_diff and self._single_diff != diff_file:
# Not the file we've copied to the gateway flash
self._missing_diffs.add(v_str)
self._handled.append(source.infuse_id)
self._no_diff += 1
self.state_update(live, "Scanning")
continue

# Is signal strong enough to connect?
if self._min_rssi and source.rssi < self._min_rssi:
continue
Expand Down
9 changes: 9 additions & 0 deletions src/infuse_iot/tools/rpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,15 @@ def run(self):
self._command.data_progress_cb,
decode_fn,
)
elif self._command.RPC_DATA_SEND_CHUNKED:
hdr, rsp = rpc_client.run_data_send_cmd_chunked(
self._command.COMMAND_ID, # type: ignore
self._command.auth_level(),
params,
self._command.data_payload_chunked(),
self._command.data_progress_cb,
decode_fn,
)
elif self._command.RPC_DATA_RECEIVE:
hdr, rsp = rpc_client.run_data_recv_cmd(
self._command.COMMAND_ID, # type: ignore
Expand Down