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
56 changes: 56 additions & 0 deletions src/infuse_iot/generated/rpc_definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,18 @@ class rpc_struct_infuse_state(VLACompatLittleEndianStruct):
_pack_ = 1


class rpc_struct_sockaddr(VLACompatLittleEndianStruct):
"""`struct sockaddr_in` or `struct sockaddr_in6` compatible address"""

_fields_ = [
("sin_family", ctypes.c_uint8),
("sin_port", ctypes.c_uint16),
("sin_addr", 16 * ctypes.c_uint8),
("scope_id", ctypes.c_uint8),
]
_pack_ = 1


class rpc_enum_bt_le_addr_type(enum.IntEnum):
"""Bluetooth LE address type"""

Expand Down Expand Up @@ -194,6 +206,16 @@ class rpc_enum_data_logger(enum.IntEnum):
FLASH_REMOVABLE = 2


class rpc_enum_zperf_data_source(enum.IntEnum):
"""Source for zperf data upload"""

CONSTANT = 0
RANDOM = 1
FLASH_ONBOARD = 2
FLASH_REMOVABLE = 3
ENCRYPT = 128


class reboot:
"""Reboot the device after a delay"""

Expand Down Expand Up @@ -634,6 +656,40 @@ class response(VLACompatLittleEndianStruct):
_pack_ = 1


class zperf_upload:
"""Network upload bandwidth testing using zperf/iperf"""

HELP = "Network upload bandwidth testing using zperf/iperf"
DESCRIPTION = "Network upload bandwidth testing using zperf/iperf"
COMMAND_ID = 31

class request(VLACompatLittleEndianStruct):
_fields_ = [
("peer_address", rpc_struct_sockaddr),
("sock_type", ctypes.c_uint8),
("data_source", ctypes.c_uint8),
("duration_ms", ctypes.c_uint32),
("rate_kbps", ctypes.c_uint32),
("packet_size", ctypes.c_uint16),
]
_pack_ = 1

class response(VLACompatLittleEndianStruct):
_fields_ = [
("nb_packets_sent", ctypes.c_uint32),
("nb_packets_rcvd", ctypes.c_uint32),
("nb_packets_lost", ctypes.c_uint32),
("nb_packets_outorder", ctypes.c_uint32),
("total_len", ctypes.c_uint64),
("time_in_us", ctypes.c_uint64),
("jitter_in_us", ctypes.c_uint32),
("client_time_in_us", ctypes.c_uint64),
("packet_size", ctypes.c_uint32),
("nb_packets_errors", ctypes.c_uint32),
]
_pack_ = 1


class file_write_basic:
"""Write a file to the device"""

Expand Down
123 changes: 123 additions & 0 deletions src/infuse_iot/rpc_wrappers/zperf_upload.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
#!/usr/bin/env python3

import ctypes
import ipaddress
import socket

import tabulate

import infuse_iot.generated.rpc_definitions as defs
from infuse_iot.commands import InfuseRpcCommand
from infuse_iot.zephyr.errno import errno
from infuse_iot.zephyr.net import AddressFamily, SockType


class zperf_upload(InfuseRpcCommand, defs.zperf_upload):
@classmethod
def add_parser(cls, parser):
parser.add_argument("--address", "-a", type=str, required=True, help="Peer IP address")
parser.add_argument("--port", "-p", type=int, default=5001, help="Peer port")
socket_group = parser.add_mutually_exclusive_group(required=True)
socket_group.add_argument(
"--tcp",
dest="sock_type",
action="store_const",
const=SockType.SOCK_STREAM,
help="TCP protocol",
)
socket_group.add_argument(
"--udp",
dest="sock_type",
action="store_const",
const=SockType.SOCK_DGRAM,
help="UDP protocol",
)
source_group = parser.add_mutually_exclusive_group()
source_group.add_argument(
"--constant",
dest="data_source",
action="store_const",
const=defs.rpc_enum_zperf_data_source.CONSTANT,
default=defs.rpc_enum_zperf_data_source.CONSTANT,
help="Constant data payload ('z')",
)
source_group.add_argument(
"--random",
dest="data_source",
action="store_const",
const=defs.rpc_enum_zperf_data_source.RANDOM,
help="Random data payload",
)
source_group.add_argument(
"--onboard",
dest="data_source",
action="store_const",
const=defs.rpc_enum_zperf_data_source.FLASH_ONBOARD,
help="Read from onboard flash logger",
)
source_group.add_argument(
"--removable",
dest="data_source",
action="store_const",
const=defs.rpc_enum_zperf_data_source.FLASH_REMOVABLE,
help="Read from removable flash logger",
)
parser.add_argument("--encrypt", action="store_true", help="Encrypt payloads before transmission")
parser.add_argument("--duration", type=int, default=5000, help="Duration to run test over in milliseconds")
parser.add_argument("--rate-kbps", type=int, default=0, help="Desired upload rate in kbps")
parser.add_argument("--payload-size", type=int, default=512, help="Payload size")

def __init__(self, args):
self.peer_addr = ipaddress.ip_address(args.address)
self.peer_port = args.port
self.sock_type = args.sock_type
self.data_source = args.data_source
if args.encrypt:
self.data_source |= defs.rpc_enum_zperf_data_source.ENCRYPT
self.duration = args.duration
self.rate = args.rate_kbps
self.packet_size = args.payload_size
if self.sock_type == SockType.SOCK_DGRAM:
# Add the UDP client header size to the requested payload size
self.packet_size += 40

