WeaveInterface compatibility on Android
Some checks are pending
Build Reticulum / test (push) Waiting to run
Build Reticulum / package (push) Blocked by required conditions
Build Reticulum / release (push) Blocked by required conditions

This commit is contained in:
Mark Qvist 2025-10-29 15:44:47 +01:00
parent ddf14e5636
commit 39a63b0643
3 changed files with 84 additions and 68 deletions

View file

@ -96,7 +96,8 @@ class WDCL():
RNS.log("You can install one with the command: python3 -m pip install pyserial", RNS.LOG_CRITICAL) RNS.log("You can install one with the command: python3 -m pip install pyserial", RNS.LOG_CRITICAL)
RNS.panic() RNS.panic()
if port == None: raise ValueError("No port specified") if not RNS.vendor.platformutils.is_android():
if port == None: raise ValueError("No port specified")
self.switch_identity = owner.switch_identity self.switch_identity = owner.switch_identity
self.switch_id = self.switch_identity.sig_pub_bytes[-4:] self.switch_id = self.switch_identity.sig_pub_bytes[-4:]
@ -130,8 +131,7 @@ class WDCL():
if self.as_interface: if self.as_interface:
try: try:
self.open_port() self.open_port()
if self.serial and self.serial.is_open: self.configure_device()
if self.serial.is_open: self.configure_device()
else: raise IOError("Could not open serial port") else: raise IOError("Could not open serial port")
except Exception as e: except Exception as e:
@ -176,59 +176,60 @@ class WDCL():
dsrdtr = False) dsrdtr = False)
else: else:
# Get device parameters if self.port != None:
from usb4a import usb # Get device parameters
device = usb.get_usb_device(self.port) from usb4a import usb
if device: device = usb.get_usb_device(self.port)
vid = device.getVendorId() if device:
pid = device.getProductId() vid = device.getVendorId()
pid = device.getProductId()
# Driver overrides for speficic chips # Driver overrides for speficic chips
proxy = self.pyserial.get_serial_port proxy = self.pyserial.get_serial_port
if vid == 0x1A86 and pid == 0x55D4: if vid == 0x1A86 and pid == 0x55D4:
# Force CDC driver for Qinheng CH34x # Force CDC driver for Qinheng CH34x
RNS.log(str(self)+" using CDC driver for "+RNS.hexrep(vid)+":"+RNS.hexrep(pid), RNS.LOG_DEBUG) RNS.log(str(self)+" using CDC driver for "+RNS.hexrep(vid)+":"+RNS.hexrep(pid), RNS.LOG_DEBUG)
from usbserial4a.cdcacmserial4a import CdcAcmSerial from usbserial4a.cdcacmserial4a import CdcAcmSerial
proxy = CdcAcmSerial proxy = CdcAcmSerial
self.serial = proxy( self.serial = proxy(
self.port, self.port,
baudrate = self.speed, baudrate = self.speed,
bytesize = self.databits, bytesize = self.databits,
parity = self.parity, parity = self.parity,
stopbits = self.stopbits, stopbits = self.stopbits,
xonxoff = False, xonxoff = False,
rtscts = False, rtscts = False,
timeout = None, timeout = None,
inter_byte_timeout = None, inter_byte_timeout = None,
# write_timeout = wtimeout, # write_timeout = wtimeout,
dsrdtr = False, dsrdtr = False,
) )
if vid == 0x0403: if vid == 0x0403:
# Hardware parameters for FTDI devices @ 115200 baud # Hardware parameters for FTDI devices @ 115200 baud
self.serial.DEFAULT_READ_BUFFER_SIZE = 16 * 1024 self.serial.DEFAULT_READ_BUFFER_SIZE = 16 * 1024
self.serial.USB_READ_TIMEOUT_MILLIS = 100 self.serial.USB_READ_TIMEOUT_MILLIS = 100
self.serial.timeout = 0.1 self.serial.timeout = 0.1
elif vid == 0x10C4: elif vid == 0x10C4:
# Hardware parameters for SiLabs CP210x @ 115200 baud # Hardware parameters for SiLabs CP210x @ 115200 baud
self.serial.DEFAULT_READ_BUFFER_SIZE = 64 self.serial.DEFAULT_READ_BUFFER_SIZE = 64
self.serial.USB_READ_TIMEOUT_MILLIS = 12 self.serial.USB_READ_TIMEOUT_MILLIS = 12
self.serial.timeout = 0.012 self.serial.timeout = 0.012
elif vid == 0x1A86 and pid == 0x55D4: elif vid == 0x1A86 and pid == 0x55D4:
# Hardware parameters for Qinheng CH34x @ 115200 baud # Hardware parameters for Qinheng CH34x @ 115200 baud
self.serial.DEFAULT_READ_BUFFER_SIZE = 64 self.serial.DEFAULT_READ_BUFFER_SIZE = 64
self.serial.USB_READ_TIMEOUT_MILLIS = 12 self.serial.USB_READ_TIMEOUT_MILLIS = 12
self.serial.timeout = 0.1 self.serial.timeout = 0.1
else: else:
# Default values # Default values
self.serial.DEFAULT_READ_BUFFER_SIZE = 1 * 1024 self.serial.DEFAULT_READ_BUFFER_SIZE = 1 * 1024
self.serial.USB_READ_TIMEOUT_MILLIS = 100 self.serial.USB_READ_TIMEOUT_MILLIS = 100
self.serial.timeout = 0.1 self.serial.timeout = 0.1
RNS.log(str(self)+" USB read buffer size set to "+RNS.prettysize(self.serial.DEFAULT_READ_BUFFER_SIZE), RNS.LOG_DEBUG) RNS.log(str(self)+" USB read buffer size set to "+RNS.prettysize(self.serial.DEFAULT_READ_BUFFER_SIZE), RNS.LOG_DEBUG)
RNS.log(str(self)+" USB read timeout set to "+str(self.serial.USB_READ_TIMEOUT_MILLIS)+"ms", RNS.LOG_DEBUG) RNS.log(str(self)+" USB read timeout set to "+str(self.serial.USB_READ_TIMEOUT_MILLIS)+"ms", RNS.LOG_DEBUG)
RNS.log(str(self)+" USB write timeout set to "+str(self.serial.USB_WRITE_TIMEOUT_MILLIS)+"ms", RNS.LOG_DEBUG) RNS.log(str(self)+" USB write timeout set to "+str(self.serial.USB_WRITE_TIMEOUT_MILLIS)+"ms", RNS.LOG_DEBUG)
def close(self): def close(self):
self.should_run = False self.should_run = False
@ -280,9 +281,6 @@ class WDCL():
while self.serial.is_open: while self.serial.is_open:
data_in = self.serial.read(1500) data_in = self.serial.read(1500)
if len(data_in) > 0: if len(data_in) > 0:
# TODO: Remove debug
# self.device.receiver.log(f"Read {len(data_in)}")
# self.device.receiver.log(f"Raw:\n {RNS.hexrep(data_in).replace("7e", "[bold red]7e[/bold red]")}")
self.frame_buffer += data_in self.frame_buffer += data_in
flags_remaining = True flags_remaining = True
while flags_remaining: while flags_remaining:
@ -312,8 +310,6 @@ class WDCL():
self.owner.wlog("Will attempt to reconnect the interface periodically.") self.owner.wlog("Will attempt to reconnect the interface periodically.")
RNS.trace_exception(e) RNS.trace_exception(e)
RNS.log("READ LOOP EXIT")
self.online = False self.online = False
self.wdcl_connected = False self.wdcl_connected = False
try: self.serial.close() try: self.serial.close()
@ -327,21 +323,21 @@ class WDCL():
while not self.online: while not self.online:
try: try:
time.sleep(5) time.sleep(5)
if self.as_interface: RNS.log("Attempting to reconnect serial port "+str(self.port)+" for "+str(self)+"...", RNS.LOG_INFO) if self.as_interface: RNS.log("Attempting to reconnect serial port "+str(self.port)+" for "+str(self.owner)+"...", RNS.LOG_DEBUG)
else: self.owner.wlog("Attempting to reconnect serial port "+str(self.port.device)+" for "+str(self)+"...") else: self.owner.wlog("Attempting to reconnect serial port "+str(self.port.device)+" for "+str(self.owner)+"...")
self.open_port() self.open_port()
if self.serial.is_open: self.configure_device() if self.serial and self.serial.is_open: self.configure_device()
except Exception as e: except Exception as e:
if self.as_interface: RNS.log("Error while reconnecting port, the contained exception was: "+str(e), RNS.LOG_ERROR) if self.as_interface: RNS.log("Error while reconnecting port, the contained exception was: "+str(e), RNS.LOG_ERROR)
else: self.owner.wlog("Error while reconnecting port, the contained exception was: "+str(e)) else: self.owner.wlog("Error while reconnecting port, the contained exception was: "+str(e))
RNS.trace_exception(e)
self.reconnecting = False self.reconnecting = False
if self.as_interface: RNS.log("Reconnected serial port for "+str(self), RNS.LOG_INFO) if self.as_interface: RNS.log("Reconnected serial port for "+str(self), RNS.LOG_INFO)
else: self.owner.wlog("Reconnected serial port for "+str(self)) else: self.owner.wlog("Reconnected serial port for "+str(self))
def __str__(self): def __str__(self):
if self.as_interface: if self.as_interface: return f"WDCL over {self.port}"
return self.port
else: else:
if self.port.serial_number: sn_str = f" {self.port.serial_number}" if self.port.serial_number: sn_str = f" {self.port.serial_number}"
else: sn_str = "" else: sn_str = ""
@ -394,6 +390,7 @@ class Evt():
ET_PROTO_WEAVE_RUNNING = 0x3101 ET_PROTO_WEAVE_RUNNING = 0x3101
ET_PROTO_WEAVE_EP_ALIVE = 0x3102 ET_PROTO_WEAVE_EP_ALIVE = 0x3102
ET_PROTO_WEAVE_EP_TIMEOUT = 0x3103 ET_PROTO_WEAVE_EP_TIMEOUT = 0x3103
ET_PROTO_WEAVE_EP_VIA = 0x3104
ET_SRVCTL_REMOTE_DISPLAY = 0xA000 ET_SRVCTL_REMOTE_DISPLAY = 0xA000
ET_INTERFACE_REGISTERED = 0xD000 ET_INTERFACE_REGISTERED = 0xD000
ET_STAT_STATE = 0xE000 ET_STAT_STATE = 0xE000
@ -458,7 +455,7 @@ class Evt():
ET_PROTO_WDCL_HOST_ENDPOINT: "Weave host endpoint", ET_PROTO_WDCL_HOST_ENDPOINT: "Weave host endpoint",
ET_PROTO_WEAVE_INIT: "Weave protocol initialization", ET_PROTO_WEAVE_INIT: "Weave protocol initialization",
ET_PROTO_WEAVE_RUNNING: "Weave protocol activation", ET_PROTO_WEAVE_RUNNING: "Weave protocol activation",
ET_PROTO_WEAVE_EP_ALIVE: "Weave endpoint appeared", ET_PROTO_WEAVE_EP_ALIVE: "Weave endpoint alive",
ET_PROTO_WEAVE_EP_TIMEOUT: "Weave endpoint disappeared", ET_PROTO_WEAVE_EP_TIMEOUT: "Weave endpoint disappeared",
ET_SRVCTL_REMOTE_DISPLAY: "Remote display service control event", ET_SRVCTL_REMOTE_DISPLAY: "Remote display service control event",
ET_INTERFACE_REGISTERED: "Interface registration", ET_INTERFACE_REGISTERED: "Interface registration",
@ -566,6 +563,7 @@ class WeaveEndpoint():
def __init__(self, endpoint_addr): def __init__(self, endpoint_addr):
self.endpoint_addr = endpoint_addr self.endpoint_addr = endpoint_addr
self.alive = time.time() self.alive = time.time()
self.via = None
self.received = deque(maxlen=WeaveEndpoint.QUEUE_LEN) self.received = deque(maxlen=WeaveEndpoint.QUEUE_LEN)
def receive(self, data): def receive(self, data):
@ -701,6 +699,10 @@ class WeaveDevice():
if self.as_interface: self.rns_interface.add_peer(endpoint_id) if self.as_interface: self.rns_interface.add_peer(endpoint_id)
def endpoint_via(self, endpoint_id, via_switch_id):
if endpoint_id in self.endpoints: self.endpoints[endpoint_id].via = via_switch_id
if self.as_interface: self.rns_interface.endpoint_via(endpoint_id, via_switch_id)
def deliver_packet(self, endpoint_id, data): def deliver_packet(self, endpoint_id, data):
packet_data = endpoint_id+data packet_data = endpoint_id+data
self.wdcl_send_command(Cmd.WDCL_CMD_ENDPOINT_PKT, packet_data) self.wdcl_send_command(Cmd.WDCL_CMD_ENDPOINT_PKT, packet_data)
@ -760,8 +762,8 @@ class WeaveDevice():
# Handle system event signalling # Handle system event signalling
if frame.event == Evt.ET_PROTO_WDCL_CONNECTION: self.connection.wdcl_connected = True if frame.event == Evt.ET_PROTO_WDCL_CONNECTION: self.connection.wdcl_connected = True
if frame.event == Evt.ET_PROTO_WDCL_HOST_ENDPOINT and len(frame.data) == self.WEAVE_ENDPOINT_ID_LEN: self.endpoint_id = frame.data if frame.event == Evt.ET_PROTO_WDCL_HOST_ENDPOINT and len(frame.data) == self.WEAVE_ENDPOINT_ID_LEN: self.endpoint_id = frame.data
if frame.event == Evt.ET_PROTO_WEAVE_EP_ALIVE and len(frame.data) == 8: if frame.event == Evt.ET_PROTO_WEAVE_EP_ALIVE and len(frame.data) == self.WEAVE_ENDPOINT_ID_LEN: self.endpoint_alive(frame.data)
self.endpoint_alive(frame.data) if frame.event == Evt.ET_PROTO_WEAVE_EP_VIA and len(frame.data) == self.WEAVE_ENDPOINT_ID_LEN+self.WEAVE_SWITCH_ID_LEN: self.endpoint_via(frame.data[:self.WEAVE_ENDPOINT_ID_LEN], frame.data[self.WEAVE_ENDPOINT_ID_LEN:])
elif frame.event == Evt.ET_STAT_TASK_CPU: self.active_tasks[frame.data[1:].decode("utf-8")] = { "cpu_load": frame.data[0], "timestamp": time.time() } elif frame.event == Evt.ET_STAT_TASK_CPU: self.active_tasks[frame.data[1:].decode("utf-8")] = { "cpu_load": frame.data[0], "timestamp": time.time() }
elif frame.event == Evt.ET_STAT_CPU: elif frame.event == Evt.ET_STAT_CPU:
self.cpu_load = frame.data[0] self.cpu_load = frame.data[0]
@ -829,7 +831,7 @@ class WeaveInterface(Interface):
DEFAULT_IFAC_SIZE = 16 DEFAULT_IFAC_SIZE = 16
PEERING_TIMEOUT = 20.0 PEERING_TIMEOUT = 20.0
BITRATE_GUESS = 500*1000 BITRATE_GUESS = 250*1000
MULTI_IF_DEQUE_LEN = 48 MULTI_IF_DEQUE_LEN = 48
MULTI_IF_DEQUE_TTL = 0.75 MULTI_IF_DEQUE_TTL = 0.75
@ -871,6 +873,7 @@ class WeaveInterface(Interface):
self.port = port self.port = port
self.switch_identity = RNS.Identity() self.switch_identity = RNS.Identity()
self.owner = owner self.owner = owner
self.hw_errors = []
self._online = False self._online = False
self.final_init_done = False self.final_init_done = False
self.peers = {} self.peers = {}
@ -925,10 +928,11 @@ class WeaveInterface(Interface):
def peer_count(self): def peer_count(self):
return len(self.spawned_interfaces) return len(self.spawned_interfaces)
def endpoint_via(self, endpoint_addr, via_switch_addr):
if endpoint_addr in self.peers: self.peers[endpoint_addr][2].via_switch_id = via_switch_addr
def add_peer(self, endpoint_addr): def add_peer(self, endpoint_addr):
if not endpoint_addr in self.peers: if not endpoint_addr in self.peers:
self.peers[endpoint_addr] = [endpoint_addr, time.time()]
spawned_interface = WeaveInterfacePeer(self, endpoint_addr) spawned_interface = WeaveInterfacePeer(self, endpoint_addr)
spawned_interface.OUT = self.OUT spawned_interface.OUT = self.OUT
spawned_interface.IN = self.IN spawned_interface.IN = self.IN
@ -966,7 +970,9 @@ class WeaveInterface(Interface):
self.spawned_interfaces[endpoint_addr].detach() self.spawned_interfaces[endpoint_addr].detach()
self.spawned_interfaces[endpoint_addr].teardown() self.spawned_interfaces[endpoint_addr].teardown()
self.spawned_interfaces.pop(spawned_interface) self.spawned_interfaces.pop(spawned_interface)
self.spawned_interfaces[endpoint_addr] = spawned_interface self.spawned_interfaces[endpoint_addr] = spawned_interface
self.peers[endpoint_addr] = [endpoint_addr, time.time(), spawned_interface]
RNS.log(f"{self} added peer {RNS.hexrep(endpoint_addr)}", RNS.LOG_DEBUG) RNS.log(f"{self} added peer {RNS.hexrep(endpoint_addr)}", RNS.LOG_DEBUG)
else: else:
@ -1010,6 +1016,7 @@ class WeaveInterfacePeer(Interface):
self.owner = owner self.owner = owner
self.parent_interface = owner self.parent_interface = owner
self.endpoint_addr = endpoint_addr self.endpoint_addr = endpoint_addr
self.via_switch_id = None
self.peer_addr = None self.peer_addr = None
self.addr_info = None self.addr_info = None
self.HW_MTU = self.owner.HW_MTU self.HW_MTU = self.owner.HW_MTU

