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: 3 additions & 2 deletions scripts/apn_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ def progress_table(self):

class response(VLACompatLittleEndianStruct):
_fields_ = []
vla_field = ("rc", ctypes.c_int16)
vla_field = ("rc", 0 * ctypes.c_int16)
_pack_ = 1

def state_update(self, live: Live, state: str):
self.state = state
Expand All @@ -75,7 +76,7 @@ def announce_observed(self, live: Live, infuse_id: int, pkt: readings.announce):
params = bytes(rpc.kv_write.request(1)) + all_vals

hdr, rsp = rpc_client.run_standard_cmd(
rpc.kv_write.COMMAND_ID, Auth.DEVICE, params, self.response.from_buffer_copy
rpc.kv_write.COMMAND_ID, Auth.DEVICE, params, self.response.vla_from_buffer_copy
)
if hdr.return_code == 0:
assert rsp is not None and hasattr(rsp, "rc")
Expand Down
7 changes: 7 additions & 0 deletions src/infuse_iot/epacket/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from typing_extensions import Self

import infuse_iot.generated.rpc_definitions as rpc_defs
import infuse_iot.generated.tdf_definitions as tdf_defs
from infuse_iot.epacket.common import Serializable
from infuse_iot.util.ctypes import bytes_to_uint8

Expand Down Expand Up @@ -83,6 +84,12 @@ def from_rpc_struct(cls, struct: rpc_defs.rpc_struct_bt_addr_le):

return cls(struct.type, int.from_bytes(struct.val, "little"))

@classmethod
def from_tdf_struct(cls, struct: tdf_defs.structs.tdf_struct_bt_addr_le):
"""Create instance from the common TDF address structure"""

return cls(struct.type, struct.val)

def __init__(self, val):
self.val = val

Expand Down
3 changes: 3 additions & 0 deletions src/infuse_iot/tools/credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class SubCommand(InfuseCommand):
@classmethod
def add_parser(cls, parser):
parser.add_argument("--api-key", type=str, help="Set Infuse-IoT API key")
parser.add_argument("--api-key-print", action="store_true", help="Print Infuse-IoT API key")
parser.add_argument("--network", type=ValidFile, help="Load network credentials from file")

def __init__(self, args):
Expand All @@ -28,6 +29,8 @@ def __init__(self, args):
def run(self):
if self.args.api_key is not None:
credentials.set_api_key(self.args.api_key)
if self.args.api_key_print:
print(f"API Key: {credentials.get_api_key()}")
if self.args.network is not None:
# Read the file
with self.args.network.open("r") as f:
Expand Down
19 changes: 18 additions & 1 deletion src/infuse_iot/tools/gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@