def request_struct(self):
peer_family = (
AddressFamily.AF_INET if isinstance(self.peer_addr, ipaddress.IPv4Address) else AddressFamily.AF_INET6
)
addr_bytes = (16 * ctypes.c_uint8)(*self.peer_addr.packed)
peer = defs.rpc_struct_sockaddr(
sin_family=peer_family,
sin_port=socket.htons(self.peer_port),
sin_addr=addr_bytes,
scope_id=0,
)

return self.request(
peer_address=peer,
sock_type=self.sock_type,
data_source=self.data_source,
duration_ms=self.duration,
rate_kbps=self.rate,
packet_size=self.packet_size,
)

def request_json(self):
return {}

def handle_response(self, return_code, response):
if return_code != 0:
print(f"Failed to run zperf ({errno.strerror(-return_code)})")
return

throughput_bps = 8 * response.total_len / (response.client_time_in_us / 1000)
print(f"Average Throughput: {throughput_bps / 1000:.3f} kbps")
if self.sock_type == SockType.SOCK_DGRAM:
recv = 100 * response.nb_packets_rcvd / response.nb_packets_sent
loss = 100 * response.nb_packets_lost / response.nb_packets_sent
print(f" Packet Recv: {recv:6.2f}%")
print(f" Packet Loss: {loss:6.2f}%")
results = []
for field_name, _ in response._fields_:
results.append([field_name, getattr(response, field_name)])
print(tabulate.tabulate(results))
38 changes: 30 additions & 8 deletions src/infuse_iot/tools/bt_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@

from infuse_iot.commands import InfuseCommand
from infuse_iot.common import InfuseType
from infuse_iot.epacket import interface
from infuse_iot.socket_comms import (
ClientNotificationConnectionDropped,
ClientNotificationEpacketReceived,
GatewayRequestConnectionRequest,
LocalClient,
default_multicast_address,
)
from infuse_iot.tdf import TDF


class SubCommand(InfuseCommand):
Expand All @@ -24,24 +26,44 @@ class SubCommand(InfuseCommand):

def __init__(self, args):
self._client = LocalClient(default_multicast_address(), 60.0)
self._decoder = TDF()
self._id = args.id
self._data = args.data

@classmethod
def add_parser(cls, parser):
parser.add_argument("--id", type=lambda x: int(x, 0), help="Infuse ID to receive logs for")
parser.add_argument("--data", action="store_true", help="Subscribe to the data characteristic as well")

def run(self):
try:
with self._client.connection(self._id, GatewayRequestConnectionRequest.DataType.LOGGING) as _:
while rsp := self._client.receive():
if isinstance(rsp, ClientNotificationConnectionDropped):
types = GatewayRequestConnectionRequest.DataType.LOGGING
if self._data:
types |= GatewayRequestConnectionRequest.DataType.DATA
with self._client.connection(self._id, types) as _:
while evt := self._client.receive():
if evt is None:
continue
if isinstance(evt, ClientNotificationConnectionDropped):
print(f"Connection to {self._id:016x} lost")
break
if (
isinstance(rsp, ClientNotificationEpacketReceived)
and rsp.epacket.ptype == InfuseType.SERIAL_LOG
):
print(rsp.epacket.payload.decode("utf-8"), end="")
if not isinstance(evt, ClientNotificationEpacketReceived):
continue
source = evt.epacket.route[0]
if source.infuse_id != self._id:
continue
if source.interface != interface.ID.BT_CENTRAL:
continue

if evt.epacket.ptype == InfuseType.SERIAL_LOG:
print(evt.epacket.payload.decode("utf-8"), end="")
if evt.epacket.ptype == InfuseType.TDF:
for tdf in self._decoder.decode(evt.epacket.payload):
t = tdf.data[-1]
if len(tdf.data) > 1:
print(f"{tdf.time:.3f} TDF: {t.name}[{len(tdf.data)}]")
else:
print(f"{tdf.time:.3f} TDF: {t.name}")

except KeyboardInterrupt:
print(f"Disconnecting from {self._id:016x}")
Expand Down
2 changes: 1 addition & 1 deletion src/infuse_iot/util/soc/nrf.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def soc(device_info):


class nRF53(NRFFamily):
FICT_ADDRESS = 0x00FF0000
FICR_ADDRESS = 0x00FF0000
DEVICE_ID_OFFSET = 0x204
CUSTOMER_OFFSET = 0x100

Expand Down
44 changes: 44 additions & 0 deletions src/infuse_iot/zephyr/net.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/usr/bin/env python3

import enum


class IPProtocol(enum.IntEnum):
IPPROTO_IP = 0
IPPROTO_ICMP = 1
IPPROTO_IGMP = 2
IPPROTO_ETH_P_ALL = 3
IPPROTO_IPIP = 4
IPPROTO_TCP = 6
IPPROTO_UDP = 17
IPPROTO_IPV6 = 41
IPPROTO_ICMPV6 = 58
IPPROTO_RAW = 255


class ProtocolFamily(enum.IntEnum):
PF_UNSPEC = 0
PF_INET = 1
PF_INET6 = 2
PF_PACKET = 3
PF_CAN = 4
PF_NET_MGMT = 5
PF_LOCAL = 6
PF_UNIX = PF_LOCAL


class AddressFamily(enum.IntEnum):
AF_UNSPEC = ProtocolFamily.PF_UNSPEC
AF_INET = ProtocolFamily.PF_INET
AF_INET6 = ProtocolFamily.PF_INET6
AF_PACKET = ProtocolFamily.PF_PACKET
AF_CAN = ProtocolFamily.PF_CAN
AF_NET_MGMT = ProtocolFamily.PF_NET_MGMT
AF_LOCAL = ProtocolFamily.PF_LOCAL
AF_UNIX = ProtocolFamily.PF_UNIX


class SockType(enum.IntEnum):
SOCK_STREAM = 1
SOCK_DGRAM = 2
SOCK_RAW = 3