View file

@ -39,6 +39,7 @@ if get_platform() == "android":
from .Interfaces import UDPInterface from .Interfaces import UDPInterface
from .Interfaces import I2PInterface from .Interfaces import I2PInterface
from .Interfaces import RNodeMultiInterface from .Interfaces import RNodeMultiInterface
from .Interfaces import WeaveInterface
from .Interfaces.Android import RNodeInterface from .Interfaces.Android import RNodeInterface
from .Interfaces.Android import SerialInterface from .Interfaces.Android import SerialInterface
from .Interfaces.Android import KISSInterface from .Interfaces.Android import KISSInterface
@ -1037,6 +1038,10 @@ class Reticulum:
if interface.switch_id != None: ifstats["switch_id"] = RNS.hexrep(interface.switch_id) if interface.switch_id != None: ifstats["switch_id"] = RNS.hexrep(interface.switch_id)
else: ifstats["switch_id"] = None else: ifstats["switch_id"] = None
if hasattr(interface, "via_switch_id"):
if interface.via_switch_id != None: ifstats["via_switch_id"] = RNS.hexrep(interface.via_switch_id)
else: ifstats["via_switch_id"] = None
if hasattr(interface, "endpoint_id"): if hasattr(interface, "endpoint_id"):
if interface.endpoint_id != None: ifstats["endpoint_id"] = RNS.hexrep(interface.endpoint_id) if interface.endpoint_id != None: ifstats["endpoint_id"] = RNS.hexrep(interface.endpoint_id)
else: ifstats["endpoint_id"] = None else: ifstats["endpoint_id"] = None

View file

@ -370,6 +370,10 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=
if ifstat["endpoint_id"] != None: print(" Endpoint : {v}".format(v=str(ifstat["endpoint_id"]))) if ifstat["endpoint_id"] != None: print(" Endpoint : {v}".format(v=str(ifstat["endpoint_id"])))
else: print(" Endpoint : Unknown") else: print(" Endpoint : Unknown")
if "via_switch_id" in ifstat:
if ifstat["via_switch_id"] != None: print(" Via : {v}".format(v=str(ifstat["via_switch_id"])))
else: print(" Via : Unknown")
if "peers" in ifstat and ifstat["peers"] != None: if "peers" in ifstat and ifstat["peers"] != None:
print(" Peers : {np} reachable".format(np=ifstat["peers"])) print(" Peers : {np} reachable".format(np=ifstat["peers"]))