WeaveInterface.py
1 # Reticulum License 2 # 3 # Copyright (c) 2016-2025 Mark Qvist 4 # 5 # Permission is hereby granted, free of charge, to any person obtaining a copy 6 # of this software and associated documentation files (the "Software"), to deal 7 # in the Software without restriction, including without limitation the rights 8 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 # copies of the Software, and to permit persons to whom the Software is 10 # furnished to do so, subject to the following conditions: 11 # 12 # - The Software shall not be used in any kind of system which includes amongst 13 # its functions the ability to purposefully do harm to human beings. 14 # 15 # - The Software shall not be used, directly or indirectly, in the creation of 16 # an artificial intelligence, machine learning or language model training 17 # dataset, including but not limited to any use that contributes to the 18 # training or development of such a model or algorithm. 19 # 20 # - The above copyright notice and this permission notice shall be included in 21 # all copies or substantial portions of the Software. 22 # 23 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 # SOFTWARE. 30 31 import RNS 32 import threading 33 import time 34 35 from collections import deque 36 from RNS.Interfaces.Interface import Interface 37 38 class HDLC(): 39 FLAG = 0x7E 40 ESC = 0x7D 41 ESC_MASK = 0x20 42 43 @staticmethod 44 def escape(data): 45 data = data.replace(bytes([HDLC.ESC]), bytes([HDLC.ESC, HDLC.ESC^HDLC.ESC_MASK])) 46 data = data.replace(bytes([HDLC.FLAG]), bytes([HDLC.ESC, HDLC.FLAG^HDLC.ESC_MASK])) 47 return data 48 49 class WDCL(): 50 WDCL_T_DISCOVER = 0x00 51 WDCL_T_CONNECT = 0x01 52 WDCL_T_CMD = 0x02 53 WDCL_T_LOG = 0x03 54 WDCL_T_DISP = 0x04 55 WDCL_T_ENDPOINT_PKT = 0x05 56 WDCL_T_ENCAP_PROTO = 0x06 57 58 WDCL_BROADCAST = bytes([0xFF, 0xFF, 0xFF, 0xFF]) 59 60 WDCL_HANDSHAKE_TIMEOUT = 2 61 62 HEADER_MINSIZE = 4+1 63 MAX_CHUNK = 32768 64 port = None 65 speed = None 66 databits = None 67 parity = None 68 stopbits = None 69 serial = None 70 71 def __init__(self, owner, device, port, as_interface=False): 72 import importlib.util 73 if RNS.vendor.platformutils.is_android(): 74 self.on_android = True 75 if importlib.util.find_spec('usbserial4a') != None: 76 from usbserial4a import serial4a as serial 77 parity = "N" 78 79 if importlib.util.find_spec('jnius') == None: 80 RNS.log("Could not load jnius API wrapper for Android, RNode interface cannot be created.", RNS.LOG_CRITICAL) 81 RNS.log("This probably means you are trying to use an USB-based interface from within Termux or similar.", RNS.LOG_CRITICAL) 82 RNS.log("This is currently not possible, due to this environment limiting access to the native Android APIs.", RNS.LOG_CRITICAL) 83 RNS.panic() 84 85 else: 86 RNS.log("Could not load USB serial module for Android, Weave interface cannot be created.", RNS.LOG_CRITICAL) 87 RNS.panic() 88 89 else: 90 self.on_android = False 91 if importlib.util.find_spec('serial') != None: 92 import serial 93 parity = serial.PARITY_NONE 94 else: 95 RNS.log("Using the Weave interface requires a serial communication module to be installed.", RNS.LOG_CRITICAL) 96 RNS.log("You can install one with the command: python3 -m pip install pyserial", RNS.LOG_CRITICAL) 97 RNS.panic() 98 99 if not RNS.vendor.platformutils.is_android(): 100 if port == None: raise ValueError("No port specified") 101 102 self.supports_discovery = True 103 self.discovery_frequency = None 104 self.discovery_bandwidth = None 105 self.discovery_channel = None 106 self.discovery_modulation = None 107 108 self.switch_identity = owner.switch_identity 109 self.switch_id = self.switch_identity.sig_pub_bytes[-4:] 110 self.switch_pub_bytes = self.switch_identity.sig_pub_bytes 111 112 self.rxb = 0 113 self.txb = 0 114 self.owner = owner 115 self.as_interface = as_interface 116 self.device = device 117 self.device.connection = self 118 self.pyserial = serial 119 self.serial = None 120 self.port = port 121 self.speed = 3000000 122 self.databits = 8 123 self.parity = parity 124 self.stopbits = 1 125 self.timeout = 100 126 self.online = False 127 self.frame_buffer = b"" 128 self.next_tx = 0 129 self.should_run = True 130 self.receiver = None 131 self.wdcl_connected = False 132 self.reconnecting = False 133 self.frame_queue = deque() 134 if not self.as_interface: 135 self.id = RNS.Identity.full_hash(port.hwid.encode("utf-8")) 136 137 if self.as_interface: 138 try: 139 self.open_port() 140 if self.serial and self.serial.is_open: self.configure_device() 141 else: raise IOError("Could not open serial port") 142 143 except Exception as e: 144 RNS.log("Could not open serial port for interface "+str(self), RNS.LOG_ERROR) 145 RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR) 146 RNS.log("Reticulum will attempt to bring up this interface periodically", RNS.LOG_ERROR) 147 if not self.owner.detached and not self.reconnecting: 148 thread = threading.Thread(target=self.reconnect_port) 149 thread.daemon = True 150 thread.start() 151 152 else: 153 try: self.open_port() 154 except Exception as e: 155 self.owner.wlog("Could not open serial port") 156 raise e 157 158 if self.serial.is_open: self.configure_device() 159 else: raise IOError("Could not open serial port") 160 161 162 def open_port(self): 163 if not self.on_android: 164 if self.as_interface: 165 RNS.log(f"Opening serial port {self.port}...", RNS.LOG_VERBOSE) 166 target_port = self.port 167 else: 168 self.owner.wlog(f"Opening serial port {self.port.device}...") 169 target_port = self.port.device 170 171 self.serial = self.pyserial.Serial( 172 port = target_port, 173 baudrate = self.speed, 174 bytesize = self.databits, 175 parity = self.parity, 176 stopbits = self.stopbits, 177 xonxoff = False, 178 rtscts = False, 179 timeout = 0.250, 180 inter_byte_timeout = None, 181 write_timeout = None, 182 dsrdtr = False) 183 184 else: 185 if self.port != None: 186 # Get device parameters 187 from usb4a import usb 188 device = usb.get_usb_device(self.port) 189 if device: 190 vid = device.getVendorId() 191 pid = device.getProductId() 192 193 # Driver overrides for speficic chips 194 proxy = self.pyserial.get_serial_port 195 if vid == 0x1A86 and pid == 0x55D4: 196 # Force CDC driver for Qinheng CH34x 197 RNS.log(str(self)+" using CDC driver for "+RNS.hexrep(vid)+":"+RNS.hexrep(pid), RNS.LOG_DEBUG) 198 from usbserial4a.cdcacmserial4a import CdcAcmSerial 199 proxy = CdcAcmSerial 200 201 self.serial = proxy( 202 self.port, 203 baudrate = self.speed, 204 bytesize = self.databits, 205 parity = self.parity, 206 stopbits = self.stopbits, 207 xonxoff = False, 208 rtscts = False, 209 timeout = None, 210 inter_byte_timeout = None, 211 # write_timeout = wtimeout, 212 dsrdtr = False, 213 ) 214 215 if vid == 0x0403: 216 # Hardware parameters for FTDI devices @ 115200 baud 217 self.serial.DEFAULT_READ_BUFFER_SIZE = 16 * 1024 218 self.serial.USB_READ_TIMEOUT_MILLIS = 100 219 self.serial.timeout = 0.1 220 elif vid == 0x10C4: 221 # Hardware parameters for SiLabs CP210x @ 115200 baud 222 self.serial.DEFAULT_READ_BUFFER_SIZE = 64 223 self.serial.USB_READ_TIMEOUT_MILLIS = 12 224 self.serial.timeout = 0.012 225 elif vid == 0x1A86 and pid == 0x55D4: 226 # Hardware parameters for Qinheng CH34x @ 115200 baud 227 self.serial.DEFAULT_READ_BUFFER_SIZE = 64 228 self.serial.USB_READ_TIMEOUT_MILLIS = 12 229 self.serial.timeout = 0.1 230 else: 231 # Default values 232 self.serial.DEFAULT_READ_BUFFER_SIZE = 1 * 1024 233 self.serial.USB_READ_TIMEOUT_MILLIS = 100 234 self.serial.timeout = 0.1 235 236 RNS.log(str(self)+" USB read buffer size set to "+RNS.prettysize(self.serial.DEFAULT_READ_BUFFER_SIZE), RNS.LOG_DEBUG) 237 RNS.log(str(self)+" USB read timeout set to "+str(self.serial.USB_READ_TIMEOUT_MILLIS)+"ms", RNS.LOG_DEBUG) 238 RNS.log(str(self)+" USB write timeout set to "+str(self.serial.USB_WRITE_TIMEOUT_MILLIS)+"ms", RNS.LOG_DEBUG) 239 240 def close(self): 241 self.should_run = False 242 self.online = False 243 self.wdcl_connected = False 244 if self.serial: 245 self.serial.close() 246 if self.as_interface: RNS.log((f"Closed serial port {str(self.port)} for {str(self)}"), RNS.LOG_VERBOSE) 247 else: self.owner.wlog(f"Closed serial port {str(self.port.device)} for {str(self)}") 248 249 def configure_device(self): 250 thread = threading.Thread(target=self.read_loop) 251 thread.daemon = True 252 thread.start() 253 if self.as_interface: RNS.log(f"Serial port {self.port} is now open, discovering remote device...", RNS.LOG_VERBOSE) 254 else: self.owner.wlog("Serial port "+self.port.device+" is now open") 255 self.device.discover() 256 257 if self.as_interface: 258 timeout = time.time() + self.WDCL_HANDSHAKE_TIMEOUT 259 while time.time() < timeout and not self.wdcl_connected: time.sleep(0.1) 260 if not self.wdcl_connected: 261 raise IOError(f"WDCL connection handshake timed out for {self}") 262 self.online = False 263 self.wdcl_connected = False 264 if self.serial: 265 try: self.serial.close() 266 except Exception as e: RNS.log("Error while cleaning serial connection: {e}", RNS.LOG_ERROR) 267 268 self.online = True 269 270 def process_incoming(self, data): 271 self.rxb += len(data) 272 if self.device: 273 while len(self.frame_queue): self.device.incoming_frame(self.frame_queue.pop()) 274 self.device.incoming_frame(data) 275 else: self.frame_queue.append(data) 276 277 def process_outgoing(self, data): 278 if self.serial.is_open: 279 data = bytes([HDLC.FLAG])+HDLC.escape(data)+bytes([HDLC.FLAG]) 280 written = self.serial.write(data) 281 self.txb += len(data) 282 if written != len(data): 283 raise IOError("Serial interface only wrote "+str(written)+" bytes of "+str(len(data))) 284 285 def read_loop(self): 286 try: 287 while self.serial.is_open: 288 data_in = self.serial.read(1500) 289 if len(data_in) > 0: 290 self.frame_buffer += data_in 291 flags_remaining = True 292 while flags_remaining: 293 frame_start = self.frame_buffer.find(HDLC.FLAG) 294 if frame_start != -1: 295 frame_end = self.frame_buffer.find(HDLC.FLAG, frame_start+1) 296 if frame_end != -1: 297 frame = self.frame_buffer[frame_start+1:frame_end] 298 frame = frame.replace(bytes([HDLC.ESC, HDLC.FLAG ^ HDLC.ESC_MASK]), bytes([HDLC.FLAG])) 299 frame = frame.replace(bytes([HDLC.ESC, HDLC.ESC ^ HDLC.ESC_MASK]), bytes([HDLC.ESC])) 300 if len(frame) > WDCL.HEADER_MINSIZE: self.process_incoming(frame) 301 self.frame_buffer = self.frame_buffer[frame_end:] 302 else: 303 flags_remaining = False 304 else: 305 flags_remaining = False 306 307 except Exception as e: 308 self.online = False 309 self.wdcl_connected = False 310 if self.should_run: 311 if self.as_interface: 312 RNS.log("A serial port error occurred, the contained exception was: "+str(e), RNS.LOG_ERROR) 313 RNS.log("Will attempt to reconnect the interface periodically.", RNS.LOG_ERROR) 314 else: 315 self.owner.wlog("A serial port error occurred, the contained exception was: "+str(e)) 316 self.owner.wlog("Will attempt to reconnect the interface periodically.") 317 RNS.trace_exception(e) 318 319 self.online = False 320 self.wdcl_connected = False 321 try: self.serial.close() 322 except: pass 323 if self.should_run: self.reconnect_port() 324 325 def reconnect_port(self): 326 if self.reconnecting: return 327 self.reconnecting = True 328 self.wdcl_connected = False 329 while not self.online: 330 try: 331 time.sleep(5) 332 if self.as_interface: RNS.log("Attempting to reconnect serial port "+str(self.port)+" for "+str(self.owner)+"...", RNS.LOG_DEBUG) 333 else: self.owner.wlog("Attempting to reconnect serial port "+str(self.port.device)+" for "+str(self.owner)+"...") 334 self.open_port() 335 if self.serial and self.serial.is_open: self.configure_device() 336 except Exception as e: 337 if self.as_interface: RNS.log("Error while reconnecting port, the contained exception was: "+str(e), RNS.LOG_ERROR) 338 else: self.owner.wlog("Error while reconnecting port, the contained exception was: "+str(e)) 339 RNS.trace_exception(e) 340 341 self.reconnecting = False 342 if self.as_interface: RNS.log("Reconnected serial port for "+str(self), RNS.LOG_INFO) 343 else: self.owner.wlog("Reconnected serial port for "+str(self)) 344 345 def __str__(self): 346 if self.as_interface: return f"WDCL over {self.port}" 347 else: 348 if self.port.serial_number: sn_str = f" {self.port.serial_number}" 349 else: sn_str = "" 350 return f"{self.port.product}{sn_str} (USB)" 351 352 class Cmd(): 353 WDCL_CMD_ENDPOINT_PKT = 0x0001 354 WDCL_CMD_ENDPOINTS_LIST = 0x0100 355 WDCL_CMD_REMOTE_DISPLAY = 0x0A00 356 WDCL_CMD_REMOTE_INPUT = 0x0A01 357 358 class Evt(): 359 ET_MSG = 0x0000 360 ET_SYSTEM_BOOT = 0x0001 361 ET_CORE_INIT = 0x0002 362 ET_DRV_UART_INIT = 0x1000 363 ET_DRV_USB_CDC_INIT = 0x1010 364 ET_DRV_USB_CDC_HOST_AVAIL = 0x1011 365 ET_DRV_USB_CDC_HOST_SUSPEND = 0x1012 366 ET_DRV_USB_CDC_HOST_RESUME = 0x1013 367 ET_DRV_USB_CDC_CONNECTED = 0x1014 368 ET_DRV_USB_CDC_READ_ERR = 0x1015 369 ET_DRV_USB_CDC_OVERFLOW = 0x1016 370 ET_DRV_USB_CDC_DROPPED = 0x1017 371 ET_DRV_USB_CDC_TX_TIMEOUT = 0x1018 372 ET_DRV_I2C_INIT = 0x1020 373 ET_DRV_NVS_INIT = 0x1030 374 ET_DRV_NVS_ERASE = 0x1031 375 ET_DRV_CRYPTO_INIT = 0x1040 376 ET_DRV_DISPLAY_INIT = 0x1050 377 ET_DRV_DISPLAY_BUS_AVAILABLE = 0x1051 378 ET_DRV_DISPLAY_IO_CONFIGURED = 0x1052 379 ET_DRV_DISPLAY_PANEL_CREATED = 0x1053 380 ET_DRV_DISPLAY_PANEL_RESET = 0x1054 381 ET_DRV_DISPLAY_PANEL_INIT = 0x1055 382 ET_DRV_DISPLAY_PANEL_ENABLE = 0x1056 383 ET_DRV_DISPLAY_REMOTE_ENABLE = 0x1057 384 ET_DRV_W80211_INIT = 0x1060 385 ET_DRV_W80211_INIT = 0x1061 386 ET_DRV_W80211_CHANNEL = 0x1062 387 ET_DRV_W80211_POWER = 0x1063 388 ET_KRN_LOGGER_INIT = 0x2000 389 ET_KRN_LOGGER_OUTPUT = 0x2001 390 ET_KRN_UI_INIT = 0x2010 391 ET_PROTO_WDCL_INIT = 0x3000 392 ET_PROTO_WDCL_RUNNING = 0x3001 393 ET_PROTO_WDCL_CONNECTION = 0x3002 394 ET_PROTO_WDCL_HOST_ENDPOINT = 0x3003 395 ET_PROTO_WEAVE_INIT = 0x3100 396 ET_PROTO_WEAVE_RUNNING = 0x3101 397 ET_PROTO_WEAVE_EP_ALIVE = 0x3102 398 ET_PROTO_WEAVE_EP_TIMEOUT = 0x3103 399 ET_PROTO_WEAVE_EP_VIA = 0x3104 400 ET_SRVCTL_REMOTE_DISPLAY = 0xA000 401 ET_INTERFACE_REGISTERED = 0xD000 402 ET_STAT_STATE = 0xE000 403 ET_STAT_UPTIME = 0xE001 404 ET_STAT_TIMEBASE = 0xE002 405 ET_STAT_CPU = 0xE003 406 ET_STAT_TASK_CPU = 0xE004 407 ET_STAT_MEMORY = 0xE005 408 ET_STAT_STORAGE = 0xE006 409 ET_SYSERR_MEM_EXHAUSTED = 0xF000 410 411 IF_TYPE_USB = 0x01 412 IF_TYPE_UART = 0x02 413 IF_TYPE_W80211 = 0x03 414 IF_TYPE_BLE = 0x04 415 IF_TYPE_LORA = 0x05 416 IF_TYPE_ETHERNET = 0x06 417 IF_TYPE_WIFI = 0x07 418 IF_TYPE_TCP = 0x08 419 IF_TYPE_UDP = 0x09 420 IF_TYPE_IR = 0x0A 421 IF_TYPE_AFSK = 0x0B 422 IF_TYPE_GPIO = 0x0C 423 IF_TYPE_SPI = 0x0D 424 IF_TYPE_I2C = 0x0E 425 IF_TYPE_CAN = 0x0F 426 IF_TYPE_DMA = 0x10 427 428 event_descriptions = { 429 ET_SYSTEM_BOOT: "System boot", 430 ET_CORE_INIT: "Core initialization", 431 ET_DRV_UART_INIT: "UART driver initialization", 432 ET_DRV_USB_CDC_INIT: "USB CDC driver initialization", 433 ET_DRV_USB_CDC_HOST_AVAIL: "USB CDC host became available", 434 ET_DRV_USB_CDC_HOST_SUSPEND: "USB CDC host suspend", 435 ET_DRV_USB_CDC_HOST_RESUME: "USB CDC host resume", 436 ET_DRV_USB_CDC_CONNECTED: "USB CDC host connection", 437 ET_DRV_USB_CDC_READ_ERR: "USB CDC read error", 438 ET_DRV_USB_CDC_OVERFLOW: "USB CDC overflow occurred", 439 ET_DRV_USB_CDC_DROPPED: "USB CDC dropped bytes", 440 ET_DRV_USB_CDC_TX_TIMEOUT: "USB CDC TX flush timeout", 441 ET_DRV_I2C_INIT: "I2C driver initialization", 442 ET_DRV_NVS_INIT: "NVS driver initialization", 443 ET_DRV_CRYPTO_INIT: "Cryptography driver initialization", 444 ET_DRV_W80211_INIT: "W802.11 driver initialization", 445 ET_DRV_W80211_CHANNEL: "W802.11 channel configuration", 446 ET_DRV_W80211_POWER: "W802.11 TX power configuration", 447 ET_DRV_DISPLAY_INIT: "Display driver initialization", 448 ET_DRV_DISPLAY_BUS_AVAILABLE: "Display bus availability", 449 ET_DRV_DISPLAY_IO_CONFIGURED: "Display I/O configuration", 450 ET_DRV_DISPLAY_PANEL_CREATED: "Display panel allocation", 451 ET_DRV_DISPLAY_PANEL_RESET: "Display panel reset", 452 ET_DRV_DISPLAY_PANEL_INIT: "Display panel initialization", 453 ET_DRV_DISPLAY_PANEL_ENABLE: "Display panel activation", 454 ET_DRV_DISPLAY_REMOTE_ENABLE: "Remote display output activation", 455 ET_KRN_LOGGER_INIT: "Logging service initialization", 456 ET_KRN_LOGGER_OUTPUT: "Logging service output activation", 457 ET_KRN_UI_INIT: "User interface service initialization", 458 ET_PROTO_WDCL_INIT: "WDCL protocol initialization", 459 ET_PROTO_WDCL_RUNNING: "WDCL protocol activation", 460 ET_PROTO_WDCL_CONNECTION: "WDCL host connection", 461 ET_PROTO_WDCL_HOST_ENDPOINT: "Weave host endpoint", 462 ET_PROTO_WEAVE_INIT: "Weave protocol initialization", 463 ET_PROTO_WEAVE_RUNNING: "Weave protocol activation", 464 ET_PROTO_WEAVE_EP_ALIVE: "Weave endpoint alive", 465 ET_PROTO_WEAVE_EP_TIMEOUT: "Weave endpoint disappeared", 466 ET_SRVCTL_REMOTE_DISPLAY: "Remote display service control event", 467 ET_INTERFACE_REGISTERED: "Interface registration", 468 ET_SYSERR_MEM_EXHAUSTED: "System memory exhausted", 469 } 470 471 interface_types = { 472 IF_TYPE_USB: "usb", 473 IF_TYPE_UART: "uart", 474 IF_TYPE_W80211: "mw", 475 IF_TYPE_BLE: "ble", 476 IF_TYPE_LORA: "lora", 477 IF_TYPE_ETHERNET: "eth", 478 IF_TYPE_WIFI: "wifi", 479 IF_TYPE_TCP: "tcp", 480 IF_TYPE_UDP: "udp", 481 IF_TYPE_IR: "ir", 482 IF_TYPE_AFSK: "afsk", 483 IF_TYPE_GPIO: "gpio", 484 IF_TYPE_SPI: "spi", 485 IF_TYPE_I2C: "i2c", 486 IF_TYPE_CAN: "can", 487 IF_TYPE_DMA: "dma", 488 } 489 490 channel_descriptions = { 491 1: "Channel 1 (2412 MHz)", 492 2: "Channel 2 (2417 MHz)", 493 3: "Channel 3 (2422 MHz)", 494 4: "Channel 4 (2427 MHz)", 495 5: "Channel 5 (2432 MHz)", 496 6: "Channel 6 (2437 MHz)", 497 7: "Channel 7 (2442 MHz)", 498 8: "Channel 8 (2447 MHz)", 499 9: "Channel 9 (2452 MHz)", 500 10: "Channel 10 (2457 MHz)", 501 11: "Channel 11 (2462 MHz)", 502 12: "Channel 12 (2467 MHz)", 503 13: "Channel 13 (2472 MHz)", 504 14: "Channel 14 (2484 MHz)", 505 } 506 507 LOG_FORCE = 0 508 LOG_CRITICAL = 1 509 LOG_ERROR = 2 510 LOG_WARNING = 3 511 LOG_NOTICE = 4 512 LOG_INFO = 5 513 LOG_VERBOSE = 6 514 LOG_DEBUG = 7 515 LOG_EXTREME = 8 516 LOG_SYSTEM = 9 517 518 levels = { 519 LOG_FORCE: "Forced", 520 LOG_CRITICAL: "Critical", 521 LOG_ERROR: "Error", 522 LOG_WARNING: "Warning", 523 LOG_NOTICE: "Notice", 524 LOG_INFO: "Info", 525 LOG_VERBOSE: "Verbose", 526 LOG_DEBUG: "Debug", 527 LOG_EXTREME: "Extreme", 528 LOG_SYSTEM: "System", 529 } 530 531 task_descriptions = { 532 "taskLVGL": "Driver: UI Renderer", 533 "ui_service": "Service: User Interface", 534 "TinyUSB": "Driver: USB", 535 "drv_w80211": "Driver: W802.11", 536 "system_stats": "System: Stats", 537 "core": "System: Core", 538 "protocol_wdcl": "Protocol: WDCL", 539 "protocol_weave": "Protocol: Weave", 540 "tiT": "Protocol: TCP/IP", 541 "ipc0": "System: CPU 0 IPC", 542 "ipc1": "System: CPU 1 IPC", 543 "esp_timer": "Driver: Timers", 544 "Tmr Svc": "Service: Timers", 545 "kernel_logger": "Service: Logging", 546 "remote_display": "Service: Remote Display", 547 "wifi": "System: WiFi Hardware", 548 "sys_evt": "System: Kernel Events", 549 } 550 551 @staticmethod 552 def level(level): 553 if level in Evt.levels: return Evt.levels[level] 554 else: return "Unknown" 555 556 class LogFrame(): 557 timestamp = None 558 level = None 559 event = None 560 data = b"" 561 562 def __init__(self, timestamp=None, level=None, event=None, data=b""): 563 self.timestamp = timestamp; self.level = level 564 self.event = event; self.data = data 565 566 class WeaveEndpoint(): 567 QUEUE_LEN = 1024 568 569 def __init__(self, endpoint_addr): 570 self.endpoint_addr = endpoint_addr 571 self.alive = time.time() 572 self.via = None 573 self.received = deque(maxlen=WeaveEndpoint.QUEUE_LEN) 574 575 def receive(self, data): 576 self.received.append(data) 577 578 class WeaveDevice(): 579 STATLEN_MAX = 120 580 STAT_UPDATE_THROTTLE = 0.5 581 582 WEAVE_SWITCH_ID_LEN = 4 583 WEAVE_ENDPOINT_ID_LEN = 8 584 WEAVE_FLOWSEQ_LEN = 2 585 WEAVE_HMAC_LEN = 8 586 WEAVE_AUTH_LEN = WEAVE_ENDPOINT_ID_LEN+WEAVE_HMAC_LEN 587 588 WEAVE_PUBKEY_SIZE = 32 589 WEAVE_PRVKEY_SIZE = 64 590 WEAVE_SIGNATURE_LEN = 64 591 592 def __init__(self, as_interface=False, rns_interface=None): 593 self.identity = None 594 self.receiver = None 595 self.switch_id = None 596 self.endpoint_id = None 597 self.owner = None 598 self.rns_interface = rns_interface 599 self.as_interface = as_interface 600 self.endpoints = {} 601 self.active_tasks = {} 602 self.cpu_load = 0 603 self.memory_total = 0 604 self.memory_free = 0 605 self.memory_used = 0 606 self.memory_used_pct = 0 607 self.log_queue = deque() 608 self.memory_stats = deque(maxlen=WeaveDevice.STATLEN_MAX) 609 self.cpu_stats = deque(maxlen=WeaveDevice.STATLEN_MAX) 610 self.display_buffer = bytearray(0) 611 self.update_display = False 612 613 self.next_update_memory = 0 614 self.next_update_cpu = 0 615 616 def wdcl_send(self, packet_type, data): 617 if not self.switch_id: 618 if self.as_interface: RNS.log("Attempt to transmit on {self} while remote Weave device identity is unknown", RNS.LOG_ERROR) 619 else: self.receiver.log("Error: Attempt to transmit while remote Weave device identity is unknown") 620 else: 621 frame = self.switch_id 622 frame += bytes([packet_type]) 623 frame += data 624 self.connection.process_outgoing(frame) 625 626 def wdcl_broadcast(self, packet_type, data): 627 frame = WDCL.WDCL_BROADCAST 628 frame += bytes([packet_type]) 629 frame += data 630 self.connection.process_outgoing(frame) 631 632 def wdcl_send_command(self, command, data): 633 frame = b"" 634 frame += bytes([command>>8, (command & 0xFF)]) 635 frame += data 636 self.wdcl_send(WDCL.WDCL_T_CMD, frame) 637 638 def discover(self): 639 self.wdcl_broadcast(WDCL.WDCL_T_DISCOVER, self.connection.switch_id) 640 641 def handshake(self): 642 if self.identity == None: 643 if self.as_interface: RNS.log("Attempt to perform handshake on {self} before remote device discovery completion", RNS.LOG_ERROR) 644 else: self.receiver.log("Attempt to perform handshake before remote device discovery completion") 645 else: 646 signed_id = self.switch_id 647 signature = self.connection.switch_identity.sign(signed_id) 648 data = self.connection.switch_pub_bytes 649 data += signature 650 self.wdcl_send(WDCL.WDCL_T_CONNECT, data) 651 if self.as_interface: RNS.log(f"WDCL connection handshake sent", RNS.LOG_VERBOSE) 652 else: self.receiver.log("Connection handshake sent") 653 654 def capture_stats_cpu(self): 655 self.cpu_stats.append({"timestamp": time.time(), "cpu_load": self.cpu_load}) 656 if self.receiver and self.receiver.ready and len(self.memory_stats) > 1: self.receiver.stats_update("cpu") 657 658 def capture_stats_memory(self): 659 self.memory_stats.append({"timestamp": time.time(), "memory_used": self.memory_used}) 660 if self.receiver and self.receiver.ready and len(self.memory_stats) > 1: self.receiver.stats_update("memory") 661 662 def get_cpu_stats(self): 663 tbegin = None 664 stats = {"timestamps": [], "values": [], "max": 100, "unit": "%"} 665 for i in range(0, len(self.cpu_stats)): 666 if tbegin == None: tbegin = self.cpu_stats[len(self.cpu_stats)-1]["timestamp"] 667 stats["timestamps"].append(self.cpu_stats[i]["timestamp"]-tbegin) 668 stats["values"].append(self.cpu_stats[i]["cpu_load"]) 669 670 return stats 671 672 def get_memory_stats(self): 673 tbegin = None 674 stats = {"timestamps": [], "values": [], "max": self.memory_total, "unit": "B"} 675 for i in range(0, len(self.memory_stats)): 676 if tbegin == None: tbegin = self.memory_stats[len(self.memory_stats)-1]["timestamp"] 677 stats["timestamps"].append(self.memory_stats[i]["timestamp"]-tbegin) 678 stats["values"].append(self.memory_stats[i]["memory_used"]) 679 680 return stats 681 682 def get_active_tasks(self): 683 active_tasks = {} 684 now = time.time() 685 for task_id in self.active_tasks: 686 if not task_id.startswith("IDLE"): 687 task_description = task_id 688 if task_id in Evt.task_descriptions: task_description = Evt.task_descriptions[task_id] 689 if now - self.active_tasks[task_id]["timestamp"] < 5: 690 active_tasks[task_description] = self.active_tasks[task_id] 691 692 return active_tasks 693 694 def disconnect_display(self): 695 self.wdcl_send_command(Cmd.WDCL_CMD_REMOTE_DISPLAY, bytes([0x00])) 696 self.update_display = False 697 698 def connect_display(self): 699 self.wdcl_send_command(Cmd.WDCL_CMD_REMOTE_DISPLAY, bytes([0x01])) 700 self.update_display = True 701 702 def endpoint_alive(self, endpoint_id): 703 if not endpoint_id in self.endpoints: self.endpoints[endpoint_id] = WeaveEndpoint(endpoint_id) 704 else: self.endpoints[endpoint_id].alive = time.time() 705 706 if self.as_interface: self.rns_interface.add_peer(endpoint_id) 707 708 def endpoint_via(self, endpoint_id, via_switch_id): 709 if endpoint_id in self.endpoints: self.endpoints[endpoint_id].via = via_switch_id 710 if self.as_interface: self.rns_interface.endpoint_via(endpoint_id, via_switch_id) 711 712 def deliver_packet(self, endpoint_id, data): 713 packet_data = endpoint_id+data 714 self.wdcl_send_command(Cmd.WDCL_CMD_ENDPOINT_PKT, packet_data) 715 716 def received_packet(self, source, data): 717 self.endpoint_alive(source) 718 if self.as_interface: 719 self.rns_interface.process_incoming(data, source) 720 721 def incoming_frame(self, data): 722 if len(data) > self.WEAVE_SWITCH_ID_LEN+2 and data[self.WEAVE_SWITCH_ID_LEN] == WDCL.WDCL_T_ENDPOINT_PKT and data[:self.WEAVE_SWITCH_ID_LEN] == self.connection.switch_id: 723 payload = data[self.WEAVE_SWITCH_ID_LEN+1:-self.WEAVE_ENDPOINT_ID_LEN] 724 src_endpoint = data[-self.WEAVE_ENDPOINT_ID_LEN:] 725 self.received_packet(src_endpoint, payload) 726 727 elif len(data) > self.WEAVE_SWITCH_ID_LEN+1 and data[self.WEAVE_SWITCH_ID_LEN] == WDCL.WDCL_T_DISCOVER: 728 discovery_response_len = self.WEAVE_SWITCH_ID_LEN+1+self.WEAVE_PUBKEY_SIZE+self.WEAVE_SIGNATURE_LEN 729 if len(data) == discovery_response_len: 730 signed_id = data[:self.WEAVE_SWITCH_ID_LEN] 731 remote_pub_key = data[self.WEAVE_SWITCH_ID_LEN+1:self.WEAVE_SWITCH_ID_LEN+1+self.WEAVE_PUBKEY_SIZE] 732 remote_switch_id = remote_pub_key[-4:] 733 remote_signature = data[self.WEAVE_SWITCH_ID_LEN+1+self.WEAVE_PUBKEY_SIZE:self.WEAVE_SWITCH_ID_LEN+1+self.WEAVE_PUBKEY_SIZE+self.WEAVE_SIGNATURE_LEN] 734 remote_identity = RNS.Identity(create_keys=False) 735 remote_identity.load_public_key(remote_pub_key*2) 736 if remote_identity.validate(remote_signature, signed_id): 737 if self.as_interface: RNS.log(f"Remote Weave device {RNS.hexrep(remote_switch_id)} discovered", RNS.LOG_VERBOSE) 738 else: self.receiver.log(f"Remote Weave device {RNS.hexrep(remote_switch_id)} discovered") 739 self.identity = remote_identity 740 self.switch_id = remote_switch_id 741 self.handshake() 742 else: 743 if self.as_interface: RNS.LOG("Invalid remote device discovery response received", RNS.LOG_ERROR) 744 else: self.receiver.log("Invalid remote device discovery response received") 745 746 elif len(data) > self.WEAVE_SWITCH_ID_LEN+1 and data[self.WEAVE_SWITCH_ID_LEN] == WDCL.WDCL_T_LOG: 747 fd = data[self.WEAVE_SWITCH_ID_LEN+2:] 748 ts = fd[1] << 24 | fd[2] << 16 | fd[3] << 8 | fd[4] 749 lvl = fd[5]; evt = fd[6] << 8 | fd[7]; data = fd[8:] 750 self.log_handle(LogFrame(timestamp=ts/1000.0, level=lvl, event=evt, data=data)) 751 752 elif len(data) > self.WEAVE_SWITCH_ID_LEN+10 and data[self.WEAVE_SWITCH_ID_LEN] == WDCL.WDCL_T_DISP: 753 fd = data[self.WEAVE_SWITCH_ID_LEN+1:] 754 cf = fd[0] 755 ofs = fd[1] << 24 | fd[2] << 16 | fd[3] << 8 | fd[4] 756 dsz = fd[5] << 24 | fd[6] << 16 | fd[7] << 8 | fd[8] 757 fbf = fd[9:] 758 759 w = 128; h = 64 760 761 if dsz > len(self.display_buffer): self.display_buffer = bytearray(dsz) 762 self.display_buffer[ofs:ofs+len(fbf)] = fbf 763 764 if self.receiver and self.receiver.ready and ofs+len(fbf) == dsz: 765 if self.update_display: self.receiver.display_update(self.display_buffer, w, h) 766 767 def log_handle(self, frame): 768 # Handle system event signalling 769 if frame.event == Evt.ET_PROTO_WDCL_CONNECTION: self.connection.wdcl_connected = True 770 if frame.event == Evt.ET_PROTO_WDCL_HOST_ENDPOINT and len(frame.data) == self.WEAVE_ENDPOINT_ID_LEN: self.endpoint_id = frame.data 771 if frame.event == Evt.ET_PROTO_WEAVE_EP_ALIVE and len(frame.data) == self.WEAVE_ENDPOINT_ID_LEN: self.endpoint_alive(frame.data) 772 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:]) 773 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() } 774 elif frame.event == Evt.ET_STAT_CPU: 775 self.cpu_load = frame.data[0] 776 self.capture_stats_cpu() 777 elif frame.event == Evt.ET_STAT_MEMORY: 778 self.memory_free = int.from_bytes(frame.data[:4]) 779 self.memory_total = int.from_bytes(frame.data[4:]) 780 self.memory_used = self.memory_total-self.memory_free 781 self.memory_used_pct = round((self.memory_used/self.memory_total)*100, 2) 782 self.capture_stats_memory() 783 784 # Handle generic messages and unmapped events 785 else: 786 ts = RNS.prettytime(frame.timestamp) 787 if frame.event == Evt.ET_MSG: 788 if len(frame.data): data_string = frame.data.decode("utf-8") 789 else: data_string = "" 790 rendered = f"[{ts}] [{Evt.level(frame.level)}]: {data_string}" 791 792 else: 793 if frame.event in Evt.event_descriptions: event_description = Evt.event_descriptions[frame.event] 794 else: event_description = f"0x{RNS.hexrep(frame.event, delimit=False)}" 795 796 if frame.event == Evt.ET_INTERFACE_REGISTERED: 797 if len(frame.data) >= 2: 798 interface_index = frame.data[0]; interface_type = frame.data[1] 799 type_name = "phy" 800 if interface_type in Evt.interface_types: type_name = Evt.interface_types[interface_type] 801 data_string = f": {type_name}{interface_index}" 802 else: data_string = "" 803 else: 804 if len(frame.data): 805 data_string = f": {RNS.hexrep(frame.data)}" 806 if frame.event == Evt.ET_DRV_USB_CDC_CONNECTED: 807 if frame.data[0] == 0x01: data_string = ": Connected" 808 elif frame.data[0] == 0x00: data_string = ": Disconnected" 809 elif frame.event == Evt.ET_DRV_W80211_CHANNEL: 810 if frame.data[0] in Evt.channel_descriptions: data_string = f": {Evt.channel_descriptions[frame.data[0]]}" 811 else: data_string = f": {RNS.hexrep(frame.data)}" 812 elif frame.event == Evt.ET_DRV_W80211_POWER: 813 tx_power = frame.data[0]*0.25 814 data_string = f": {tx_power} dBm ({int(10**(tx_power/10))} mW)" 815 elif frame.event >= Evt.ET_CORE_INIT and frame.event <= Evt.ET_PROTO_WEAVE_RUNNING: 816 if frame.data[0] == 0x01: data_string = ": Success" 817 elif frame.data[0] == 0x00: 818 if frame.level == Evt.LOG_ERROR: data_string = ": Failure" 819 else: data_string = ": Stopped" 820 else: data_string = f": {RNS.hexrep(frame.data)}" 821 822 else: data_string = "" 823 824 rendered = f"[{ts}] [{Evt.level(frame.level)}] [{event_description}]{data_string}" 825 826 if self.as_interface: 827 RNS.log(f"{self.rns_interface}: {rendered}", RNS.LOG_EXTREME) 828 else: 829 if self.receiver and self.receiver.ready: 830 while len(self.log_queue): self.receiver.log(self.log_queue.pop()) 831 self.receiver.log(rendered) 832 else: self.log_queue.append(rendered) 833 834 class WeaveInterface(Interface): 835 HW_MTU = 1024 836 FIXED_MTU = True 837 838 DEFAULT_IFAC_SIZE = 16 839 PEERING_TIMEOUT = 20.0 840 BITRATE_GUESS = 250*1000 841 842 MULTI_IF_DEQUE_LEN = 48 843 MULTI_IF_DEQUE_TTL = 0.75 844 845 @property 846 def cpu_load(self): 847 if not self.device: return None 848 else: return self.device.cpu_load 849 850 @property 851 def mem_load(self): 852 if not self.device: return None 853 else: return self.device.memory_used_pct 854 855 @property 856 def switch_id(self): 857 if not self.device: return None 858 else: return self.device.switch_id 859 860 @property 861 def endpoint_id(self): 862 if not self.device: return None 863 else: return self.device.endpoint_id 864 865 def __init__(self, owner, configuration): 866 c = Interface.get_config_obj(configuration) 867 name = c["name"] 868 port = c["port"] 869 configured_bitrate = c["configured_bitrate"] if "configured_bitrate" in c else None 870 871 from RNS.Interfaces import netinfo 872 super().__init__() 873 self.netinfo = netinfo 874 875 self.HW_MTU = WeaveInterface.HW_MTU 876 self.IN = True 877 self.OUT = False 878 self.name = name 879 self.port = port 880 self.switch_identity = RNS.Identity() 881 self.owner = owner 882 self.hw_errors = [] 883 self._online = False 884 self.final_init_done = False 885 self.peers = {} 886 self.timed_out_interfaces = {} 887 self.spawned_interfaces = {} 888 self.write_lock = threading.Lock() 889 self.mif_deque = deque(maxlen=WeaveInterface.MULTI_IF_DEQUE_LEN) 890 self.mif_deque_times = deque(maxlen=WeaveInterface.MULTI_IF_DEQUE_LEN) 891 892 self.announce_rate_target = None 893 self.peer_job_interval = WeaveInterface.PEERING_TIMEOUT*1.1 894 self.peering_timeout = WeaveInterface.PEERING_TIMEOUT 895 896 self.receives = True 897 if configured_bitrate != None: self.bitrate = configured_bitrate 898 else: self.bitrate = WeaveInterface.BITRATE_GUESS 899 900 def final_init(self): 901 self.device = WeaveDevice(as_interface=True, rns_interface=self) 902 self.connection = WDCL(owner=self, device=self.device, port=self.port, as_interface=True) 903 904 job_thread = threading.Thread(target=self.peer_jobs) 905 job_thread.daemon = True 906 job_thread.start() 907 908 self._online = True 909 self.final_init_done = True 910 911 def peer_jobs(self): 912 while True: 913 time.sleep(self.peer_job_interval) 914 now = time.time() 915 timed_out_peers = [] 916 917 # Check for timed out peers 918 for peer_addr in self.peers: 919 peer = self.peers[peer_addr] 920 last_heard = peer[1] 921 if now > last_heard+self.peering_timeout: 922 timed_out_peers.append(peer_addr) 923 924 # Remove any timed out peers 925 for peer_addr in timed_out_peers: 926 removed_peer = self.peers.pop(peer_addr) 927 if peer_addr in self.spawned_interfaces: 928 spawned_interface = self.spawned_interfaces[peer_addr] 929 spawned_interface.detach() 930 spawned_interface.teardown() 931 RNS.log(str(self)+" removed peer "+RNS.hexrep(peer_addr)+" on "+RNS.hexrep(removed_peer[0]), RNS.LOG_DEBUG) 932 933 @property 934 def peer_count(self): 935 return len(self.spawned_interfaces) 936 937 def endpoint_via(self, endpoint_addr, via_switch_addr): 938 if endpoint_addr in self.peers: self.peers[endpoint_addr][2].via_switch_id = via_switch_addr 939 940 def add_peer(self, endpoint_addr): 941 if not endpoint_addr in self.peers: 942 spawned_interface = WeaveInterfacePeer(self, endpoint_addr) 943 spawned_interface.OUT = self.OUT 944 spawned_interface.IN = self.IN 945 spawned_interface.parent_interface = self 946 spawned_interface.bitrate = self.bitrate 947 948 spawned_interface.ifac_size = self.ifac_size 949 spawned_interface.ifac_netname = self.ifac_netname 950 spawned_interface.ifac_netkey = self.ifac_netkey 951 if spawned_interface.ifac_netname != None or spawned_interface.ifac_netkey != None: 952 ifac_origin = b"" 953 if spawned_interface.ifac_netname != None: 954 ifac_origin += RNS.Identity.full_hash(spawned_interface.ifac_netname.encode("utf-8")) 955 if spawned_interface.ifac_netkey != None: 956 ifac_origin += RNS.Identity.full_hash(spawned_interface.ifac_netkey.encode("utf-8")) 957 958 ifac_origin_hash = RNS.Identity.full_hash(ifac_origin) 959 spawned_interface.ifac_key = RNS.Cryptography.hkdf( 960 length=64, 961 derive_from=ifac_origin_hash, 962 salt=RNS.Reticulum.IFAC_SALT, 963 context=None 964 ) 965 spawned_interface.ifac_identity = RNS.Identity.from_bytes(spawned_interface.ifac_key) 966 spawned_interface.ifac_signature = spawned_interface.ifac_identity.sign(RNS.Identity.full_hash(spawned_interface.ifac_key)) 967 968 spawned_interface.announce_rate_target = self.announce_rate_target 969 spawned_interface.announce_rate_grace = self.announce_rate_grace 970 spawned_interface.announce_rate_penalty = self.announce_rate_penalty 971 spawned_interface.mode = self.mode 972 spawned_interface.HW_MTU = self.HW_MTU 973 spawned_interface._online = True 974 RNS.Transport.interfaces.append(spawned_interface) 975 if endpoint_addr in self.spawned_interfaces: 976 self.spawned_interfaces[endpoint_addr].detach() 977 self.spawned_interfaces[endpoint_addr].teardown() 978 self.spawned_interfaces.pop(spawned_interface) 979 980 self.spawned_interfaces[endpoint_addr] = spawned_interface 981 self.peers[endpoint_addr] = [endpoint_addr, time.time(), spawned_interface] 982 983 RNS.log(f"{self} added peer {RNS.hexrep(endpoint_addr)}", RNS.LOG_DEBUG) 984 else: 985 self.refresh_peer(endpoint_addr) 986 987 def refresh_peer(self, endpoint_addr): 988 try: 989 self.peers[endpoint_addr][1] = time.time() 990 except Exception as e: 991 RNS.log(f"An error occurred while refreshing peer {RNS.hexrep(endpoint_addr)} on {self}: {e}", RNS.LOG_ERROR) 992 993 def process_incoming(self, data, endpoint_addr=None): 994 if self.online and endpoint_addr in self.spawned_interfaces: 995 self.spawned_interfaces[endpoint_addr].process_incoming(data, endpoint_addr) 996 997 def process_outgoing(self,data): 998 pass 999 1000 def detach(self): 1001 self._online = False 1002 1003 @property 1004 def online(self): 1005 if not self._online: return False 1006 else: return self.connection.online 1007 1008 @online.setter 1009 def online(self, value): 1010 self._online = value 1011 1012 def __str__(self): 1013 return "WeaveInterface["+self.name+"]" 1014 1015 class WeaveInterfacePeer(Interface): 1016 1017 def __init__(self, owner, endpoint_addr): 1018 super().__init__() 1019 self.owner = owner 1020 self.parent_interface = owner 1021 self.endpoint_addr = endpoint_addr 1022 self.via_switch_id = None 1023 self.peer_addr = None 1024 self.addr_info = None 1025 self.HW_MTU = self.owner.HW_MTU 1026 self.FIXED_MTU = self.owner.FIXED_MTU 1027 self._online = False 1028 1029 def __str__(self): 1030 return f"WeaveInterfacePeer[{RNS.hexrep(self.endpoint_addr)}]" 1031 1032 @property 1033 def online(self): 1034 if not self._online or not self.owner: return false 1035 else: return self.owner.online 1036 1037 @online.setter 1038 def online(self, value): 1039 self._online = value 1040 1041 def process_incoming(self, data, endpoint_addr=None): 1042 if self.online: 1043 data_hash = RNS.Identity.full_hash(data) 1044 deque_hit = False 1045 if data_hash in self.owner.mif_deque: 1046 for te in self.owner.mif_deque_times: 1047 if te[0] == data_hash and time.time() < te[1]+WeaveInterface.MULTI_IF_DEQUE_TTL: 1048 deque_hit = True 1049 break 1050 1051 if not deque_hit: 1052 self.owner.refresh_peer(self.endpoint_addr) 1053 self.owner.mif_deque.append(data_hash) 1054 self.owner.mif_deque_times.append([data_hash, time.time()]) 1055 self.rxb += len(data) 1056 self.owner.rxb += len(data) 1057 self.owner.owner.inbound(data, self) 1058 1059 def process_outgoing(self, data): 1060 if self.online: 1061 with self.owner.write_lock: 1062 try: 1063 self.owner.device.deliver_packet(self.endpoint_addr, data) 1064 self.txb += len(data) 1065 self.owner.txb += len(data) 1066 except Exception as e: 1067 RNS.log("Could not transmit on "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR) 1068 1069 def detach(self): 1070 self._online = False 1071 self.detached = True 1072 1073 def teardown(self): 1074 if not self.detached: 1075 RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is being torn down.", RNS.LOG_ERROR) 1076 if RNS.Reticulum.panic_on_interface_error: 1077 RNS.panic() 1078 1079 else: RNS.log("The interface "+str(self)+" is being torn down.", RNS.LOG_VERBOSE) 1080 1081 self._online = False 1082 self.OUT = False 1083 self.IN = False 1084 1085 if self.endpoint_addr in self.owner.spawned_interfaces: 1086 try: self.owner.spawned_interfaces.pop(self.endpoint_addr) 1087 except Exception as e: 1088 RNS.log(f"Could not remove {self} from parent interface on detach. The contained exception was: {e}", RNS.LOG_ERROR) 1089 1090 if self in RNS.Transport.interfaces: 1091 RNS.Transport.interfaces.remove(self)