Compare commits

..

30 Commits

Author SHA1 Message Date
Mark Qvist 708f666787 Updated changelog 2025-11-22 15:00:17 +01:00
Mark Qvist 4f03302ae2 Cleanup 2025-11-22 14:57:50 +01:00
Mark Qvist d8f6ab206b Updated docs 2025-11-22 12:06:58 +01:00
Mark Qvist 472e69fe9a Updated docs 2025-11-22 11:21:33 +01:00
Mark Qvist aeed5279f8 Fixed formatting 2025-11-22 11:21:05 +01:00
Mark Qvist f3b8965fa6 Fixed formatting 2025-11-22 11:16:29 +01:00
Mark Qvist 1bbaab1db9 Updated version 2025-11-21 17:10:13 +01:00
Mark Qvist bf2fcbba37 Added interference detection status and history to rnstatus output for RNode interfaces 2025-11-21 15:56:17 +01:00
Mark Qvist a63dd67a07 Updated changelog 2025-11-19 15:40:03 +01:00
Mark Qvist b27f9836ae Updated docs and manual 2025-11-19 15:39:30 +01:00
Mark Qvist 9504c5b863 Updated docs 2025-11-19 15:37:24 +01:00
Mark Qvist 9767b3453e Updated docs 2025-11-19 15:23:35 +01:00
Mark Qvist 643fbbbc84 Fixed broken links. Thanks @symbioquine in #999. 2025-11-19 15:21:55 +01:00
Mark Qvist 2a5bcd5f52 Updated docs 2025-11-19 15:16:46 +01:00
Mark Qvist 237c3160eb Updated RNode TCP read timeouts 2025-11-19 14:37:47 +01:00
Mark Qvist fcdcf1a2a8 RNode TCP connection on Android 2025-11-18 12:27:36 +01:00
Mark Qvist 7c99aca1d0 Improved reconnect/hotplug reliability and responsiveness for RNodes connected over WiFi 2025-11-18 03:12:42 +01:00
Mark Qvist 309f1999e7 Cleanup 2025-11-17 21:13:21 +01:00
Mark Qvist fa6de7ff79 Updated docs 2025-11-17 19:00:13 +01:00
Mark Qvist 47dfcab170 Added ability to configure RNode IP settings to rnodeconf 2025-11-17 18:46:36 +01:00
Mark Qvist 8abd19800f Updated version 2025-11-17 17:16:29 +01:00
Mark Qvist b2d6ed733d Added support for configuring RNode WiFi settings to rnodeconf 2025-11-17 17:16:06 +01:00
Mark Qvist 1179757893 Added support for connecting RNode devices over TCP connections 2025-11-17 00:31:37 +01:00
Mark Qvist d328ef5ce0 Cleanup 2025-11-15 14:20:43 +01:00
Mark Qvist f577d3018f Updated manual 2025-11-15 13:43:36 +01:00
Mark Qvist e6db629915 Updated manual 2025-11-15 13:42:14 +01:00
Mark Qvist acaab30b91 Updated manual 2025-11-15 13:35:00 +01:00
Mark Qvist 76cedeed07 Updated BLE connection read timeouts on Android 2025-11-15 12:11:45 +01:00
Mark Qvist 5beea74eb3 Handle serial port never being opened due to failure on interface detach for RNodeInterface 2025-11-11 10:29:06 +01:00
Mark Qvist 1f91a8f6f2 Updated manual 2025-11-10 19:05:36 +01:00
38 changed files with 1171 additions and 302 deletions
+72
View File
@@ -1,3 +1,75 @@
### 2025-11-19: RNS 1.0.4
This release includes updates to RNode BLE reliability, and adds support for connecting RNodes to a host over WiFi and Ethernet.
**Changes**
- Improved handling for RNodes with PA/LNA combo
- Added interference detection stats to `rnstatus` output for RNode interfaces
- Updated documentation
**Release Hashes**
```
7a2b7893410833b42c0fa7f9a9e3369cebb085cdd26bd83f3031fa6c1051653c rns-1.0.4-py3-none-any.whl
ee647e7b3b94abdf1fab618a861390531a4aacc93eecce12c9e97280195c0e2d rnspure-1.0.4-py3-none-any.whl
```
### 2025-11-19: RNS 1.0.3
This release includes updates to RNode BLE reliability, and adds support for connecting RNodes to a host over WiFi and Ethernet.
**Changes**
- Added support for connecting RNode devices over WiFi and Ethernet
- Added support for configuring RNode WiFi and IP settings to `rnodeconf`
- Updated BLE connection read timeouts on Android, fixes intermittent BLE connection resets in areas with high 2.4GHz spectrum utilization
- Added handling for edge case where RNode serial port was never opened due to failure on interface detach
- Fixed broken links in documentation
**Release Hashes**
```
6bafde4c838ad778bf6878967e84c798e34d6ca621b255f59a60f38cb04ac138 rns-1.0.3-py3-none-any.whl
f277899f95c1189c6bf3beb40ac656c8b36dfd3d7e4cfb2bc3b4a1e6dc3484fa rnspure-1.0.3-py3-none-any.whl
```
### 2025-11-10: RNS 1.0.2
This maintenance release adds support for high-power RNodes with a LoRa PA and/or LNA.
**Changes**
- Added support for RNodes with a PA and/or LNA
- Added support for monitoring RNode CPU temperature via `rnodeconf`
**Release Hashes**
```
723bcf0a839025060ff680c4202b09fa766b35093a4a08506bb85485b8a1f154 rns-1.0.2-py3-none-any.whl
b02de8aeb1381ed2610f27f78799bab031367ed7bf500951fb8d5c2542d4a409 rnspure-1.0.2-py3-none-any.whl
```
### 2025-11-02: RNS 1.0.1
This release brings a number of bugfixes, as well as stability and reliability improvements. It also adds support for using Weave devices as Reticulum interfaces, fixes long-standing Bluetooth Low Energy connection issues on Android, and includes several API and usability improvements.
**Changes**
- Added path response signalling to announce handler API
- Added interface module for Weave devices
- Added support for connecting to Weave devices over serial/USB on Android
- Added support for allow files to `rnx`
- Added detection and logging of multicast echoes never arriving on AutoInterface system devices.
- Added Heltec v4 support to `rnodeconf`
- Implemented handler for ensuring dynamic destination app data can be generated and sent even on first system-internal discovery announce
- Updated documentation and manual
- Improved `AutoInterface` peering timing
- Fixed RNodeInterface Bluetooth Low Energy connection hangs on Android
- Fixed RNodeInterface Bluetooth Low Energy MTU not being configured correctly on Android
- Fixed command byte collision in RNodeInterface and RNodeMultiInterface
- Fixed string formatting for Android log output
- Updated output formatting for `rnid`
**Release Hashes**
```
aa77b4c8e1b6899117666e1e55b05b3250416ab5fea2826254358ae320e8b3ed rns-1.0.1-py3-none-any.whl
b3ddfa0b533631d9f1213043a0282952ae6e9f72c3072bbca053ac48e0483f7e rnspure-1.0.1-py3-none-any.whl
```
### 2025-07-14: RNS 1.0.0
We're out of beta. Thanks to **everyone** who helped make it this far.
+234 -46
View File
@@ -32,6 +32,7 @@ from RNS.Interfaces.Interface import Interface
from time import sleep
import sys
import threading
import socket
import time
import math
import RNS
@@ -364,7 +365,9 @@ class RNodeInterface(Interface):
target_device_address = c["target_device_address"] if "target_device_address" in c else None
ble_name = c["ble_name"] if "ble_name" in c else None
ble_addr = c["ble_addr"] if "ble_addr" in c else None
tcp_host = c["tcp_host"] if "tcp_host" in c else None
force_ble = c["force_ble"] if "force_ble" in c else False
force_tcp = c["force_tcp"] if "force_tcp" in c else False
frequency = int(c["frequency"]) if "frequency" in c else 0
bandwidth = int(c["bandwidth"]) if "bandwidth" in c else 0
txpower = int(c["txpower"]) if "txpower" in c else 0
@@ -436,6 +439,14 @@ class RNodeInterface(Interface):
self.ble_rx_queue= b""
self.ble_tx_queue= b""
self.tcp = None
self.use_tcp = False
self.tcp_host = tcp_host
self.tcp_rx_queue= b""
self.tcp_tx_queue= b""
self.tcp_rx_lock = threading.Lock()
self.tcp_tx_lock = threading.Lock()
self.frequency = frequency
self.bandwidth = bandwidth
self.txpower = txpower
@@ -489,6 +500,8 @@ class RNodeInterface(Interface):
self.r_csma_cw_max = None
self.r_current_rssi = None
self.r_noise_floor = None
self.r_interference = None
self.r_interference_l = None
self.r_temperature = None
self.r_battery_state = RNodeInterface.BATTERY_STATE_UNKNOWN
@@ -511,8 +524,8 @@ class RNodeInterface(Interface):
self.port_io_timeout = RNodeInterface.PORT_IO_TIMEOUT
self.last_imagedata = None
if force_ble or self.ble_addr != None or self.ble_name != None:
self.use_ble = True
if force_ble or self.ble_addr != None or self.ble_name != None: self.use_ble = True
if force_tcp or self.tcp_host != None: self.use_tcp = True
self.validcfg = True
if (self.frequency < RNodeInterface.FREQ_MIN or self.frequency > RNodeInterface.FREQ_MAX):
@@ -562,10 +575,8 @@ class RNodeInterface(Interface):
self.open_port()
if self.serial != None:
if self.serial.is_open:
self.configure_device()
else:
raise IOError("Could not open serial port")
if self.serial.is_open: self.configure_device()
else: raise IOError("Could not open serial port")
elif self.bt_manager != None:
if self.bt_manager.connected:
self.configure_device()
@@ -583,12 +594,9 @@ class RNodeInterface(Interface):
def read_mux(self, len=None):
if self.serial != None:
return self.serial.read()
elif self.bt_manager != None:
return self.bt_manager.read()
else:
raise IOError("No ports available for reading")
if self.serial != None: return self.serial.read()
elif self.bt_manager != None: return self.bt_manager.read()
else: raise IOError("No ports available for reading")
def write_mux(self, data):
if self.serial != None:
@@ -615,7 +623,7 @@ class RNodeInterface(Interface):
RNS.log(f"New connection instance: "+str(self.ble), RNS.LOG_DEBUG)
def open_port(self):
if not self.use_ble:
if not self.use_ble and not self.use_tcp:
if self.port != None:
RNS.log("Opening serial port "+self.port+"...")
# Get device parameters
@@ -683,7 +691,7 @@ class RNodeInterface(Interface):
if self.bt_manager != None:
self.bt_manager.connect_any_device()
else:
elif self.use_ble:
if self.ble == None:
self.ble = BLEConnection(owner=self, target_name=self.ble_name, target_bt_addr=self.ble_addr)
self.serial = self.ble
@@ -692,6 +700,24 @@ class RNodeInterface(Interface):
while not self.ble.connected and time.time() < open_time + self.ble.CONNECT_TIMEOUT:
time.sleep(1)
elif self.use_tcp:
RNS.log(f"Opening TCP connection for {self}...")
if self.tcp != None and self.tcp.running == False:
self.tcp.close()
self.tcp.cleanup()
self.tcp = None
if self.tcp == None:
self.tcp = TCPConnection(owner=self, target_host=self.tcp_host)
self.serial = self.tcp
open_time = time.time()
while not self.tcp.connected and time.time() < open_time + self.tcp.CONNECT_TIMEOUT:
time.sleep(1)
else:
raise TypeError("No valid device connection type defined for RNode interface")
def configure_device(self):
self.resetRadioState()
@@ -699,17 +725,19 @@ class RNodeInterface(Interface):
thread = threading.Thread(target=self.readLoop, daemon=True).start()
self.detect()
if not self.use_ble:
sleep(0.5)
else:
if self.use_tcp:
tcp_detect_timeout = 5.0
detect_time = time.time()
while not self.detected and time.time() < detect_time + tcp_detect_timeout: time.sleep(0.1)
if not self.detected: RNS.log(f"RNode detect timed out over TCP", RNS.LOG_ERROR)
elif self.use_ble:
ble_detect_timeout = 5
detect_time = time.time()
while not self.detected and time.time() < detect_time + ble_detect_timeout:
time.sleep(0.1)
if self.detected:
detect_time = RNS.prettytime(time.time()-detect_time)
else:
RNS.log(f"RNode detect timed out over {self.port}", RNS.LOG_ERROR)
while not self.detected and time.time() < detect_time + ble_detect_timeout: time.sleep(0.1)
if self.detected: detect_time = RNS.prettytime(time.time()-detect_time)
else: RNS.log(f"RNode detect timed out over {self.port}", RNS.LOG_ERROR)
else:
sleep(0.2)
if not self.detected:
raise IOError("Could not detect device")
@@ -722,11 +750,19 @@ class RNodeInterface(Interface):
if self.serial != None and self.port != None:
self.timeout = 200
RNS.log("Serial port "+self.port+" is now open")
RNS.log(f"Serial port {self.port} is now open")
if self.bt_manager != None and self.bt_manager.connected:
self.timeout = 1500
RNS.log("Bluetooth connection to RNode now open")
RNS.log(f"Bluetooth connection to RNode now open")
if self.ble != None and self.ble.connected:
self.timeout = 1500
RNS.log(f"BLE connection {self.port} to RNode now open")
if self.tcp != None and self.tcp.connected:
self.timeout = 1500
RNS.log(f"TCP connection tcp://{self.tcp_host} to RNode now open")
RNS.log("Configuring RNode interface...", RNS.LOG_VERBOSE)
self.initRadio()
@@ -978,10 +1014,8 @@ class RNodeInterface(Interface):
def validateRadioState(self):
RNS.log("Waiting for radio configuration validation for "+str(self)+"...", RNS.LOG_VERBOSE)
if not self.platform == KISS.PLATFORM_ESP32:
sleep(1.00);
else:
sleep(2.00);
if not self.platform == KISS.PLATFORM_ESP32: sleep(1.00);
else: sleep(2.00);
self.validcfg = True
if (self.r_frequency != None and abs(self.frequency - int(self.r_frequency)) > 100):
@@ -1278,10 +1312,12 @@ class RNodeInterface(Interface):
self.r_channel_load_long = cul/100.0
self.r_current_rssi = crs-RNodeInterface.RSSI_OFFSET
self.r_noise_floor = nfl-RNodeInterface.RSSI_OFFSET
if ntf == 0xFF:
self.r_interference = None
else:
self.r_interference = ntf-RNodeInterface.RSSI_OFFSET
self.r_interference_l = [time.time(), self.r_interference]
if self.r_interference != None:
RNS.log(f"{self} Radio detected interference at {self.r_interference} dBm", RNS.LOG_DEBUG)
@@ -1447,7 +1483,7 @@ class RNodeInterface(Interface):
if got == 0:
time_since_last = int(time.time()*1000) - last_read_ms
if len(data_buffer) > 0 and time_since_last > self.timeout:
RNS.log(str(self)+" serial read timeout in command "+str(command), RNS.LOG_WARNING)
RNS.log(f"{self} device read timeout in command {command} after {RNS.prettytime(self.timeout/1000.0)}", RNS.LOG_WARNING)
data_buffer = b""
in_frame = False
command = KISS.CMD_UNKNOWN
@@ -1458,19 +1494,19 @@ class RNodeInterface(Interface):
if time.time() > self.first_tx + self.id_interval:
RNS.log("Interface "+str(self)+" is transmitting beacon data: "+str(self.id_callsign.decode("utf-8")), RNS.LOG_DEBUG)
self.process_outgoing(self.id_callsign)
if (time.time() - self.last_port_io > self.port_io_timeout):
self.detect()
if (time.time() - self.last_port_io > self.port_io_timeout*3):
raise IOError("Connected port for "+str(self)+" became unresponsive")
if self.bt_manager != None:
sleep(0.08)
if self.use_tcp:
if self.tcp and self.tcp.connected:
if time.time() > self.tcp.last_write + self.tcp.ACTIVITY_KEEPALIVE:
self.detect()
if (time.time() - self.last_port_io > self.port_io_timeout): self.detect()
if (time.time() - self.last_port_io > self.port_io_timeout*3): raise IOError("Connected port for "+str(self)+" became unresponsive")
if self.bt_manager != None or self.ble != None: sleep(0.08)
except Exception as e:
self.online = False
RNS.log("A serial port occurred, the contained exception was: "+str(e), RNS.LOG_ERROR)
RNS.log("A serial port error occurred, the contained exception was: "+str(e), RNS.LOG_ERROR)
RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is now offline.", RNS.LOG_ERROR)
if RNS.Reticulum.panic_on_interface_error:
@@ -1528,12 +1564,18 @@ class RNodeInterface(Interface):
def detach(self):
self.detached = True
self.disable_external_framebuffer()
self.setRadioState(KISS.RADIO_STATE_OFF)
self.leave()
try:
self.disable_external_framebuffer()
self.setRadioState(KISS.RADIO_STATE_OFF)
self.leave()
if self.use_ble:
self.ble.close()
except Exception as e:
RNS.log(f"An error occurred while detaching {self}: {e}", RNS.LOG_ERROR)
if self.use_ble: self.ble.close()
if self.use_tcp:
time.sleep(0.5)
self.tcp.close()
def should_ingress_limit(self):
return False
@@ -1554,6 +1596,17 @@ class RNodeInterface(Interface):
def get_battery_percent(self):
return self.r_battery_percent
def tcp_receive(self, data):
with self.tcp_rx_lock: self.tcp_rx_queue += data
def tcp_waiting(self): return len(self.tcp_tx_queue) > 0
def get_tcp_waiting(self, n):
with self.tcp_tx_lock:
data = self.tcp_tx_queue[:n]
self.tcp_tx_queue = self.tcp_tx_queue[n:]
return data
def ble_receive(self, data):
with self.ble_rx_lock:
self.ble_rx_queue += data
@@ -1568,7 +1621,7 @@ class RNodeInterface(Interface):
return data
def __str__(self):
return "RNodeInterface["+str(self.name)+"]"
return f"RNodeInterface[{self.name}]"
class BLEConnection(BluetoothDispatcher):
UART_SERVICE_UUID = "6e400001-b5a3-f393-e0a9-e50e24dcca9e"
@@ -1811,4 +1864,139 @@ class BLEConnection(BluetoothDispatcher):
def on_characteristic_changed(self, characteristic):
if characteristic.getUuid().toString() == BLEConnection.UART_TX_CHAR_UUID:
recvd = bytes(characteristic.getValue())
self.owner.ble_receive(recvd)
self.owner.ble_receive(recvd)
class TCPConnection():
TARGET_PORT = 7633
CONNECT_TIMEOUT = 5.0
INITIAL_CONNECT_TIMEOUT = 5.0
RECONNECT_WAIT = 4.0
ACTIVITY_TIMEOUT = 6.0
ACTIVITY_KEEPALIVE = ACTIVITY_TIMEOUT-2.5
TCP_USER_TIMEOUT = 24
TCP_PROBE_AFTER = 5
TCP_PROBE_INTERVAL = 2
TCP_PROBES = 12
@property
def is_open(self):
return self.connected
@property
def in_waiting(self):
buflen = len(self.owner.tcp_rx_queue)
return buflen > 0
def write(self, data_bytes):
if self.connected and self.socket:
with self.owner.tcp_tx_lock:
if len(self.owner.tcp_tx_queue) > 0:
self.socket.sendall(self.owner.tcp_tx_queue)
self.owner.tcp_tx_queue = b""
self.socket.sendall(data_bytes)
self.last_write = time.time()
else:
with self.owner.tcp_tx_lock: self.owner.tcp_tx_queue += data_bytes
return len(data_bytes)
def read(self, n=4096):
with self.owner.tcp_rx_lock:
data = self.owner.tcp_rx_queue[:n]
self.owner.tcp_rx_queue = self.owner.tcp_rx_queue[n:]
return data
def close(self):
if self.connected:
RNS.log(f"Disconnecting TCP socket for {self.owner}", RNS.LOG_DEBUG)
self.must_disconnect = True
if self.socket: self.socket.close()
def __init__(self, owner=None, target_host=None):
self.owner = owner
self.target_host = target_host
self.connected = False
self.reconnecting = False
self.running = False
self.should_run = False
self.must_disconnect = False
self.connect_job_running = False
self.last_write = time.time()
self.should_run = True
self.connection_thread = threading.Thread(target=self.initial_connect, daemon=True).start()
def set_timeouts_linux(self):
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_USER_TIMEOUT, int(self.TCP_USER_TIMEOUT * 1000))
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, int(self.TCP_PROBE_AFTER))
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, int(self.TCP_PROBE_INTERVAL))
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, int(self.TCP_PROBES))
def set_timeouts_osx(self):
if hasattr(socket, "TCP_KEEPALIVE"): TCP_KEEPIDLE = socket.TCP_KEEPALIVE
else: TCP_KEEPIDLE = 0x10
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
self.socket.setsockopt(socket.IPPROTO_TCP, TCP_KEEPIDLE, int(self.TCP_PROBE_AFTER))
def cleanup(self):
try:
if self.socket: self.socket.close()
except Exception as e:
RNS.log(f"Error while disconnecting TCP socket on cleanup for {self.owner}", RNS.LOG_ERROR)
self.should_run = False
def initial_connect(self):
if self.connect(initial=True): threading.Thread(target=self.read_loop, daemon=True).start()
def connect(self, initial=False):
try:
if initial:
RNS.log(f"Establishing TCP connection to device for {self.owner}...", RNS.LOG_DEBUG)
address_info = socket.getaddrinfo(self.target_host, self.TARGET_PORT, proto=socket.IPPROTO_TCP)[0]
address_family = address_info[0]
target_address = address_info[4]
self.socket = socket.socket(address_family, socket.SOCK_STREAM)
self.socket.settimeout(self.INITIAL_CONNECT_TIMEOUT)
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
self.socket.connect(target_address)
self.socket.settimeout(None)
self.connected = True
self.last_write = time.time()
RNS.log(f"TCP connection to device for {self.owner} established", RNS.LOG_DEBUG)
if RNS.vendor.platformutils.is_linux(): self.set_timeouts_linux()
elif RNS.vendor.platformutils.is_darwin(): self.set_timeouts_osx()
return True
except Exception as e:
if initial:
RNS.log(f"TCP connection to device for {self.owner} could not be established: {e}", RNS.LOG_ERROR)
return False
else: raise e
def read_loop(self):
try:
data_in = b""
while not self.must_disconnect:
if self.socket: data_in = self.socket.recv(4096)
else: data_in = b""
if len(data_in) > 0: self.owner.tcp_receive(data_in)
else:
self.connected = False
RNS.log(f"The TCP socket for {self} was closed", RNS.LOG_WARNING)
break
except Exception as e:
self.connected = False
RNS.log(f"A TCP read error occurred for {self}, the contained exception was: {e}", RNS.LOG_WARNING)
+276 -63
View File
@@ -32,6 +32,7 @@ from RNS.Interfaces.Interface import Interface
from time import sleep
import sys
import threading
import socket
import time
import math
import RNS
@@ -159,8 +160,11 @@ class RNodeInterface(Interface):
lt_alock = float(c["airtime_limit_long"]) if "airtime_limit_long" in c else None
force_ble = False
ble_name = None
ble_addr = None
ble_name = None
ble_addr = None
force_tcp = False
tcp_host = None
port = c["port"] if "port" in c else None
@@ -168,6 +172,7 @@ class RNodeInterface(Interface):
raise ValueError("No port specified for RNode interface")
if port != None:
tcp_uri_scheme = "tcp://"
ble_uri_scheme = "ble://"
if port.lower().startswith(ble_uri_scheme):
force_ble = True
@@ -180,6 +185,13 @@ class RNodeInterface(Interface):
else:
ble_name = ble_string
if port.lower().startswith(tcp_uri_scheme):
force_tcp = True
tcp_string = port[len(tcp_uri_scheme):]
port = None
if len(tcp_string) == 0: pass
else: tcp_host = tcp_string
self.HW_MTU = 508
self.pyserial = serial
@@ -196,6 +208,14 @@ class RNodeInterface(Interface):
self.reconnecting= False
self.hw_errors = []
self.use_tcp = False
self.tcp = None
self.tcp_host = tcp_host
self.tcp_rx_queue= b""
self.tcp_tx_queue= b""
self.tcp_rx_lock = threading.Lock()
self.tcp_tx_lock = threading.Lock()
self.use_ble = False
self.ble_name = ble_name
self.ble_addr = ble_addr
@@ -256,6 +276,8 @@ class RNodeInterface(Interface):
self.r_csma_cw_max = None
self.r_current_rssi = None
self.r_noise_floor = None
self.r_interference = None
self.r_interference_l = None
self.r_battery_state = RNodeInterface.BATTERY_STATE_UNKNOWN
self.r_battery_percent = 0
@@ -275,8 +297,8 @@ class RNodeInterface(Interface):
self.interface_ready = False
self.announce_rate_target = None
if force_ble or self.ble_addr != None or self.ble_name != None:
self.use_ble = True
if force_ble or self.ble_addr != None or self.ble_name != None: self.use_ble = True
if force_tcp or self.tcp_host != None: self.use_tcp = True
self.validcfg = True
if (self.frequency < RNodeInterface.FREQ_MIN or self.frequency > RNodeInterface.FREQ_MAX):
@@ -325,10 +347,8 @@ class RNodeInterface(Interface):
try:
self.open_port()
if self.serial.is_open:
self.configure_device()
else:
raise IOError("Could not open serial port")
if self.serial.is_open: self.configure_device()
else: raise IOError("Could not open serial port")
except Exception as e:
RNS.log("Could not open serial port for interface "+str(self), RNS.LOG_ERROR)
@@ -341,8 +361,8 @@ class RNodeInterface(Interface):
def open_port(self):
if not self.use_ble:
RNS.log("Opening serial port "+self.port+"...")
if not self.use_ble and not self.use_tcp:
RNS.log(f"Opening serial port {self.port}...")
self.serial = self.pyserial.Serial(
port = self.port,
baudrate = self.speed,
@@ -358,19 +378,37 @@ class RNodeInterface(Interface):
)
else:
RNS.log(f"Opening BLE connection for {self}...")
if self.ble != None and self.ble.running == False:
self.ble.close()
self.ble.cleanup()
self.ble = None
if self.use_ble:
RNS.log(f"Opening BLE connection for {self}...")
self.timeout = 1250
if self.ble != None and self.ble.running == False:
self.ble.close()
self.ble.cleanup()
self.ble = None
if self.ble == None:
self.ble = BLEConnection(owner=self, target_name=self.ble_name, target_bt_addr=self.ble_addr)
self.serial = self.ble
if self.ble == None:
self.ble = BLEConnection(owner=self, target_name=self.ble_name, target_bt_addr=self.ble_addr)
self.serial = self.ble
open_time = time.time()
while not self.ble.connected and time.time() < open_time + self.ble.CONNECT_TIMEOUT:
time.sleep(1)
open_time = time.time()
while not self.ble.connected and time.time() < open_time + self.ble.CONNECT_TIMEOUT:
time.sleep(1)
if self.use_tcp:
self.timeout = 1500
RNS.log(f"Opening TCP connection for {self}...")
if self.tcp != None and self.tcp.running == False:
self.tcp.close()
self.tcp.cleanup()
self.tcp = None
if self.tcp == None:
self.tcp = TCPConnection(owner=self, target_host=self.tcp_host)
self.serial = self.tcp
open_time = time.time()
while not self.tcp.connected and time.time() < open_time + self.tcp.CONNECT_TIMEOUT:
time.sleep(1)
def reset_radio_state(self):
self.r_frequency = None
@@ -391,38 +429,41 @@ class RNodeInterface(Interface):
thread.start()
self.detect()
if not self.use_ble:
sleep(0.2)
else:
ble_detect_timeout = 5
if self.use_tcp:
tcp_detect_timeout = 5.0
detect_time = time.time()
while not self.detected and time.time() < detect_time + ble_detect_timeout:
time.sleep(0.1)
if self.detected:
detect_time = RNS.prettytime(time.time()-detect_time)
else:
RNS.log(f"RNode detect timed out over {self.port}", RNS.LOG_ERROR)
while not self.detected and time.time() < detect_time + tcp_detect_timeout: time.sleep(0.1)
if not self.detected: RNS.log(f"RNode detect timed out over TCP", RNS.LOG_ERROR)
elif self.use_ble:
ble_detect_timeout = 5.0
detect_time = time.time()
while not self.detected and time.time() < detect_time + ble_detect_timeout: time.sleep(0.1)
if not self.detected: RNS.log(f"RNode detect timed out over BLE", RNS.LOG_ERROR)
else:
sleep(0.2)
if not self.detected:
RNS.log("Could not detect device for "+str(self), RNS.LOG_ERROR)
RNS.log(f"Could not detect device for {self}", RNS.LOG_ERROR)
self.serial.close()
else:
if self.platform == KISS.PLATFORM_ESP32 or self.platform == KISS.PLATFORM_NRF52:
self.display = True
if self.platform == KISS.PLATFORM_ESP32 or self.platform == KISS.PLATFORM_NRF52: self.display = True
RNS.log("Serial port "+self.port+" is now open")
RNS.log("Configuring RNode interface...", RNS.LOG_VERBOSE)
self.initRadio()
if (self.validateRadioState()):
self.interface_ready = True
RNS.log(str(self)+" is configured and powered up")
sleep(0.3)
self.online = True
else:
RNS.log("After configuring "+str(self)+", the reported radio parameters did not match your configuration.", RNS.LOG_ERROR)
RNS.log("Make sure that your hardware actually supports the parameters specified in the configuration", RNS.LOG_ERROR)
RNS.log("Aborting RNode startup", RNS.LOG_ERROR)
self.serial.close()
if self.use_tcp: RNS.log(f"TCP connection to {self.tcp_host} is now open", RNS.LOG_VERBOSE)
elif self.use_ble: RNS.log(f"BLE connection to {self} is now open", RNS.LOG_VERBOSE)
else: RNS.log(f"Serial port {self.port} is now open", RNS.LOG_VERBOSE)
RNS.log("Configuring RNode interface...", RNS.LOG_VERBOSE)
self.initRadio()
if (self.validateRadioState()):
self.interface_ready = True
RNS.log(str(self)+" is configured and powered up")
sleep(0.3)
self.online = True
else:
RNS.log("After configuring "+str(self)+", the reported radio parameters did not match your configuration.", RNS.LOG_ERROR)
RNS.log("Make sure that your hardware actually supports the parameters specified in the configuration", RNS.LOG_ERROR)
RNS.log("Aborting RNode startup", RNS.LOG_ERROR)
self.serial.close()
def initRadio(self):
@@ -617,10 +658,9 @@ class RNodeInterface(Interface):
def validateRadioState(self):
RNS.log("Waiting for radio configuration validation for "+str(self)+"...", RNS.LOG_VERBOSE)
if self.use_ble:
sleep(1.00)
else:
sleep(0.25)
if self.use_ble: sleep(1.00)
elif self.use_tcp: sleep(1.5)
else: sleep(0.25)
if self.use_ble and self.ble != None and self.ble.device_disappeared:
RNS.log(f"Device disappeared during radio state validation for {self}", RNS.LOG_ERROR)
@@ -905,16 +945,35 @@ class RNodeInterface(Interface):
self.r_channel_load_long = cul/100.0
self.r_current_rssi = crs-RNodeInterface.RSSI_OFFSET
self.r_noise_floor = nfl-RNodeInterface.RSSI_OFFSET
# TODO: Remove debug
# interference_log_threshold = 10
# if ntf == 0xFF:
# self.r_interference = None
# if self.r_noise_floor != None:
# # Filter potential false interference events due to LNA recalibration
# if self.r_interference_l != None:
# if self.r_interference_l[1] < self.r_noise_floor+interference_log_threshold:
# self.r_interference_l = None
# else:
# if self.r_noise_floor != None:
# interference = ntf-RNodeInterface.RSSI_OFFSET
# # Filter potential false interference events due to LNA recalibration
# if interference > self.r_noise_floor+interference_log_threshold:
# self.r_interference = ntf-RNodeInterface.RSSI_OFFSET
# self.r_interference_l = [time.time(), self.r_interference]
if ntf == 0xFF:
self.r_interference = None
else:
self.r_interference = ntf-RNodeInterface.RSSI_OFFSET
self.r_interference_l = [time.time(), self.r_interference]
if self.r_interference != None:
RNS.log(f"{self} Radio detected interference at {self.r_interference} dBm", RNS.LOG_DEBUG)
# TODO: Remove debug
# RNS.log(f"RSSI: {self.r_current_rssi}, Noise floor: {self.r_noise_floor}, Interference: {self.r_interference}", RNS.LOG_EXTREME)
# RNS.log(f"RSSI: {self.r_current_rssi}, Noise floor: {self.r_noise_floor}, Interference: {self.r_interference}", RNS.LOG_DEBUG)
elif (command == KISS.CMD_STAT_PHYPRM):
if (byte == KISS.FESC):
escape = True
@@ -1073,7 +1132,7 @@ class RNodeInterface(Interface):
else:
time_since_last = int(time.time()*1000) - last_read_ms
if len(data_buffer) > 0 and time_since_last > self.timeout:
RNS.log(str(self)+" serial read timeout in command "+str(command), RNS.LOG_WARNING)
RNS.log(f"{self} device read timeout in command {command} after {RNS.prettytime(self.timeout/1000.0)}", RNS.LOG_WARNING)
data_buffer = b""
in_frame = False
command = KISS.CMD_UNKNOWN
@@ -1085,6 +1144,11 @@ class RNodeInterface(Interface):
RNS.log("Interface "+str(self)+" is transmitting beacon data: "+str(self.id_callsign.decode("utf-8")), RNS.LOG_DEBUG)
self.process_outgoing(self.id_callsign)
if self.use_tcp:
if self.tcp and self.tcp.connected:
if time.time() > self.tcp.last_write + self.tcp.ACTIVITY_KEEPALIVE:
self.detect()
sleep(0.08)
except Exception as e:
@@ -1113,23 +1177,28 @@ class RNodeInterface(Interface):
time.sleep(5)
RNS.log("Attempting to reconnect serial port "+str(self.port)+" for "+str(self)+"...", RNS.LOG_VERBOSE)
self.open_port()
if self.serial.is_open:
self.configure_device()
if self.serial.is_open: self.configure_device()
except Exception as e:
RNS.log("Error while reconnecting port, the contained exception was: "+str(e), RNS.LOG_ERROR)
self.reconnecting = False
if self.online:
RNS.log("Reconnected serial port for "+str(self))
if self.online: RNS.log(f"Reconnected port for {self}")
def detach(self):
self.detached = True
self.disable_external_framebuffer()
self.setRadioState(KISS.RADIO_STATE_OFF)
self.leave()
try:
self.disable_external_framebuffer()
self.setRadioState(KISS.RADIO_STATE_OFF)
self.leave()
except Exception as e:
RNS.log(f"An error occurred while detaching {self}: {e}", RNS.LOG_ERROR)
if self.use_ble:
self.ble.close()
if self.use_ble: self.ble.close()
if self.use_tcp:
time.sleep(0.5)
self.tcp.close()
def should_ingress_limit(self):
return False
@@ -1150,6 +1219,17 @@ class RNodeInterface(Interface):
def get_battery_percent(self):
return self.r_battery_percent
def tcp_receive(self, data):
with self.tcp_rx_lock: self.tcp_rx_queue += data
def tcp_waiting(self): return len(self.tcp_tx_queue) > 0
def get_tcp_waiting(self, n):
with self.tcp_tx_lock:
data = self.tcp_tx_queue[:n]
self.tcp_tx_queue = self.tcp_tx_queue[n:]
return data
def ble_receive(self, data):
with self.ble_rx_lock:
self.ble_rx_queue += data
@@ -1164,7 +1244,7 @@ class RNodeInterface(Interface):
return data
def __str__(self):
return "RNodeInterface["+str(self.name)+"]"
return f"RNodeInterface[{self.name}]"
class BLEConnection():
UART_SERVICE_UUID = "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"
@@ -1343,3 +1423,136 @@ class BLEConnection():
RNS.log(f"Error while determining device bond status for {device}, the contained exception was: {e}", RNS.LOG_ERROR)
return False
class TCPConnection():
TARGET_PORT = 7633
CONNECT_TIMEOUT = 5.0
INITIAL_CONNECT_TIMEOUT = 5.0
RECONNECT_WAIT = 4.0
ACTIVITY_TIMEOUT = 6.0
ACTIVITY_KEEPALIVE = ACTIVITY_TIMEOUT-2.5
TCP_USER_TIMEOUT = 24
TCP_PROBE_AFTER = 5
TCP_PROBE_INTERVAL = 2
TCP_PROBES = 12
@property
def is_open(self):
return self.connected
@property
def in_waiting(self):
buflen = len(self.owner.tcp_rx_queue)
return buflen > 0
def write(self, data_bytes):
if self.connected and self.socket:
with self.owner.tcp_tx_lock:
if len(self.owner.tcp_tx_queue) > 0:
self.socket.sendall(self.owner.tcp_tx_queue)
self.owner.tcp_tx_queue = b""
self.socket.sendall(data_bytes)
self.last_write = time.time()
else:
with self.owner.tcp_tx_lock: self.owner.tcp_tx_queue += data_bytes
return len(data_bytes)
def read(self, n):
with self.owner.tcp_rx_lock:
data = self.owner.tcp_rx_queue[:n]
self.owner.tcp_rx_queue = self.owner.tcp_rx_queue[n:]
return data
def close(self):
if self.connected:
RNS.log(f"Disconnecting TCP socket for {self.owner}", RNS.LOG_DEBUG)
self.must_disconnect = True
if self.socket: self.socket.close()
def __init__(self, owner=None, target_host=None):
self.owner = owner
self.target_host = target_host
self.connected = False
self.reconnecting = False
self.running = False
self.should_run = False
self.must_disconnect = False
self.connect_job_running = False
self.last_write = time.time()
self.should_run = True
self.connection_thread = threading.Thread(target=self.initial_connect, daemon=True).start()
def set_timeouts_linux(self):
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_USER_TIMEOUT, int(self.TCP_USER_TIMEOUT * 1000))
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, int(self.TCP_PROBE_AFTER))
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, int(self.TCP_PROBE_INTERVAL))
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, int(self.TCP_PROBES))
def set_timeouts_osx(self):
if hasattr(socket, "TCP_KEEPALIVE"): TCP_KEEPIDLE = socket.TCP_KEEPALIVE
else: TCP_KEEPIDLE = 0x10
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
self.socket.setsockopt(socket.IPPROTO_TCP, TCP_KEEPIDLE, int(self.TCP_PROBE_AFTER))
def cleanup(self):
try:
if self.socket: self.socket.close()
except Exception as e: RNS.log(f"Error while disconnecting TCP socket on cleanup for {self.owner}", RNS.LOG_ERROR)
self.should_run = False
def initial_connect(self):
if self.connect(initial=True): threading.Thread(target=self.read_loop, daemon=True).start()
def connect(self, initial=False):
try:
if initial:
RNS.log(f"Establishing TCP connection to device for {self.owner}...", RNS.LOG_DEBUG)
address_info = socket.getaddrinfo(self.target_host, self.TARGET_PORT, proto=socket.IPPROTO_TCP)[0]
address_family = address_info[0]
target_address = address_info[4]
self.socket = socket.socket(address_family, socket.SOCK_STREAM)
self.socket.settimeout(self.INITIAL_CONNECT_TIMEOUT)
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
self.socket.connect(target_address)
self.socket.settimeout(None)
self.connected = True
self.last_write = time.time()
RNS.log(f"TCP connection to device for {self.owner} established", RNS.LOG_DEBUG)
if RNS.vendor.platformutils.is_linux(): self.set_timeouts_linux()
elif RNS.vendor.platformutils.is_darwin(): self.set_timeouts_osx()
return True
except Exception as e:
if initial:
RNS.log(f"TCP connection to device for {self.owner} could not be established: {e}", RNS.LOG_ERROR)
return False
else: raise e
def read_loop(self):
try:
data_in = b""
while not self.must_disconnect:
if self.socket: data_in = self.socket.recv(4096)
else: data_in = b""
if len(data_in) > 0: self.owner.tcp_receive(data_in)
else:
self.connected = False
RNS.log(f"The TCP socket for {self} was closed", RNS.LOG_WARNING)
break
except Exception as e:
self.connected = False
RNS.log(f"A TCP read error occurred for {self}, the contained exception was: {e}", RNS.LOG_WARNING)
+1 -1
View File
@@ -608,7 +608,7 @@ class Resource:
if sleep_time < 0:
if self.retries_left > 0:
ms = "" if self.outstanding_parts == 1 else "s"
RNS.log("Timed out waiting for "+str(self.outstanding_parts)+" part"+ms+", requesting retry", RNS.LOG_DEBUG)
RNS.log(f"Timed out waiting for {self.outstanding_parts} part{ms}, requesting retry on {self}", RNS.LOG_DEBUG)
if self.window > self.window_min:
self.window -= 1
if self.window_max > self.window_min:
+7
View File
@@ -1028,6 +1028,13 @@ class Reticulum:
if hasattr(interface, "r_noise_floor"):
ifstats["noise_floor"] = interface.r_noise_floor
if hasattr(interface, "r_interference"):
ifstats["interference"] = interface.r_interference
if hasattr(interface, "r_interference_l") and type(interface.r_interference_l) == list:
ifstats["interference_last_ts"] = interface.r_interference_l[0]
ifstats["interference_last_dbm"] = interface.r_interference_l[1]
if hasattr(interface, "cpu_temp"):
ifstats["cpu_temp"] = interface.cpu_temp
+286 -36
View File
@@ -97,10 +97,17 @@ class KISS():
CMD_BT_CTRL = 0x46
CMD_BT_PIN = 0x62
CMD_DIS_IA = 0x69
CMD_WIFI_MODE = 0x6A
CMD_WIFI_SSID = 0x6B
CMD_WIFI_PSK = 0x6C
CMD_WIFI_CHN = 0x6E
CMD_WIFI_IP = 0x84
CMD_WIFI_NM = 0x85
CMD_BOARD = 0x47
CMD_PLATFORM = 0x48
CMD_MCU = 0x49
CMD_FW_VERSION = 0x50
CMD_CFG_READ = 0x6D
CMD_ROM_READ = 0x51
CMD_ROM_WRITE = 0x52
CMD_ROM_WIPE = 0x59
@@ -245,6 +252,12 @@ class ROM():
ADDR_CONF_PINT = 0xB6
ADDR_CONF_BSET = 0xB7
ADDR_CONF_DIA = 0xB9
ADDR_CONF_WIFI = 0xBA
ADDR_CONF_WCHN = 0xBB
ADDR_CONF_SSID = 0x00
ADDR_CONF_PSK = 0x21
ADDR_CONF_IP = 0x42
ADDR_CONF_NM = 0x46
INFO_LOCK_BYTE = 0x73
CONF_OK_BYTE = 0x73
@@ -402,6 +415,7 @@ class RNode():
self.platform = None
self.mcu = None
self.eeprom = None
self.cfg_sector = None
self.major_version = None
self.minor_version = None
self.version = None
@@ -461,12 +475,17 @@ class RNode():
in_frame = False
data_buffer = b""
command_buffer = b""
elif (in_frame and byte == KISS.FEND and command == KISS.CMD_CFG_READ):
self.cfg_sector = data_buffer
in_frame = False
data_buffer = b""
command_buffer = b""
elif (byte == KISS.FEND):
in_frame = True
command = KISS.CMD_UNKNOWN
data_buffer = b""
command_buffer = b""
elif (in_frame and len(data_buffer) < 512):
elif (in_frame and len(data_buffer) < 1024):
if (len(data_buffer) == 0 and command == KISS.CMD_UNKNOWN):
command = byte
elif (command == KISS.CMD_ROM_READ):
@@ -480,6 +499,17 @@ class RNode():
byte = KISS.FESC
escape = False
data_buffer = data_buffer+bytes([byte])
elif (command == KISS.CMD_CFG_READ):
if (byte == KISS.FESC):
escape = True
else:
if (escape):
if (byte == KISS.TFEND):
byte = KISS.FEND
if (byte == KISS.TFESC):
byte = KISS.FESC
escape = False
data_buffer = data_buffer+bytes([byte])
elif (command == KISS.CMD_DATA):
if (byte == KISS.FESC):
escape = True
@@ -788,6 +818,92 @@ class RNode():
if written != len(kiss_command):
raise IOError("An IO error occurred while sending firmware update command to device")
def set_wifi_mode(self, mode):
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_WIFI_MODE, mode])+bytes([KISS.FEND])
written = self.serial.write(kiss_command)
if written != len(kiss_command):
raise IOError("An IO error occurred while sending wifi mode command to device")
def set_wifi_channel(self, channel):
try: ch = int(channel)
except: raise ValueError("Invalid WiFi channel")
if ch < 1 or ch > 14: raise ValueError("Invalid WiFi channel")
ch_data = bytes([ch])
data = KISS.escape(ch_data)
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_WIFI_CHN])+data+bytes([KISS.FEND])
written = self.serial.write(kiss_command)
if written != len(kiss_command):
raise IOError("An IO error occurred while sending wifi channel to device")
def set_wifi_ip(self, ip):
if ip == None: ip_data = bytes([0x00, 0x00, 0x00, 0x00])
else:
ip_data = b""
if not type(ip) == str: raise TypeError("Invalid IP address")
octets = ip.split(".")
if not len(octets) == 4: raise ValueError("Invalid IP address length")
try:
for i in range(0, 4):
octet = int(octets[i])
if octet < 0 or octet > 255: raise ValueError("Invalid IP octet value")
else: ip_data += bytes([octet])
except Exception as e:
raise ValueError(f"Could not decode IP address octet: {e}")
data = KISS.escape(ip_data)
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_WIFI_IP])+data+bytes([KISS.FEND])
written = self.serial.write(kiss_command)
if written != len(kiss_command): raise IOError("An IO error occurred while sending wifi IP address to device")
def set_wifi_nm(self, nm):
if nm == None: nm_data = bytes([0x00, 0x00, 0x00, 0x00])
else:
nm_data = b""
if not type(nm) == str: raise TypeError("Invalid IP address")
octets = nm.split(".")
if not len(octets) == 4: raise ValueError("Invalid IP address length")
try:
for i in range(0, 4):
octet = int(octets[i])
if octet < 0 or octet > 255: raise ValueError("Invalid IP octet value")
else: nm_data += bytes([octet])
except Exception as e:
raise ValueError(f"Could not decode IP address octet: {e}")
data = KISS.escape(nm_data)
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_WIFI_NM])+data+bytes([KISS.FEND])
written = self.serial.write(kiss_command)
if written != len(kiss_command): raise IOError("An IO error occurred while sending wifi netmask to device")
def set_wifi_ssid(self, ssid):
if ssid == None: data = bytes([0x00])
else:
ssid_data = ssid.encode("utf-8")+bytes([0x00])
if len(ssid_data) < 0 or len(ssid_data) > 33: raise ValueError("Invalid SSID length")
data = KISS.escape(ssid_data)
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_WIFI_SSID])+data+bytes([KISS.FEND])
written = self.serial.write(kiss_command)
if written != len(kiss_command):
raise IOError("An IO error occurred while sending wifi SSID to device")
def set_wifi_psk(self, psk):
if psk == None: data = bytes([0x00])
else:
psk_data = psk.encode("utf-8")+bytes([0x00])
if len(psk_data) < 8 or len(psk_data) > 33: raise ValueError("Invalid psk length")
data = KISS.escape(psk_data)
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_WIFI_PSK])+data+bytes([KISS.FEND])
written = self.serial.write(kiss_command)
if written != len(kiss_command):
raise IOError("An IO error occurred while sending wifi SSID to device")
def initRadio(self):
self.setFrequency()
self.setBandwidth()
@@ -894,7 +1010,7 @@ class RNode():
kiss_command = bytes([KISS.FEND, KISS.CMD_ROM_READ, 0x00, KISS.FEND])
written = self.serial.write(kiss_command)
if written != len(kiss_command):
raise IOError("An IO error occurred while configuring radio state")
raise IOError("An IO error occurred while downloading EEPROM")
sleep(0.6)
if self.eeprom == None:
@@ -903,6 +1019,15 @@ class RNode():
else:
self.parse_eeprom()
def download_cfg_sector(self):
self.cfg_sector = None
kiss_command = bytes([KISS.FEND, KISS.CMD_CFG_READ, 0x00, KISS.FEND])
written = self.serial.write(kiss_command)
if written != len(kiss_command):
raise IOError("An IO error occurred while downloading config sector")
sleep(0.6)
def parse_eeprom(self):
global squashvw;
try:
@@ -1365,6 +1490,14 @@ def main():
parser.add_argument("-B", "--bluetooth-off", action="store_true", help="Turn device bluetooth off")
parser.add_argument("-p", "--bluetooth-pair", action="store_true", help="Put device into bluetooth pairing mode")
parser.add_argument("-w", "--wifi", action="store", metavar="mode", default=None, help="Set WiFi mode (OFF, AP or STATION)")
parser.add_argument("--channel", action="store", metavar="channel", default=None, help="Set WiFi channel")
parser.add_argument("--ssid", action="store", metavar="ssid", default=None, help="Set WiFi SSID (NONE to delete)")
parser.add_argument("--psk", action="store", metavar="psk", default=None, help="Set WiFi PSK (NONE to delete)")
parser.add_argument("--show-psk", action="store_true", default=False, help="Display stored WiFi PSK")
parser.add_argument("--ip", action="store", metavar="ip", default=None, help="Set static WiFi IP address (NONE for DHCP)")
parser.add_argument("--nm", action="store", metavar="nm", default=None, help="Set static WiFi network mask (NONE for DHCP)")
parser.add_argument("-D", "--display", action="store", metavar="i", type=int, default=None, help="Set display intensity (0-255)")
parser.add_argument("-t", "--timeout", action="store", metavar="s", type=int, default=None, help="Set display timeout in seconds, 0 to disable")
parser.add_argument("-R", "--rotation", action="store", metavar="rotation", type=int, default=None, help="Set display rotation, valid values are 0 through 3")
@@ -3558,17 +3691,14 @@ def main():
graceful_exit()
if args.config:
rnode.download_cfg_sector()
eeprom_reserved = 200
if rnode.platform == ROM.PLATFORM_ESP32:
eeprom_size = 296
elif rnode.platform == ROM.PLATFORM_NRF52:
eeprom_size = 296
else:
eeprom_size = 4096
if rnode.platform == ROM.PLATFORM_ESP32: eeprom_size = 296
elif rnode.platform == ROM.PLATFORM_NRF52: eeprom_size = 296
else: eeprom_size = 4096
eeprom_offset = eeprom_size-eeprom_reserved
def ea(a):
return a+eeprom_offset
def ea(a): return a+eeprom_offset
ec_bt = rnode.eeprom[ROM.ADDR_CONF_BT]
ec_dint = rnode.eeprom[ROM.ADDR_CONF_DINT]
ec_dadr = rnode.eeprom[ROM.ADDR_CONF_DADR]
@@ -3578,40 +3708,89 @@ def main():
ec_pint = rnode.eeprom[ROM.ADDR_CONF_PINT]
ec_bset = rnode.eeprom[ROM.ADDR_CONF_BSET]
ec_dia = rnode.eeprom[ROM.ADDR_CONF_DIA]
ec_wifi = rnode.eeprom[ROM.ADDR_CONF_WIFI]
ec_wchn = rnode.eeprom[ROM.ADDR_CONF_WCHN]
ec_ssid = None
ec_psk = None
ec_ip = None
ec_nm = None
if ec_wchn < 1 or ec_wchn > 14: ec_wchn = 1
if rnode.cfg_sector:
ssid_bytes = b""
for i in range(0, 32):
byte = rnode.cfg_sector[ROM.ADDR_CONF_SSID+i]
if byte == 0xFF: byte = 0x00
if byte == 0x00: break
else: ssid_bytes += bytes([byte])
try: ec_ssid = ssid_bytes.decode("utf-8")
except Exception as e: print(f"Error: Could not decode WiFi SSID read from device")
psk_bytes = b""
for i in range(0, 32):
byte = rnode.cfg_sector[ROM.ADDR_CONF_PSK+i]
if byte == 0xFF: byte = 0x00
if byte == 0x00: break
else: psk_bytes += bytes([byte])
ip_bytes = b""
for i in range(0, 4):
byte = rnode.cfg_sector[ROM.ADDR_CONF_IP+i]
ip_bytes += bytes([byte])
if len(ip_bytes) == 4: ec_ip = f"{int(ip_bytes[0])}.{int(ip_bytes[1])}.{int(ip_bytes[2])}.{int(ip_bytes[3])}"
if ec_ip == "255.255.255.255" or ec_ip == "0.0.0.0": ec_ip = None
nm_bytes = b""
for i in range(0, 4):
byte = rnode.cfg_sector[ROM.ADDR_CONF_NM+i]
nm_bytes += bytes([byte])
if len(nm_bytes) == 4: ec_nm = f"{int(nm_bytes[0])}.{int(nm_bytes[1])}.{int(nm_bytes[2])}.{int(nm_bytes[3])}"
if ec_nm == "255.255.255.255" or ec_nm == "0.0.0.0": ec_nm = None
if ec_wifi == 0x02:
ec_ip = "10.0.0.1"
ec_nm = "255.255.255.0"
try: ec_psk = psk_bytes.decode("utf-8")
except Exception as e: print(f"Error: Could not decode WiFi PSK read from device")
if not args.show_psk and ec_psk: ec_psk = "*"*len(ec_psk)
print("\nDevice configuration:")
if ec_bt == 0x73:
print(f" Bluetooth : Enabled")
else:
print(f" Bluetooth : Disabled")
if ec_dia == 0x00:
print(f" Interference avoidance : Enabled")
else:
print(f" Interference avoidance : Disabled")
if ec_bt == 0x73: print(f" Bluetooth : Enabled")
else: print(f" Bluetooth : Disabled")
if ec_wifi == 0x01: print(f" WiFi : Enabled (Station)")
if ec_wifi == 0x02: print(f" WiFi : Enabled (AP)")
else: print(f" WiFi : Disabled")
if ec_wifi == 0x01 or ec_wifi == 0x02:
if not ec_wchn: print(f" Channel : Unknown")
else: print(f" Channel : {ec_wchn}")
if not ec_ssid: print(f" SSID : Not set")
else: print(f" SSID : {ec_ssid}")
if not ec_psk: print(f" PSK : Not set")
else: print(f" PSK : {ec_psk}")
if not ec_ip: print(f" IP Address : DHCP")
else: print(f" IP Address : {ec_ip}")
if ec_ip and ec_nm: print(f" Network Mask : {ec_nm}")
if ec_dia == 0x00: print(f" Interference avoidance : Enabled")
else: print(f" Interference avoidance : Disabled")
print( f" Display brightness : {ec_dint}")
if ec_dadr == 0xFF:
print(f" Display address : Default")
else:
print(f" Display address : {RNS.hexrep(ec_dadr, delimit=False)}")
if ec_bset == 0x73 and ec_dblk != 0x00:
print(f" Display blanking : {ec_dblk}s")
else:
print(f" Display blanking : Disabled")
if ec_dadr == 0xFF: print(f" Display address : Default")
else: print(f" Display address : {RNS.hexrep(ec_dadr, delimit=False)}")
if ec_bset == 0x73 and ec_dblk != 0x00: print(f" Display blanking : {ec_dblk}s")
else: print(f" Display blanking : Disabled")
if ec_drot != 0xFF:
if ec_drot == 0x00:
rstr = "Landscape"
if ec_drot == 0x01:
rstr = "Portrait"
if ec_drot == 0x02:
rstr = "Landscape 180"
if ec_drot == 0x03:
rstr = "Portrait 180"
if ec_drot == 0x00: rstr = "Landscape"
if ec_drot == 0x01: rstr = "Portrait"
if ec_drot == 0x02: rstr = "Landscape 180"
if ec_drot == 0x03: rstr = "Portrait 180"
print(f" Display rotation : {rstr}")
else:
print(f" Display rotation : Default")
if ec_pset == 0x73:
print(f" Neopixel Intensity : {ec_pint}")
if ec_pset == 0x73: print(f" Neopixel Intensity : {ec_pint}")
print("")
rnode.leave()
graceful_exit()
if args.eeprom_dump:
@@ -3722,6 +3901,77 @@ def main():
input()
rnode.leave()
if args.channel:
try:
RNS.log(f"Setting WiFi channel to {args.channel}")
rnode.set_wifi_channel(args.channel)
except Exception as e:
print(f"Could not set WiFi channel: {e}")
graceful_exit()
if args.ssid:
try:
if args.ssid.lower() == "none":
ssid_str = None
RNS.log(f"Deleting WiFi SSID")
else:
ssid_str = str(args.ssid)
RNS.log(f"Setting WiFi SSID to: {ssid_str}")
rnode.set_wifi_ssid(ssid_str)
except Exception as e:
print(f"Could not set WiFi SSID: {e}")
graceful_exit()
if args.psk:
try:
if args.psk.lower() == "none":
psk_str = None
RNS.log(f"Deleting WiFi PSK")
else:
psk_str = str(args.psk)
RNS.log(f"Setting WiFi PSK")
rnode.set_wifi_psk(psk_str)
except Exception as e:
print(f"Could not set WiFi PSK: {e}")
graceful_exit()
if args.ip:
try:
if args.ip.lower() == "none":
RNS.log(f"Setting WiFi IP to DHCP...")
rnode.set_wifi_ip(None)
else:
RNS.log(f"Setting WiFi static IP to: {args.ip}")
rnode.set_wifi_ip(args.ip)
except Exception as e:
print(f"Could not set WiFi IP: {e}")
graceful_exit()
if args.nm:
try:
if args.nm.lower() == "none":
RNS.log(f"Deleting WiFi static netmask configuration...")
rnode.set_wifi_nm(None)
else:
RNS.log(f"Setting WiFi static netmask to: {args.nm}")
rnode.set_wifi_nm(args.nm)
except Exception as e:
print(f"Could not set WiFi netmask: {e}")
graceful_exit()
if args.wifi:
try:
mode = 0x00
if str(args.wifi).lower().startswith("sta"): mode = 0x01
elif str(args.wifi).lower().startswith("ap"): mode = 0x02
if mode == 0x00: RNS.log(f"Disabling WiFi...")
elif mode == 0x01: RNS.log(f"Setting WiFi to station mode")
elif mode == 0x02: RNS.log(f"Setting WiFi to AP mode")
rnode.set_wifi_mode(mode)
except Exception as e:
print(f"Could not set WiFi mode: {e}")
graceful_exit()
if args.info:
if rnode.provisioned:
timestamp = struct.unpack(">I", rnode.made)[0]
+13 -3
View File
@@ -327,10 +327,20 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=
print(" Rate : {ss}".format(ss=speed_str(ifstat["bitrate"])))
if "noise_floor" in ifstat:
if ifstat["noise_floor"] != None:
print(" Noise Fl. : {nfl} dBm".format(nfl=str(ifstat["noise_floor"])))
if not "interference" in ifstat: nstr = ""
else:
print(" Noise Fl. : Unknown")
nf = ifstat["interference"]
lstr = ", no interference"
if "interference_last_ts" in ifstat and "interference_last_dbm" in ifstat:
lago = time.time()-ifstat["interference_last_ts"]
ldbm = ifstat["interference_last_dbm"]
lstr = f"\n Intrfrnc. : {ldbm} dBm {RNS.prettytime(lago, compact=True)} ago"
nstr = f"\n Intrfrnc. : {nf} dBm" if nf else lstr
if ifstat["noise_floor"] != None: print(" Noise Fl. : {nfl} dBm{ntr}".format(nfl=str(ifstat["noise_floor"]), ntr=nstr))
else: print(" Noise Fl. : Unknown")
if "cpu_load" in ifstat:
if ifstat["cpu_load"] != None: print(" CPU load : {v} %".format(v=str(ifstat["cpu_load"])))
+1 -1
View File
@@ -1 +1 @@
__version__ = "1.0.2"
__version__ = "1.0.4"