import infuse_iot.epacket.interface as interface
import infuse_iot.generated.rpc_definitions as defs
from infuse_iot import rpc
import infuse_iot.generated.tdf_definitions as tdf_defs
from infuse_iot import rpc, tdf
from infuse_iot.commands import InfuseCommand
from infuse_iot.common import InfuseID, InfuseType
from infuse_iot.database import (
Expand All @@ -39,6 +40,7 @@
from infuse_iot.socket_comms import (
ClientNotification,
ClientNotificationConnectionCreated,
ClientNotificationConnectionDropped,
ClientNotificationConnectionFailed,
ClientNotificationEpacketReceived,
GatewayRequestConnectionRelease,
Expand Down Expand Up @@ -153,6 +155,7 @@ def __init__(self, common: CommonThreadState, log: io.TextIOWrapper):
self._line = ""
self._log = log
self._next_ping = 0.0
self._tdf_decoder = tdf.TDF()
super().__init__(self._iter)

def _iter(self) -> None:
Expand Down Expand Up @@ -190,6 +193,17 @@ class memfault_chunk_header(ctypes.LittleEndianStructure):
p = p[3 + hdr.len :]
print(f"Memfault Chunk {hdr.cnt:3d}: {base64.b64encode(chunk).decode('utf-8')}")

def _handle_local_tdf(self, pkt: PacketReceived):
if self._common.server is None:
# No-one to broadcast events to
return
for reading in self._tdf_decoder.decode(pkt.payload):
if isinstance(reading.data[0], tdf_defs.readings.bluetooth_connection) and reading.data[0].connected == 0:
if_addr = interface.Address.BluetoothLeAddr.from_tdf_struct(reading.data[0].address)
infuse_id = self._common.ddb.infuse_id_from_bluetooth(if_addr)
if infuse_id:
self._common.server.broadcast(ClientNotificationConnectionDropped(infuse_id))

def _handle_serial_frame(self, frame: bytearray):
try:
# Decode the serial packet
Expand All @@ -216,6 +230,9 @@ def _handle_serial_frame(self, frame: bytearray):
# Iterate over all contained subpackets
for pkt in decoded:
Console.log_rx(pkt.ptype, len(frame))
# Handle any local TDFs
if len(pkt.route) == 1 and pkt.ptype == InfuseType.TDF:
self._handle_local_tdf(pkt)
# Handle any local RPC responses
self._common.rpc.handle(pkt)
# Handle any Memfault chunks
Expand Down
29 changes: 20 additions & 9 deletions src/infuse_iot/tools/ota_upgrade.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
LocalClient,
default_multicast_address,
)
from infuse_iot.util.argparse import ValidRelease
from infuse_iot.util.argparse import ValidFile, ValidRelease


class SubCommand(InfuseCommand):
Expand All @@ -38,7 +38,7 @@ def __init__(self, args):
self._client = LocalClient(default_multicast_address(), 1.0)
self._conn_timeout = args.conn_timeout
self._min_rssi: int | None = args.rssi
self._single_id: int | None = args.id
self._explicit_ids: list[int] = []
self._release: ValidRelease = args.release
self._app_name = self._release.metadata["application"]["primary"]
self._app_id = self._release.metadata["application"]["id"]
Expand All @@ -63,17 +63,26 @@ def __init__(self, args):
else:
self._log = open(args.log, "+a", encoding="utf-8") # noqa: SIM115

if args.id is not None:
self._explicit_ids.append(args.id)
elif args.list is not None:
with args.list.open("r") as f:
for line in f.readlines():
self._explicit_ids.append(int(line.strip(), 0))

@classmethod
def add_parser(cls, parser):
parser.add_argument(
"--release", "-r", type=ValidRelease, required=True, help="Application release to upgrade to"
)
parser.add_argument("--rssi", type=int, help="Minimum RSSI to attempt upgrade process")
parser.add_argument("--id", type=lambda x: int(x, 0), help="Single device to upgrade")
parser.add_argument("--log", type=str, help="File to write upgrade results to")
parser.add_argument(
"--conn-timeout", type=int, default=10000, help="Timeout to wait for a connection to the device (ms)"
)
explicit = parser.add_mutually_exclusive_group()
explicit.add_argument("--id", type=lambda x: int(x, 0), help="Single device to upgrade")
explicit.add_argument("--list", type=ValidFile, help="File containing a list of IDs to upgrade")

def progress_table(self):
table = Table()
Expand Down Expand Up @@ -111,14 +120,16 @@ def run(self):
with Live(self.progress_table(), refresh_per_second=4) as live:
for source, announce in self._client.observe_announce():
self.state_update(live, "Scanning")
if announce.application != self._app_id and not self._single_id:
continue
if self._single_id:
if self._single_id != source.infuse_id:
if len(self._explicit_ids):
if source.infuse_id not in self._explicit_ids:
continue
if self._single_id in self._handled:
# The one device we care about has been upgraded
if len(self._handled) == len(self._explicit_ids):
# We've handled all devices
self.state_update(live, "All devices updated")
return
else:
if announce.application != self._app_id:
continue
if source.infuse_id in self._handled:
continue
v = announce.version
Expand Down