mirror of
https://github.com/markqvist/Reticulum.git
synced 2026-06-23 12:24:30 -07:00
Compare commits
89 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 700601d63e | |||
| 274c7199b0 | |||
| 7960226883 | |||
| bb74878e94 | |||
| 549d22be68 | |||
| 5c2c935b6f | |||
| 8402541c73 | |||
| c34c268a6a | |||
| 8fcdc4613c | |||
| f645fa569b | |||
| 469947dab9 | |||
| 2386fc3635 | |||
| e9e98a00c2 | |||
| b305eb8e0a | |||
| dd7931d421 | |||
| 191dce1301 | |||
| 3b5a27ba60 | |||
| 3c91f7f18b | |||
| 171457713b | |||
| 67ee8d6aab | |||
| 13fa7d49d9 | |||
| 66d921e669 | |||
| 85f60ea04e | |||
| 4870e741f6 | |||
| f71c1986af | |||
| 30d8e351dd | |||
| 5e62e3bc22 | |||
| 1a67e276ad | |||
| df37a4a884 | |||
| d26bbbd59f | |||
| 2a264fa7d6 | |||
| d5e0a461cf | |||
| e28dbd4afa | |||
| 8626dcd69f | |||
| e34f21f4dc | |||
| f692e81b8e | |||
| 28e43b52f9 | |||
| 680d17fb98 | |||
| 1e477c976c | |||
| ab301cdb79 | |||
| cecb4b3acb | |||
| de53a105a4 | |||
| 9e4ae3c6fe | |||
| 3482d84bc0 | |||
| 51c5c85fcd | |||
| 57aeab43a2 | |||
| 92cccddaab | |||
| 3de182192a | |||
| aca6b0c110 | |||
| 3d6e7a9597 | |||
| 21da55dd39 | |||
| 9e664af1c6 | |||
| 7736ed589e | |||
| f22504d080 | |||
| f22e5cc200 | |||
| 87b73b6c67 | |||
| 36906f6567 | |||
| 52edb54d21 | |||
| 88b88b9b64 | |||
| 76fcad0b53 | |||
| 01e520b082 | |||
| 1d2a0fe4c8 | |||
| 0f19ced9d3 | |||
| 4ca32c039d | |||
| 81ec701240 | |||
| b16d614495 | |||
| 5f7e37187f | |||
| 622fd6cf46 | |||
| b9d73518dd | |||
| 17bdf45ac1 | |||
| 36052e2c61 | |||
| 06d232f889 | |||
| f9b3c749e0 | |||
| 63a59753af | |||
| 20696e7827 | |||
| 127c9862da | |||
| fee9473cac | |||
| 5337b72853 | |||
| 9bc5d91106 | |||
| 45ae66e9bf | |||
| f03cf34370 | |||
| 47db2a3bd5 | |||
| 40cd961eab | |||
| 34cdd4bf0f | |||
| b0ef58e5ca | |||
| b6020b5ea8 | |||
| ee544fcf31 | |||
| 886b0ac0ca | |||
| ed4070a3d1 |
@@ -37,7 +37,7 @@ The full documentation for Reticulum is available at [markqvist.github.io/Reticu
|
||||
|
||||
You can also [download the Reticulum manual as a PDF](https://github.com/markqvist/Reticulum/raw/master/docs/Reticulum%20Manual.pdf)
|
||||
|
||||
For more info, see [unsigned.io/projects/reticulum](https://unsigned.io/projects/reticulum/)
|
||||
For more info, see [reticulum.network](https://reticulum.network/)
|
||||
|
||||
## Notable Features
|
||||
- Coordination-less globally unique addressing and identification
|
||||
@@ -62,6 +62,11 @@ For more info, see [unsigned.io/projects/reticulum](https://unsigned.io/projects
|
||||
- Total bandwidth cost of setting up an encrypted link is 3 packets totaling 297 bytes
|
||||
- Low cost of keeping links open at only 0.44 bits per second
|
||||
|
||||
## Development Roadmap
|
||||
While Reticulum is already a fully featured and functional networking stack, many improvements and additions are actively being worked on, and planned for the future.
|
||||
|
||||
To learn more about the direction and future of Reticulum, please see the [Development Roadmap](./Roadmap.md).
|
||||
|
||||
## Examples of Reticulum Applications
|
||||
If you want to quickly get an idea of what Reticulum can do, take a look at the
|
||||
following resources.
|
||||
@@ -177,39 +182,6 @@ features are implemented and functioning, but additions will probably occur as
|
||||
real-world use is explored. There will be bugs. The API and wire-format can be
|
||||
considered relatively stable at the moment, but could change if warranted.
|
||||
|
||||
## Development Roadmap
|
||||
- Version 0.4.0
|
||||
- Improving [the manual](https://markqvist.github.io/Reticulum/manual/) with sections specifically for beginners
|
||||
- Performance and memory optimisations
|
||||
- Utilities for managing identities, signing and encryption
|
||||
- User friendly interface configuration tool
|
||||
- More interface types for even broader compatibility
|
||||
- Plain ESP32 devices (ESP-Now, WiFi, Bluetooth, etc.)
|
||||
- More LoRa transceivers
|
||||
- IR Transceivers
|
||||
- Planned, but not yet scheduled
|
||||
- OpenWRT support
|
||||
- Metric-based path selection
|
||||
- Distributed Destination Naming System
|
||||
- Network-wide path balancing
|
||||
- Globally routable multicast
|
||||
- Bindings for other programming languages
|
||||
- Multiple paths in path table for quick recovery on link failures
|
||||
- A portable Reticulum implementation in C, see [#21](https://github.com/markqvist/Reticulum/discussions/21)
|
||||
- Easy way to share interface configurations, see [#19](https://github.com/markqvist/Reticulum/discussions/19)
|
||||
- More interface types
|
||||
- AT-compatible modems
|
||||
- Optical mediums
|
||||
- AWDL / OWL
|
||||
- HF Modems
|
||||
- CAN-bus
|
||||
- ZeroMQ
|
||||
- MQTT
|
||||
- XBee
|
||||
- SPI
|
||||
- i²c
|
||||
- Tor
|
||||
|
||||
## Dependencies
|
||||
The installation of the default `rns` package requires the dependencies listed
|
||||
below. Almost all systems and distributions have readily available packages for
|
||||
@@ -273,7 +245,7 @@ file:
|
||||
[[RNS Testnet I2P Hub A]]
|
||||
type = I2PInterface
|
||||
enabled = yes
|
||||
peers = mrwqlsioq4hoo2lmeeud7dkfscnm7yxak7dmiyvsrnpfag3z5tsq.b32.i2p
|
||||
peers = pmlm3l5rpympihoy2o5ago43kluei2jjjzsalcuiuylbve3mwi2a.b32.i2p
|
||||
|
||||
# Interface to I2P Hub B
|
||||
[[RNS Testnet I2P Hub B]]
|
||||
|
||||
@@ -33,7 +33,7 @@ def hkdf(length=None, derive_from=None, salt=None, context=None):
|
||||
if length == None or length < 1:
|
||||
raise ValueError("Invalid output key length")
|
||||
|
||||
if derive_from == "None" or derive_from == "":
|
||||
if derive_from == None or derive_from == "":
|
||||
raise ValueError("Cannot derive key from empty input material")
|
||||
|
||||
if salt == None or len(salt) == 0:
|
||||
@@ -54,4 +54,4 @@ def hkdf(length=None, derive_from=None, salt=None, context=None):
|
||||
block = hmac_sha256(pseudorandom_key, block + context + bytes([i + 1]))
|
||||
derived += block
|
||||
|
||||
return derived[:length]
|
||||
return derived[:length]
|
||||
|
||||
+48
-20
@@ -69,6 +69,8 @@ class Destination:
|
||||
OUT = 0x12;
|
||||
directions = [IN, OUT]
|
||||
|
||||
PR_TAG_WINDOW = 30
|
||||
|
||||
@staticmethod
|
||||
def expand_name(identity, app_name, *aspects):
|
||||
"""
|
||||
@@ -132,6 +134,7 @@ class Destination:
|
||||
self.proof_strategy = Destination.PROVE_NONE
|
||||
self.mtu = 0
|
||||
|
||||
self.path_responses = {}
|
||||
self.links = []
|
||||
|
||||
if identity == None and direction == Destination.IN and self.type != Destination.PLAIN:
|
||||
@@ -163,7 +166,7 @@ class Destination:
|
||||
return "<"+self.name+"/"+self.hexhash+">"
|
||||
|
||||
|
||||
def announce(self, app_data=None, path_response=False, send=True):
|
||||
def announce(self, app_data=None, path_response=False, attached_interface=None, tag=None, send=True):
|
||||
"""
|
||||
Creates an announce packet for this destination and broadcasts it on all
|
||||
relevant interfaces. Application specific data can be added to the announce.
|
||||
@@ -173,35 +176,60 @@ class Destination:
|
||||
"""
|
||||
if self.type != Destination.SINGLE:
|
||||
raise TypeError("Only SINGLE destination types can be announced")
|
||||
|
||||
destination_hash = self.hash
|
||||
random_hash = RNS.Identity.get_random_hash()[0:5]+int(time.time()).to_bytes(5, "big")
|
||||
|
||||
if app_data == None and self.default_app_data != None:
|
||||
if isinstance(self.default_app_data, bytes):
|
||||
app_data = self.default_app_data
|
||||
elif callable(self.default_app_data):
|
||||
returned_app_data = self.default_app_data()
|
||||
if isinstance(returned_app_data, bytes):
|
||||
app_data = returned_app_data
|
||||
|
||||
signed_data = self.hash+self.identity.get_public_key()+self.name_hash+random_hash
|
||||
if app_data != None:
|
||||
signed_data += app_data
|
||||
now = time.time()
|
||||
stale_responses = []
|
||||
for entry_tag in self.path_responses:
|
||||
entry = self.path_responses[entry_tag]
|
||||
if now > entry[0]+Destination.PR_TAG_WINDOW:
|
||||
stale_responses.append(entry_tag)
|
||||
|
||||
signature = self.identity.sign(signed_data)
|
||||
for entry_tag in stale_responses:
|
||||
self.path_responses.pop(entry_tag)
|
||||
|
||||
announce_data = self.identity.get_public_key()+self.name_hash+random_hash+signature
|
||||
if (path_response == True and tag != None) and tag in self.path_responses:
|
||||
# This code is currently not used, since Transport will block duplicate
|
||||
# path requests based on tags. When multi-path support is implemented in
|
||||
# Transport, this will allow Transport to detect redundant paths to the
|
||||
# same destination, and select the best one based on chosen criteria,
|
||||
# since it will be able to detect that a single emitted announce was
|
||||
# received via multiple paths. The difference in reception time will
|
||||
# potentially also be useful in determining characteristics of the
|
||||
# multiple available paths, and to choose the best one.
|
||||
RNS.log("Using cached announce data for answering path request with tag "+RNS.prettyhexrep(tag), RNS.LOG_EXTREME)
|
||||
announce_data = self.path_responses[tag][1]
|
||||
|
||||
else:
|
||||
destination_hash = self.hash
|
||||
random_hash = RNS.Identity.get_random_hash()[0:5]+int(time.time()).to_bytes(5, "big")
|
||||
|
||||
if app_data != None:
|
||||
announce_data += app_data
|
||||
if app_data == None and self.default_app_data != None:
|
||||
if isinstance(self.default_app_data, bytes):
|
||||
app_data = self.default_app_data
|
||||
elif callable(self.default_app_data):
|
||||
returned_app_data = self.default_app_data()
|
||||
if isinstance(returned_app_data, bytes):
|
||||
app_data = returned_app_data
|
||||
|
||||
signed_data = self.hash+self.identity.get_public_key()+self.name_hash+random_hash
|
||||
if app_data != None:
|
||||
signed_data += app_data
|
||||
|
||||
signature = self.identity.sign(signed_data)
|
||||
|
||||
announce_data = self.identity.get_public_key()+self.name_hash+random_hash+signature
|
||||
|
||||
if app_data != None:
|
||||
announce_data += app_data
|
||||
|
||||
self.path_responses[tag] = [time.time(), announce_data]
|
||||
|
||||
if path_response:
|
||||
announce_context = RNS.Packet.PATH_RESPONSE
|
||||
else:
|
||||
announce_context = RNS.Packet.NONE
|
||||
|
||||
announce_packet = RNS.Packet(self, announce_data, RNS.Packet.ANNOUNCE, context = announce_context)
|
||||
announce_packet = RNS.Packet(self, announce_data, RNS.Packet.ANNOUNCE, context = announce_context, attached_interface = attached_interface)
|
||||
|
||||
if send:
|
||||
announce_packet.send()
|
||||
|
||||
+15
-3
@@ -255,10 +255,22 @@ class Identity:
|
||||
RNS.Identity.remember(packet.get_hash(), destination_hash, public_key, app_data)
|
||||
del announced_identity
|
||||
|
||||
if hasattr(packet, "transport_id") and packet.transport_id != None:
|
||||
RNS.log("Valid announce for "+RNS.prettyhexrep(destination_hash)+" "+str(packet.hops)+" hops away, received via "+RNS.prettyhexrep(packet.transport_id)+" on "+str(packet.receiving_interface), RNS.LOG_EXTREME)
|
||||
if packet.rssi != None or packet.snr != None:
|
||||
signal_str = " ["
|
||||
if packet.rssi != None:
|
||||
signal_str += "RSSI "+str(packet.rssi)+"dBm"
|
||||
if packet.snr != None:
|
||||
signal_str += ", "
|
||||
if packet.snr != None:
|
||||
signal_str += "SNR "+str(packet.snr)+"dB"
|
||||
signal_str += "]"
|
||||
else:
|
||||
RNS.log("Valid announce for "+RNS.prettyhexrep(destination_hash)+" "+str(packet.hops)+" hops away, received on "+str(packet.receiving_interface), RNS.LOG_EXTREME)
|
||||
signal_str = ""
|
||||
|
||||
if hasattr(packet, "transport_id") and packet.transport_id != None:
|
||||
RNS.log("Valid announce for "+RNS.prettyhexrep(destination_hash)+" "+str(packet.hops)+" hops away, received via "+RNS.prettyhexrep(packet.transport_id)+" on "+str(packet.receiving_interface)+signal_str, RNS.LOG_EXTREME)
|
||||
else:
|
||||
RNS.log("Valid announce for "+RNS.prettyhexrep(destination_hash)+" "+str(packet.hops)+" hops away, received on "+str(packet.receiving_interface)+signal_str, RNS.LOG_EXTREME)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@@ -44,6 +44,8 @@ class KISS():
|
||||
CMD_RADIO_STATE = 0x06
|
||||
CMD_RADIO_LOCK = 0x07
|
||||
CMD_DETECT = 0x08
|
||||
CMD_IMPLICIT = 0x09
|
||||
CMD_LEAVE = 0x0A
|
||||
CMD_READY = 0x0F
|
||||
CMD_STAT_RX = 0x21
|
||||
CMD_STAT_TX = 0x22
|
||||
@@ -51,6 +53,11 @@ class KISS():
|
||||
CMD_STAT_SNR = 0x24
|
||||
CMD_BLINK = 0x30
|
||||
CMD_RANDOM = 0x40
|
||||
CMD_FB_EXT = 0x41
|
||||
CMD_FB_READ = 0x42
|
||||
CMD_FB_WRITE = 0x43
|
||||
CMD_FB_READL = 0x44
|
||||
CMD_BT_CTRL = 0x46
|
||||
CMD_PLATFORM = 0x48
|
||||
CMD_MCU = 0x49
|
||||
CMD_FW_VERSION = 0x50
|
||||
@@ -68,6 +75,7 @@ class KISS():
|
||||
ERROR_INITRADIO = 0x01
|
||||
ERROR_TXFAILED = 0x02
|
||||
ERROR_EEPROM_LOCKED = 0x03
|
||||
ERROR_INVALID_FIRMWARE = 0x10
|
||||
|
||||
PLATFORM_AVR = 0x90
|
||||
PLATFORM_ESP32 = 0x80
|
||||
@@ -77,7 +85,135 @@ class KISS():
|
||||
data = data.replace(bytes([0xdb]), bytes([0xdb, 0xdd]))
|
||||
data = data.replace(bytes([0xc0]), bytes([0xdb, 0xdc]))
|
||||
return data
|
||||
|
||||
|
||||
class AndroidBluetoothManager():
|
||||
def __init__(self, owner, target_device_name = None, target_device_address = None):
|
||||
from jnius import autoclass
|
||||
self.owner = owner
|
||||
self.connected = False
|
||||
self.target_device_name = target_device_name
|
||||
self.target_device_address = target_device_address
|
||||
self.potential_remote_devices = []
|
||||
self.rfcomm_socket = None
|
||||
self.connected_device = None
|
||||
self.connection_failed = False
|
||||
self.bt_adapter = autoclass('android.bluetooth.BluetoothAdapter')
|
||||
self.bt_device = autoclass('android.bluetooth.BluetoothDevice')
|
||||
self.bt_socket = autoclass('android.bluetooth.BluetoothSocket')
|
||||
self.bt_rfcomm_service_record = autoclass('java.util.UUID').fromString("00001101-0000-1000-8000-00805F9B34FB")
|
||||
self.buffered_input_stream = autoclass('java.io.BufferedInputStream')
|
||||
|
||||
def connect(self, device_address=None):
|
||||
self.rfcomm_socket = self.remote_device.createRfcommSocketToServiceRecord(self.bt_rfcomm_service_record)
|
||||
|
||||
def bt_enabled(self):
|
||||
return self.bt_adapter.getDefaultAdapter().isEnabled()
|
||||
|
||||
def get_paired_devices(self):
|
||||
if self.bt_enabled():
|
||||
return self.bt_adapter.getDefaultAdapter().getBondedDevices()
|
||||
else:
|
||||
RNS.log("Could not query paired devices, Bluetooth is disabled", RNS.LOG_DEBUG)
|
||||
return []
|
||||
|
||||
def get_potential_devices(self):
|
||||
potential_devices = []
|
||||
for device in self.get_paired_devices():
|
||||
if self.target_device_address != None:
|
||||
if str(device.getAddress()).replace(":", "").lower() == str(self.target_device_address).replace(":", "").lower():
|
||||
if self.target_device_name == None:
|
||||
potential_devices.append(device)
|
||||
else:
|
||||
if device.getName().lower() == self.target_device_name.lower():
|
||||
potential_devices.append(device)
|
||||
|
||||
elif self.target_device_name != None:
|
||||
if device.getName().lower() == self.target_device_name.lower():
|
||||
potential_devices.append(device)
|
||||
|
||||
else:
|
||||
if device.getName().lower().startswith("rnode "):
|
||||
potential_devices.append(device)
|
||||
|
||||
return potential_devices
|
||||
|
||||
def connect_any_device(self):
|
||||
if (self.rfcomm_socket != None and not self.rfcomm_socket.isConnected()) or self.rfcomm_socket == None:
|
||||
self.connection_failed = False
|
||||
if len(self.potential_remote_devices) == 0:
|
||||
self.potential_remote_devices = self.get_potential_devices()
|
||||
if len(self.potential_remote_devices) == 0:
|
||||
RNS.log("No suitable bluetooth devices available, can't connect", RNS.LOG_DEBUG)
|
||||
return
|
||||
|
||||
while not self.connected and len(self.potential_remote_devices) > 0:
|
||||
device = self.potential_remote_devices.pop()
|
||||
try:
|
||||
self.rfcomm_socket = device.createRfcommSocketToServiceRecord(self.bt_rfcomm_service_record)
|
||||
if self.rfcomm_socket == None:
|
||||
raise IOError("Bluetooth stack returned no socket object")
|
||||
else:
|
||||
if not self.rfcomm_socket.isConnected():
|
||||
try:
|
||||
self.rfcomm_socket.connect()
|
||||
self.rfcomm_reader = self.buffered_input_stream(self.rfcomm_socket.getInputStream(), 1024)
|
||||
self.rfcomm_writer = self.rfcomm_socket.getOutputStream()
|
||||
self.connected = True
|
||||
self.connected_device = device
|
||||
RNS.log("Bluetooth device "+str(self.connected_device.getName())+" "+str(self.connected_device.getAddress())+" connected.")
|
||||
except Exception as e:
|
||||
raise IOError("The Bluetooth RFcomm socket could not be connected: "+str(e))
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Could not create and connect Bluetooth RFcomm socket for "+str(device.getName())+" "+str(device.getAddress()), RNS.LOG_ERROR)
|
||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
def close(self):
|
||||
if self.connected:
|
||||
if self.rfcomm_reader != None:
|
||||
self.rfcomm_reader.close()
|
||||
self.rfcomm_reader = None
|
||||
|
||||
if self.rfcomm_writer != None:
|
||||
self.rfcomm_writer.close()
|
||||
self.rfcomm_writer = None
|
||||
|
||||
if self.rfcomm_socket != None:
|
||||
self.rfcomm_socket.close()
|
||||
|
||||
self.connected = False
|
||||
self.connected_device = None
|
||||
self.potential_remote_devices = []
|
||||
|
||||
|
||||
def read(self, len = None):
|
||||
if self.connection_failed:
|
||||
raise IOError("Bluetooth connection failed")
|
||||
else:
|
||||
if self.connected and self.rfcomm_reader != None:
|
||||
available = self.rfcomm_reader.available()
|
||||
if available > 0:
|
||||
if hasattr(self.rfcomm_reader, "readNBytes"):
|
||||
return self.rfcomm_reader.readNBytes(available)
|
||||
else:
|
||||
# Compatibility mode for older android versions lacking readNBytes
|
||||
rb = self.rfcomm_reader.read().to_bytes(1, "big")
|
||||
return rb
|
||||
else:
|
||||
return bytes([])
|
||||
else:
|
||||
raise IOError("No RFcomm socket available")
|
||||
|
||||
def write(self, data):
|
||||
try:
|
||||
self.rfcomm_writer.write(data)
|
||||
self.rfcomm_writer.flush()
|
||||
return len(data)
|
||||
except Exception as e:
|
||||
RNS.log("Bluetooth connection failed for "+str(self), RNS.LOG_ERROR)
|
||||
self.connection_failed = True
|
||||
return 0
|
||||
|
||||
|
||||
class RNodeInterface(Interface):
|
||||
MAX_CHUNK = 32768
|
||||
@@ -90,11 +226,96 @@ class RNodeInterface(Interface):
|
||||
CALLSIGN_MAX_LEN = 32
|
||||
|
||||
REQUIRED_FW_VER_MAJ = 1
|
||||
REQUIRED_FW_VER_MIN = 26
|
||||
REQUIRED_FW_VER_MIN = 52
|
||||
|
||||
RECONNECT_WAIT = 5
|
||||
PORT_IO_TIMEOUT = 3
|
||||
|
||||
def __init__(self, owner, name, port, frequency = None, bandwidth = None, txpower = None, sf = None, cr = None, flow_control = False, id_interval = None, id_callsign = None):
|
||||
@classmethod
|
||||
def bluetooth_control(device_serial = None, port = None, enable_bluetooth = False, disable_bluetooth = False, pairing_mode = False):
|
||||
if (port != None or device_serial != None) and (enable_bluetooth or disable_bluetooth or pairing_mode):
|
||||
serial = None
|
||||
bluetooth_state = None
|
||||
if pairing_mode:
|
||||
bluetooth_state = 0x01
|
||||
elif enable_bluetooth:
|
||||
bluetooth_state = 0x01
|
||||
elif disable_bluetooth:
|
||||
bluetooth_state = 0x00
|
||||
|
||||
if port != None:
|
||||
RNS.log("Opening serial port "+port+"...")
|
||||
# Get device parameters
|
||||
from usb4a import usb
|
||||
device = usb.get_usb_device(port)
|
||||
if device:
|
||||
vid = device.getVendorId()
|
||||
pid = device.getProductId()
|
||||
|
||||
# Driver overrides for speficic chips
|
||||
from usbserial4a import serial4a as pyserial
|
||||
proxy = pyserial.get_serial_port
|
||||
if vid == 0x1A86 and pid == 0x55D4:
|
||||
# Force CDC driver for Qinheng CH34x
|
||||
RNS.log("Using CDC driver for "+RNS.hexrep(vid)+":"+RNS.hexrep(pid), RNS.LOG_DEBUG)
|
||||
from usbserial4a.cdcacmserial4a import CdcAcmSerial
|
||||
proxy = CdcAcmSerial
|
||||
|
||||
serial = proxy(
|
||||
port,
|
||||
baudrate = 115200,
|
||||
bytesize = 8,
|
||||
parity = "N",
|
||||
stopbits = 1,
|
||||
xonxoff = False,
|
||||
rtscts = False,
|
||||
timeout = None,
|
||||
inter_byte_timeout = None,
|
||||
# write_timeout = wtimeout,
|
||||
dsrdtr = False,
|
||||
)
|
||||
|
||||
if vid == 0x0403:
|
||||
# Hardware parameters for FTDI devices @ 115200 baud
|
||||
serial.DEFAULT_READ_BUFFER_SIZE = 16 * 1024
|
||||
serial.USB_READ_TIMEOUT_MILLIS = 100
|
||||
serial.timeout = 0.1
|
||||
elif vid == 0x10C4:
|
||||
# Hardware parameters for SiLabs CP210x @ 115200 baud
|
||||
serial.DEFAULT_READ_BUFFER_SIZE = 64
|
||||
serial.USB_READ_TIMEOUT_MILLIS = 12
|
||||
serial.timeout = 0.012
|
||||
elif vid == 0x1A86 and pid == 0x55D4:
|
||||
# Hardware parameters for Qinheng CH34x @ 115200 baud
|
||||
serial.DEFAULT_READ_BUFFER_SIZE = 64
|
||||
serial.USB_READ_TIMEOUT_MILLIS = 12
|
||||
serial.timeout = 0.1
|
||||
else:
|
||||
# Default values
|
||||
serial.DEFAULT_READ_BUFFER_SIZE = 1 * 1024
|
||||
serial.USB_READ_TIMEOUT_MILLIS = 100
|
||||
serial.timeout = 0.1
|
||||
|
||||
elif device_serial != None:
|
||||
serial = device_serial
|
||||
|
||||
if serial != None:
|
||||
if serial.is_open:
|
||||
kiss_command = bytes([KISS.FEND, KISS.CMD_BT_CTRL, bluetooth_state, KISS.FEND])
|
||||
serial.write(kiss_command)
|
||||
if pairing_mode:
|
||||
kiss_command = bytes([KISS.FEND, KISS.CMD_BT_CTRL, 0x02, KISS.FEND])
|
||||
serial.write(kiss_command)
|
||||
|
||||
if port != None:
|
||||
serial.close()
|
||||
|
||||
|
||||
def __init__(
|
||||
self, owner, name, port, frequency = None, bandwidth = None, txpower = None,
|
||||
sf = None, cr = None, flow_control = False, id_interval = None,
|
||||
allow_bluetooth = False, target_device_name = None,
|
||||
target_device_address = None, id_callsign = None):
|
||||
import importlib
|
||||
if RNS.vendor.platformutils.is_android():
|
||||
self.on_android = True
|
||||
@@ -107,6 +328,18 @@ class RNodeInterface(Interface):
|
||||
|
||||
from usbserial4a import serial4a as serial
|
||||
self.parity = "N"
|
||||
|
||||
self.bt_target_device_name = target_device_name
|
||||
self.bt_target_device_address = target_device_address
|
||||
if allow_bluetooth:
|
||||
self.bt_manager = AndroidBluetoothManager(
|
||||
owner = self,
|
||||
target_device_name = self.bt_target_device_name,
|
||||
target_device_address = self.bt_target_device_address
|
||||
)
|
||||
|
||||
else:
|
||||
self.bt_manager = None
|
||||
|
||||
else:
|
||||
RNS.log("Could not load USB serial module for Android, RNode interface cannot be created.", RNS.LOG_CRITICAL)
|
||||
@@ -130,6 +363,8 @@ class RNodeInterface(Interface):
|
||||
self.stopbits = 1
|
||||
self.timeout = 150
|
||||
self.online = False
|
||||
self.hw_errors = []
|
||||
self.allow_bluetooth = allow_bluetooth
|
||||
|
||||
self.frequency = frequency
|
||||
self.bandwidth = bandwidth
|
||||
@@ -139,6 +374,7 @@ class RNodeInterface(Interface):
|
||||
self.state = KISS.RADIO_STATE_OFF
|
||||
self.bitrate = 0
|
||||
self.platform = None
|
||||
self.display = None
|
||||
self.mcu = None
|
||||
self.detected = False
|
||||
self.firmware_ok = False
|
||||
@@ -165,6 +401,9 @@ class RNodeInterface(Interface):
|
||||
self.flow_control = flow_control
|
||||
self.interface_ready = False
|
||||
self.announce_rate_target = None
|
||||
self.last_port_io = 0
|
||||
self.port_io_timeout = RNodeInterface.PORT_IO_TIMEOUT
|
||||
self.last_imagedata = None
|
||||
|
||||
self.validcfg = True
|
||||
if (self.frequency < RNodeInterface.FREQ_MIN or self.frequency > RNodeInterface.FREQ_MAX):
|
||||
@@ -205,75 +444,117 @@ class RNodeInterface(Interface):
|
||||
try:
|
||||
self.open_port()
|
||||
|
||||
if self.serial.is_open:
|
||||
self.configure_device()
|
||||
if self.serial != None:
|
||||
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()
|
||||
else:
|
||||
raise IOError("Could not connect to any Bluetooth devices")
|
||||
else:
|
||||
raise IOError("Could not open serial port")
|
||||
raise IOError("Neither serial port nor Bluetooth devices available")
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Could not open serial port for interface "+str(self), RNS.LOG_ERROR)
|
||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
RNS.log("Reticulum will attempt to bring up this interface periodically", RNS.LOG_ERROR)
|
||||
thread = threading.Thread(target=self.reconnect_port)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
if len(self.hw_errors) == 0:
|
||||
RNS.log("Reticulum will attempt to bring up this interface periodically", RNS.LOG_ERROR)
|
||||
thread = threading.Thread(target=self.reconnect_port)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
|
||||
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")
|
||||
|
||||
def write_mux(self, data):
|
||||
if self.serial != None:
|
||||
written = self.serial.write(data)
|
||||
self.last_port_io = time.time()
|
||||
return written
|
||||
elif self.bt_manager != None:
|
||||
written = self.bt_manager.write(data)
|
||||
if (written == len(data)):
|
||||
self.last_port_io = time.time()
|
||||
return written
|
||||
else:
|
||||
raise IOError("No ports available for writing")
|
||||
|
||||
def open_port(self):
|
||||
RNS.log("Opening serial port "+self.port+"...")
|
||||
# Get device parameters
|
||||
from usb4a import usb
|
||||
device = usb.get_usb_device(self.port)
|
||||
if device:
|
||||
vid = device.getVendorId()
|
||||
pid = device.getProductId()
|
||||
if self.port != None:
|
||||
RNS.log("Opening serial port "+self.port+"...")
|
||||
# Get device parameters
|
||||
from usb4a import usb
|
||||
device = usb.get_usb_device(self.port)
|
||||
if device:
|
||||
vid = device.getVendorId()
|
||||
pid = device.getProductId()
|
||||
|
||||
# Driver overrides for speficic chips
|
||||
proxy = self.pyserial.get_serial_port
|
||||
if vid == 0x1A86 and pid == 0x55D4:
|
||||
# Force CDC driver for Qinheng CH34x
|
||||
RNS.log(str(self)+" using CDC driver for "+RNS.hexrep(vid)+":"+RNS.hexrep(pid), RNS.LOG_DEBUG)
|
||||
from usbserial4a.cdcacmserial4a import CdcAcmSerial
|
||||
proxy = CdcAcmSerial
|
||||
# Driver overrides for speficic chips
|
||||
proxy = self.pyserial.get_serial_port
|
||||
if vid == 0x1A86 and pid == 0x55D4:
|
||||
# Force CDC driver for Qinheng CH34x
|
||||
RNS.log(str(self)+" using CDC driver for "+RNS.hexrep(vid)+":"+RNS.hexrep(pid), RNS.LOG_DEBUG)
|
||||
from usbserial4a.cdcacmserial4a import CdcAcmSerial
|
||||
proxy = CdcAcmSerial
|
||||
|
||||
self.serial = proxy(
|
||||
self.port,
|
||||
baudrate = self.speed,
|
||||
bytesize = self.databits,
|
||||
parity = self.parity,
|
||||
stopbits = self.stopbits,
|
||||
xonxoff = False,
|
||||
rtscts = False,
|
||||
timeout = None,
|
||||
inter_byte_timeout = None,
|
||||
# write_timeout = wtimeout,
|
||||
dsrdtr = False,
|
||||
)
|
||||
self.serial = proxy(
|
||||
self.port,
|
||||
baudrate = self.speed,
|
||||
bytesize = self.databits,
|
||||
parity = self.parity,
|
||||
stopbits = self.stopbits,
|
||||
xonxoff = False,
|
||||
rtscts = False,
|
||||
timeout = None,
|
||||
inter_byte_timeout = None,
|
||||
# write_timeout = wtimeout,
|
||||
dsrdtr = False,
|
||||
)
|
||||
|
||||
if vid == 0x0403:
|
||||
# Hardware parameters for FTDI devices @ 115200 baud
|
||||
self.serial.DEFAULT_READ_BUFFER_SIZE = 16 * 1024
|
||||
self.serial.USB_READ_TIMEOUT_MILLIS = 100
|
||||
self.serial.timeout = 0.1
|
||||
elif vid == 0x10C4:
|
||||
# Hardware parameters for SiLabs CP210x @ 115200 baud
|
||||
self.serial.DEFAULT_READ_BUFFER_SIZE = 64
|
||||
self.serial.USB_READ_TIMEOUT_MILLIS = 12
|
||||
self.serial.timeout = 0.012
|
||||
elif vid == 0x1A86 and pid == 0x55D4:
|
||||
# Hardware parameters for Qinheng CH34x @ 115200 baud
|
||||
self.serial.DEFAULT_READ_BUFFER_SIZE = 64
|
||||
self.serial.USB_READ_TIMEOUT_MILLIS = 12
|
||||
self.serial.timeout = 0.1
|
||||
else:
|
||||
# Default values
|
||||
self.serial.DEFAULT_READ_BUFFER_SIZE = 1 * 1024
|
||||
self.serial.USB_READ_TIMEOUT_MILLIS = 100
|
||||
self.serial.timeout = 0.1
|
||||
if vid == 0x0403:
|
||||
# Hardware parameters for FTDI devices @ 115200 baud
|
||||
self.serial.DEFAULT_READ_BUFFER_SIZE = 16 * 1024
|
||||
self.serial.USB_READ_TIMEOUT_MILLIS = 100
|
||||
self.serial.timeout = 0.1
|
||||
elif vid == 0x10C4:
|
||||
# Hardware parameters for SiLabs CP210x @ 115200 baud
|
||||
self.serial.DEFAULT_READ_BUFFER_SIZE = 64
|
||||
self.serial.USB_READ_TIMEOUT_MILLIS = 12
|
||||
self.serial.timeout = 0.012
|
||||
elif vid == 0x1A86 and pid == 0x55D4:
|
||||
# Hardware parameters for Qinheng CH34x @ 115200 baud
|
||||
self.serial.DEFAULT_READ_BUFFER_SIZE = 64
|
||||
self.serial.USB_READ_TIMEOUT_MILLIS = 12
|
||||
self.serial.timeout = 0.1
|
||||
else:
|
||||
# Default values
|
||||
self.serial.DEFAULT_READ_BUFFER_SIZE = 1 * 1024
|
||||
self.serial.USB_READ_TIMEOUT_MILLIS = 100
|
||||
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 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 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 write timeout set to "+str(self.serial.USB_WRITE_TIMEOUT_MILLIS)+"ms", RNS.LOG_DEBUG)
|
||||
|
||||
elif self.allow_bluetooth:
|
||||
if self.bt_manager == None:
|
||||
self.bt_manager = AndroidBluetoothManager(
|
||||
owner = self,
|
||||
target_device_name = self.bt_target_device_name,
|
||||
target_device_address = self.bt_target_device_address
|
||||
)
|
||||
|
||||
if self.bt_manager != None:
|
||||
self.bt_manager.connect_any_device()
|
||||
|
||||
|
||||
def configure_device(self):
|
||||
@@ -283,18 +564,22 @@ class RNodeInterface(Interface):
|
||||
thread.start()
|
||||
|
||||
self.detect()
|
||||
sleep(0.4)
|
||||
|
||||
|
||||
sleep(0.5)
|
||||
|
||||
if not self.detected:
|
||||
raise IOError("Could not detect device")
|
||||
# else:
|
||||
# if self.platform == KISS.PLATFORM_ESP32:
|
||||
# TODO: Consider reintroducing this
|
||||
# RNS.log("Resetting ESP32-based device before configuration...", RNS.LOG_VERBOSE)
|
||||
# self.hard_reset()
|
||||
else:
|
||||
if self.platform == KISS.PLATFORM_ESP32:
|
||||
self.display = True
|
||||
|
||||
if not self.firmware_ok:
|
||||
raise IOError("Invalid device firmware")
|
||||
|
||||
if self.serial != None and self.port != None:
|
||||
RNS.log("Serial port "+self.port+" is now open")
|
||||
if self.bt_manager != None and self.bt_manager.connected:
|
||||
RNS.log("Bluetooth connection to RNode now open")
|
||||
|
||||
RNS.log("Serial port "+self.port+" is now open")
|
||||
RNS.log("Configuring RNode interface...", RNS.LOG_VERBOSE)
|
||||
self.initRadio()
|
||||
if (self.validateRadioState()):
|
||||
@@ -306,38 +591,106 @@ class RNodeInterface(Interface):
|
||||
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.serial != None:
|
||||
self.serial.close()
|
||||
if self.bt_manager != None:
|
||||
self.bt_manager.close()
|
||||
|
||||
raise IOError("RNode interface did not pass configuration validation")
|
||||
|
||||
|
||||
def initRadio(self):
|
||||
self.setFrequency()
|
||||
time.sleep(0.1)
|
||||
time.sleep(0.15)
|
||||
|
||||
self.setBandwidth()
|
||||
time.sleep(0.1)
|
||||
time.sleep(0.15)
|
||||
|
||||
self.setTXPower()
|
||||
time.sleep(0.1)
|
||||
time.sleep(0.15)
|
||||
|
||||
self.setSpreadingFactor()
|
||||
time.sleep(0.1)
|
||||
time.sleep(0.15)
|
||||
|
||||
self.setCodingRate()
|
||||
time.sleep(0.1)
|
||||
time.sleep(0.15)
|
||||
|
||||
self.setRadioState(KISS.RADIO_STATE_ON)
|
||||
time.sleep(0.1)
|
||||
time.sleep(0.15)
|
||||
|
||||
def detect(self):
|
||||
kiss_command = bytes([KISS.FEND, KISS.CMD_DETECT, KISS.DETECT_REQ, KISS.FEND, KISS.CMD_FW_VERSION, 0x00, KISS.FEND, KISS.CMD_PLATFORM, 0x00, KISS.FEND, KISS.CMD_MCU, 0x00, KISS.FEND])
|
||||
written = self.serial.write(kiss_command)
|
||||
written = self.write_mux(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while detecting hardware for "+self(str))
|
||||
raise IOError("An IO error occurred while detecting hardware for "+str(self))
|
||||
|
||||
def leave(self):
|
||||
kiss_command = bytes([KISS.FEND, KISS.CMD_LEAVE, 0xFF, KISS.FEND])
|
||||
written = self.write_mux(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while sending host left command to device")
|
||||
|
||||
def enable_bluetooth(self):
|
||||
kiss_command = bytes([KISS.FEND, KISS.CMD_BT_CTRL, 0x01, KISS.FEND])
|
||||
written = self.write_mux(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while sending bluetooth enable command to device")
|
||||
|
||||
def disable_bluetooth(self):
|
||||
kiss_command = bytes([KISS.FEND, KISS.CMD_BT_CTRL, 0x00, KISS.FEND])
|
||||
written = self.write_mux(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while sending bluetooth disable command to device")
|
||||
|
||||
def bluetooth_pair(self):
|
||||
kiss_command = bytes([KISS.FEND, KISS.CMD_BT_CTRL, 0x02, KISS.FEND])
|
||||
written = self.write_mux(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while sending bluetooth pair command to device")
|
||||
|
||||
def enable_external_framebuffer(self):
|
||||
if self.display != None:
|
||||
kiss_command = bytes([KISS.FEND, KISS.CMD_FB_EXT, 0x01, KISS.FEND])
|
||||
written = self.write_mux(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while enabling external framebuffer on device")
|
||||
|
||||
def disable_external_framebuffer(self):
|
||||
if self.display != None:
|
||||
kiss_command = bytes([KISS.FEND, KISS.CMD_FB_EXT, 0x00, KISS.FEND])
|
||||
written = self.write_mux(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while disabling external framebuffer on device")
|
||||
|
||||
FB_PIXEL_WIDTH = 64
|
||||
FB_BITS_PER_PIXEL = 1
|
||||
FB_PIXELS_PER_BYTE = 8//FB_BITS_PER_PIXEL
|
||||
FB_BYTES_PER_LINE = FB_PIXEL_WIDTH//FB_PIXELS_PER_BYTE
|
||||
def display_image(self, imagedata):
|
||||
self.last_imagedata = imagedata
|
||||
if self.display != None:
|
||||
lines = len(imagedata)//8
|
||||
for line in range(lines):
|
||||
line_start = line*RNodeInterface.FB_BYTES_PER_LINE
|
||||
line_end = line_start+RNodeInterface.FB_BYTES_PER_LINE
|
||||
line_data = bytes(imagedata[line_start:line_end])
|
||||
self.write_framebuffer(line, line_data)
|
||||
|
||||
def write_framebuffer(self, line, line_data):
|
||||
if self.display != None:
|
||||
line_byte = line.to_bytes(1, byteorder="big", signed=False)
|
||||
data = line_byte+line_data
|
||||
escaped_data = KISS.escape(data)
|
||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_FB_WRITE])+escaped_data+bytes([KISS.FEND])
|
||||
|
||||
written = self.write_mux(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while writing framebuffer data device")
|
||||
|
||||
def hard_reset(self):
|
||||
kiss_command = bytes([KISS.FEND, KISS.CMD_RESET, 0xf8, KISS.FEND])
|
||||
written = self.serial.write(kiss_command)
|
||||
written = self.write_mux(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while restarting device")
|
||||
sleep(4.0);
|
||||
@@ -350,9 +703,9 @@ class RNodeInterface(Interface):
|
||||
data = KISS.escape(bytes([c1])+bytes([c2])+bytes([c3])+bytes([c4]))
|
||||
|
||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_FREQUENCY])+data+bytes([KISS.FEND])
|
||||
written = self.serial.write(kiss_command)
|
||||
written = self.write_mux(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while configuring frequency for "+self(str))
|
||||
raise IOError("An IO error occurred while configuring frequency for "+str(self))
|
||||
|
||||
def setBandwidth(self):
|
||||
c1 = self.bandwidth >> 24
|
||||
@@ -362,37 +715,37 @@ class RNodeInterface(Interface):
|
||||
data = KISS.escape(bytes([c1])+bytes([c2])+bytes([c3])+bytes([c4]))
|
||||
|
||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_BANDWIDTH])+data+bytes([KISS.FEND])
|
||||
written = self.serial.write(kiss_command)
|
||||
written = self.write_mux(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while configuring bandwidth for "+self(str))
|
||||
raise IOError("An IO error occurred while configuring bandwidth for "+str(self))
|
||||
|
||||
def setTXPower(self):
|
||||
txp = bytes([self.txpower])
|
||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_TXPOWER])+txp+bytes([KISS.FEND])
|
||||
written = self.serial.write(kiss_command)
|
||||
written = self.write_mux(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while configuring TX power for "+self(str))
|
||||
raise IOError("An IO error occurred while configuring TX power for "+str(self))
|
||||
|
||||
def setSpreadingFactor(self):
|
||||
sf = bytes([self.sf])
|
||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_SF])+sf+bytes([KISS.FEND])
|
||||
written = self.serial.write(kiss_command)
|
||||
written = self.write_mux(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while configuring spreading factor for "+self(str))
|
||||
raise IOError("An IO error occurred while configuring spreading factor for "+str(self))
|
||||
|
||||
def setCodingRate(self):
|
||||
cr = bytes([self.cr])
|
||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_CR])+cr+bytes([KISS.FEND])
|
||||
written = self.serial.write(kiss_command)
|
||||
written = self.write_mux(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while configuring coding rate for "+self(str))
|
||||
raise IOError("An IO error occurred while configuring coding rate for "+str(self))
|
||||
|
||||
def setRadioState(self, state):
|
||||
self.state = state
|
||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_RADIO_STATE])+bytes([state])+bytes([KISS.FEND])
|
||||
written = self.serial.write(kiss_command)
|
||||
written = self.write_mux(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while configuring radio state for "+self(str))
|
||||
raise IOError("An IO error occurred while configuring radio state for "+str(self))
|
||||
|
||||
def validate_firmware(self):
|
||||
if (self.maj_version >= RNodeInterface.REQUIRED_FW_VER_MAJ):
|
||||
@@ -404,8 +757,11 @@ class RNodeInterface(Interface):
|
||||
|
||||
RNS.log("The firmware version of the connected RNode is "+str(self.maj_version)+"."+str(self.min_version), RNS.LOG_ERROR)
|
||||
RNS.log("This version of Reticulum requires at least version "+str(RNodeInterface.REQUIRED_FW_VER_MAJ)+"."+str(RNodeInterface.REQUIRED_FW_VER_MIN), RNS.LOG_ERROR)
|
||||
RNS.log("Please update your RNode firmware with rnodeconf (https://github.com/markqvist/rnodeconfigutil/)")
|
||||
RNS.panic()
|
||||
RNS.log("Please update your RNode firmware with rnodeconf from https://github.com/markqvist/rnodeconfigutil/")
|
||||
error_description = "The firmware version of the connected RNode is "+str(self.maj_version)+"."+str(self.min_version)+". "
|
||||
error_description += "This version of Reticulum requires at least version "+str(RNodeInterface.REQUIRED_FW_VER_MAJ)+"."+str(RNodeInterface.REQUIRED_FW_VER_MIN)+". "
|
||||
error_description += "Please update your RNode firmware with rnodeconf from: https://github.com/markqvist/rnodeconfigutil/"
|
||||
self.hw_errors.append({"error": KISS.ERROR_INVALID_FIRMWARE, "description": error_description})
|
||||
|
||||
|
||||
def validateRadioState(self):
|
||||
@@ -473,7 +829,7 @@ class RNodeInterface(Interface):
|
||||
data = KISS.escape(data)
|
||||
frame = bytes([0xc0])+bytes([0x00])+data+bytes([0xc0])
|
||||
|
||||
written = self.serial.write(frame)
|
||||
written = self.write_mux(frame)
|
||||
self.txb += datalen
|
||||
|
||||
if written != len(frame):
|
||||
@@ -501,11 +857,14 @@ class RNodeInterface(Interface):
|
||||
command_buffer = b""
|
||||
last_read_ms = int(time.time()*1000)
|
||||
|
||||
# TODO: Ensure hotplug support
|
||||
while self.serial.is_open:
|
||||
# TODO: Check multibyte reads
|
||||
serial_bytes = self.serial.read()
|
||||
# TODO: Ensure hotplug support for serial drivers
|
||||
# This should work now with the new time-based
|
||||
# detect polling.
|
||||
while (self.serial != None and self.serial.is_open) or (self.bt_manager != None and self.bt_manager.connected):
|
||||
serial_bytes = self.read_mux()
|
||||
got = len(serial_bytes)
|
||||
if got > 0:
|
||||
self.last_port_io = time.time()
|
||||
|
||||
for byte in serial_bytes:
|
||||
last_read_ms = int(time.time()*1000)
|
||||
@@ -678,11 +1037,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.processOutgoing(self.id_callsign)
|
||||
# sleep(0.08)
|
||||
|
||||
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)
|
||||
|
||||
except Exception as e:
|
||||
self.online = False
|
||||
RNS.log("A serial port error occurred, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
RNS.log("A serial port 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:
|
||||
@@ -691,21 +1058,51 @@ class RNodeInterface(Interface):
|
||||
RNS.log("Reticulum will attempt to reconnect the interface periodically.", RNS.LOG_ERROR)
|
||||
|
||||
self.online = False
|
||||
self.serial.close()
|
||||
|
||||
if self.serial != None:
|
||||
self.serial.close()
|
||||
|
||||
if self.bt_manager != None:
|
||||
self.bt_manager.close()
|
||||
|
||||
self.reconnect_port()
|
||||
|
||||
def reconnect_port(self):
|
||||
while not self.online:
|
||||
while not self.online and len(self.hw_errors) == 0:
|
||||
try:
|
||||
time.sleep(self.reconnect_w)
|
||||
RNS.log("Attempting to reconnect serial port "+str(self.port)+" for "+str(self)+"...", RNS.LOG_VERBOSE)
|
||||
if self.serial != None and self.port != None:
|
||||
RNS.log("Attempting to reconnect serial port "+str(self.port)+" for "+str(self)+"...", RNS.LOG_EXTREME)
|
||||
|
||||
if self.bt_manager != None:
|
||||
RNS.log("Attempting to reconnect Bluetooth device for "+str(self)+"...", RNS.LOG_EXTREME)
|
||||
|
||||
self.open_port()
|
||||
|
||||
if hasattr(self, "serial") and self.serial != None and 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)
|
||||
if self.online:
|
||||
if self.last_imagedata != None:
|
||||
self.display_image(self.last_imagedata)
|
||||
self.enable_external_framebuffer()
|
||||
|
||||
elif hasattr(self, "bt_manager") and self.bt_manager != None and self.bt_manager.connected:
|
||||
self.configure_device()
|
||||
if self.online:
|
||||
if self.last_imagedata != None:
|
||||
self.display_image(self.last_imagedata)
|
||||
self.enable_external_framebuffer()
|
||||
|
||||
RNS.log("Reconnected serial port for "+str(self))
|
||||
except Exception as e:
|
||||
RNS.log("Error while reconnecting RNode, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
if self.online:
|
||||
RNS.log("Reconnected serial port for "+str(self))
|
||||
|
||||
def detach(self):
|
||||
self.disable_external_framebuffer()
|
||||
self.setRadioState(KISS.RADIO_STATE_OFF)
|
||||
self.leave()
|
||||
|
||||
def __str__(self):
|
||||
return "RNodeInterface["+str(self.name)+"]"
|
||||
|
||||
@@ -48,6 +48,11 @@ class AutoInterface(Interface):
|
||||
|
||||
BITRATE_GUESS = 10*1000*1000
|
||||
|
||||
def handler_factory(self, callback):
|
||||
def create_handler(*args, **keys):
|
||||
return AutoInterfaceHandler(callback, *args, **keys)
|
||||
return create_handler
|
||||
|
||||
def __init__(self, owner, name, group_id=None, discovery_scope=None, discovery_port=None, data_port=None, allowed_interfaces=None, ignored_interfaces=None, configured_bitrate=None):
|
||||
import importlib
|
||||
if importlib.util.find_spec('netifaces') != None:
|
||||
@@ -70,8 +75,10 @@ class AutoInterface(Interface):
|
||||
self.peers = {}
|
||||
self.link_local_addresses = []
|
||||
self.adopted_interfaces = {}
|
||||
self.interface_servers = {}
|
||||
self.multicast_echoes = {}
|
||||
self.timed_out_interfaces = {}
|
||||
self.carrier_changed = False
|
||||
|
||||
self.outbound_udp_socket = None
|
||||
|
||||
@@ -199,11 +206,6 @@ class AutoInterface(Interface):
|
||||
peering_wait = self.announce_interval*1.2
|
||||
RNS.log(str(self)+" discovering peers for "+str(round(peering_wait, 2))+" seconds...", RNS.LOG_VERBOSE)
|
||||
|
||||
def handlerFactory(callback):
|
||||
def createHandler(*args, **keys):
|
||||
return AutoInterfaceHandler(callback, *args, **keys)
|
||||
return createHandler
|
||||
|
||||
self.owner = owner
|
||||
socketserver.UDPServer.address_family = socket.AF_INET6
|
||||
|
||||
@@ -212,9 +214,10 @@ class AutoInterface(Interface):
|
||||
addr_info = socket.getaddrinfo(local_addr, self.data_port, socket.AF_INET6, socket.SOCK_DGRAM)
|
||||
address = addr_info[0][4]
|
||||
|
||||
self.server = socketserver.UDPServer(address, handlerFactory(self.processIncoming))
|
||||
udp_server = socketserver.UDPServer(address, self.handler_factory(self.processIncoming))
|
||||
self.interface_servers[ifname] = udp_server
|
||||
|
||||
thread = threading.Thread(target=self.server.serve_forever)
|
||||
thread = threading.Thread(target=udp_server.serve_forever)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
@@ -277,7 +280,33 @@ class AutoInterface(Interface):
|
||||
if address["addr"].startswith("fe80:"):
|
||||
link_local_addr = address["addr"].split("%")[0]
|
||||
if link_local_addr != self.adopted_interfaces[ifname]:
|
||||
old_link_local_address = self.adopted_interfaces[ifname]
|
||||
RNS.log("Replacing link-local address "+str(old_link_local_address)+" for "+str(ifname)+" with "+str(link_local_addr), RNS.LOG_DEBUG)
|
||||
self.adopted_interfaces[ifname] = link_local_addr
|
||||
self.link_local_addresses.append(link_local_addr)
|
||||
|
||||
if old_link_local_address in self.link_local_addresses:
|
||||
self.link_local_addresses.remove(old_link_local_address)
|
||||
|
||||
local_addr = link_local_addr+"%"+ifname
|
||||
addr_info = socket.getaddrinfo(local_addr, self.data_port, socket.AF_INET6, socket.SOCK_DGRAM)
|
||||
listen_address = addr_info[0][4]
|
||||
|
||||
if ifname in self.interface_servers:
|
||||
RNS.log("Shutting down previous UDP listener for "+str(self)+" "+str(ifname), RNS.LOG_DEBUG)
|
||||
previous_server = self.interface_servers[ifname]
|
||||
def shutdown_server():
|
||||
previous_server.shutdown()
|
||||
threading.Thread(target=shutdown_server, daemon=True).start()
|
||||
|
||||
RNS.log("Starting new UDP listener for "+str(self)+" "+str(ifname), RNS.LOG_DEBUG)
|
||||
|
||||
udp_server = socketserver.UDPServer(listen_address, self.handler_factory(self.processIncoming))
|
||||
self.interface_servers[ifname] = udp_server
|
||||
|
||||
thread = threading.Thread(target=udp_server.serve_forever)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Could not get device information while updating link-local addresses for "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
@@ -289,10 +318,12 @@ class AutoInterface(Interface):
|
||||
|
||||
if now - last_multicast_echo > self.multicast_echo_timeout:
|
||||
if ifname in self.timed_out_interfaces and self.timed_out_interfaces[ifname] == False:
|
||||
self.carrier_changed = True
|
||||
RNS.log("Multicast echo timeout for "+str(ifname)+". Carrier lost.", RNS.LOG_WARNING)
|
||||
self.timed_out_interfaces[ifname] = True
|
||||
else:
|
||||
if ifname in self.timed_out_interfaces and self.timed_out_interfaces[ifname] == True:
|
||||
self.carrier_changed = True
|
||||
RNS.log(str(self)+" Carrier recovered on "+str(ifname), RNS.LOG_WARNING)
|
||||
self.timed_out_interfaces[ifname] = False
|
||||
|
||||
|
||||
+125
-27
@@ -161,8 +161,6 @@ class I2PController:
|
||||
raise tn.status["exception"]
|
||||
|
||||
else:
|
||||
self.client_tunnels[i2p_destination] = True
|
||||
owner.awaiting_i2p_tunnel = False
|
||||
if owner.socket != None:
|
||||
if hasattr(owner.socket, "close"):
|
||||
if callable(owner.socket.close):
|
||||
@@ -175,6 +173,8 @@ class I2PController:
|
||||
owner.socket.close()
|
||||
except Exception as e:
|
||||
RNS.log("Error while closing socket for "+str(owner)+": "+str(e))
|
||||
self.client_tunnels[i2p_destination] = True
|
||||
owner.awaiting_i2p_tunnel = False
|
||||
|
||||
RNS.log(str(owner)+" tunnel setup complete", RNS.LOG_VERBOSE)
|
||||
|
||||
@@ -383,6 +383,11 @@ class I2PInterfacePeer(Interface):
|
||||
I2P_PROBE_AFTER = 10
|
||||
I2P_PROBE_INTERVAL = 9
|
||||
I2P_PROBES = 5
|
||||
I2P_READ_TIMEOUT = (I2P_PROBE_INTERVAL * I2P_PROBES + I2P_PROBE_AFTER)*2
|
||||
|
||||
TUNNEL_STATE_INIT = 0x00
|
||||
TUNNEL_STATE_ACTIVE = 0x01
|
||||
TUNNEL_STATE_STALE = 0x02
|
||||
|
||||
def __init__(self, parent_interface, owner, name, target_i2p_dest=None, connected_socket=None, max_reconnect_tries=None):
|
||||
self.rxb = 0
|
||||
@@ -409,6 +414,30 @@ class I2PInterfacePeer(Interface):
|
||||
self.i2p_tunnel_ready = False
|
||||
self.mode = RNS.Interfaces.Interface.Interface.MODE_FULL
|
||||
self.bitrate = I2PInterface.BITRATE_GUESS
|
||||
self.last_read = 0
|
||||
self.last_write = 0
|
||||
self.wd_reset = False
|
||||
self.i2p_tunnel_state = I2PInterfacePeer.TUNNEL_STATE_INIT
|
||||
|
||||
self.ifac_size = self.parent_interface.ifac_size
|
||||
self.ifac_netname = self.parent_interface.ifac_netname
|
||||
self.ifac_netkey = self.parent_interface.ifac_netkey
|
||||
if self.ifac_netname != None or self.ifac_netkey != None:
|
||||
ifac_origin = b""
|
||||
if self.ifac_netname != None:
|
||||
ifac_origin += RNS.Identity.full_hash(self.ifac_netname.encode("utf-8"))
|
||||
if self.ifac_netkey != None:
|
||||
ifac_origin += RNS.Identity.full_hash(self.ifac_netkey.encode("utf-8"))
|
||||
|
||||
ifac_origin_hash = RNS.Identity.full_hash(ifac_origin)
|
||||
self.ifac_key = RNS.Cryptography.hkdf(
|
||||
length=64,
|
||||
derive_from=ifac_origin_hash,
|
||||
salt=RNS.Reticulum.IFAC_SALT,
|
||||
context=None
|
||||
)
|
||||
self.ifac_identity = RNS.Identity.from_bytes(self.ifac_key)
|
||||
self.ifac_signature = self.ifac_identity.sign(RNS.Identity.full_hash(self.ifac_key))
|
||||
|
||||
self.announce_rate_target = None
|
||||
self.announce_rate_grace = None
|
||||
@@ -454,7 +483,7 @@ class I2PInterfacePeer(Interface):
|
||||
RNS.log("Error while while configuring "+str(self)+": "+str(e), RNS.LOG_ERROR)
|
||||
RNS.log("Check that I2P is installed and running, and that SAM is enabled. Retrying tunnel setup later.", RNS.LOG_ERROR)
|
||||
|
||||
time.sleep(15)
|
||||
time.sleep(8)
|
||||
|
||||
thread = threading.Thread(target=tunnel_job)
|
||||
thread.daemon = True
|
||||
@@ -463,6 +492,7 @@ class I2PInterfacePeer(Interface):
|
||||
def wait_job():
|
||||
while self.awaiting_i2p_tunnel:
|
||||
time.sleep(0.25)
|
||||
time.sleep(2)
|
||||
|
||||
if not self.kiss_framing:
|
||||
self.wants_tunnel = True
|
||||
@@ -482,18 +512,11 @@ class I2PInterfacePeer(Interface):
|
||||
|
||||
|
||||
def set_timeouts_linux(self):
|
||||
if not self.i2p_tunneled:
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_USER_TIMEOUT, int(I2PInterfacePeer.TCP_USER_TIMEOUT * 1000))
|
||||
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, int(I2PInterfacePeer.TCP_PROBE_AFTER))
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, int(I2PInterfacePeer.TCP_PROBE_INTERVAL))
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, int(I2PInterfacePeer.TCP_PROBES))
|
||||
else:
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_USER_TIMEOUT, int(I2PInterfacePeer.I2P_USER_TIMEOUT * 1000))
|
||||
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, int(I2PInterfacePeer.I2P_PROBE_AFTER))
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, int(I2PInterfacePeer.I2P_PROBE_INTERVAL))
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, int(I2PInterfacePeer.I2P_PROBES))
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_USER_TIMEOUT, int(I2PInterfacePeer.I2P_USER_TIMEOUT * 1000))
|
||||
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, int(I2PInterfacePeer.I2P_PROBE_AFTER))
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, int(I2PInterfacePeer.I2P_PROBE_INTERVAL))
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, int(I2PInterfacePeer.I2P_PROBES))
|
||||
|
||||
def set_timeouts_osx(self):
|
||||
if hasattr(socket, "TCP_KEEPALIVE"):
|
||||
@@ -502,22 +525,19 @@ class I2PInterfacePeer(Interface):
|
||||
TCP_KEEPIDLE = 0x10
|
||||
|
||||
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
|
||||
|
||||
if not self.i2p_tunneled:
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, TCP_KEEPIDLE, int(I2PInterfacePeer.TCP_PROBE_AFTER))
|
||||
else:
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, TCP_KEEPIDLE, int(I2PInterfacePeer.I2P_PROBE_AFTER))
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, TCP_KEEPIDLE, int(I2PInterfacePeer.I2P_PROBE_AFTER))
|
||||
|
||||
def shutdown_socket(self, socket):
|
||||
if callable(socket.close):
|
||||
|
||||
def shutdown_socket(self, target_socket):
|
||||
if callable(target_socket.close):
|
||||
try:
|
||||
socket.shutdown(socket.SHUT_RDWR)
|
||||
if socket != None:
|
||||
target_socket.shutdown(socket.SHUT_RDWR)
|
||||
except Exception as e:
|
||||
RNS.log("Error while shutting down socket for "+str(self)+": "+str(e))
|
||||
|
||||
try:
|
||||
socket.close()
|
||||
if socket != None:
|
||||
target_socket.close()
|
||||
except Exception as e:
|
||||
RNS.log("Error while closing socket for "+str(self)+": "+str(e))
|
||||
|
||||
@@ -571,7 +591,6 @@ class I2PInterfacePeer(Interface):
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def reconnect(self):
|
||||
if self.initiator:
|
||||
if not self.reconnecting:
|
||||
@@ -632,6 +651,7 @@ class I2PInterfacePeer(Interface):
|
||||
self.socket.sendall(data)
|
||||
self.writing = False
|
||||
self.txb += len(data)
|
||||
self.last_write = time.time()
|
||||
|
||||
if hasattr(self, "parent_interface") and self.parent_interface != None and self.parent_count:
|
||||
self.parent_interface.txb += len(data)
|
||||
@@ -642,8 +662,59 @@ class I2PInterfacePeer(Interface):
|
||||
self.teardown()
|
||||
|
||||
|
||||
def read_watchdog(self):
|
||||
while self.wd_reset:
|
||||
time.sleep(0.25)
|
||||
|
||||
should_run = True
|
||||
try:
|
||||
while should_run and not self.wd_reset:
|
||||
time.sleep(1)
|
||||
|
||||
if (time.time()-self.last_read > I2PInterfacePeer.I2P_PROBE_AFTER*2):
|
||||
if self.i2p_tunnel_state != I2PInterfacePeer.TUNNEL_STATE_STALE:
|
||||
RNS.log("I2P tunnel became unresponsive", RNS.LOG_DEBUG)
|
||||
|
||||
self.i2p_tunnel_state = I2PInterfacePeer.TUNNEL_STATE_STALE
|
||||
else:
|
||||
self.i2p_tunnel_state = I2PInterfacePeer.TUNNEL_STATE_ACTIVE
|
||||
|
||||
if (time.time()-self.last_write > I2PInterfacePeer.I2P_PROBE_AFTER*1):
|
||||
try:
|
||||
if self.socket != None:
|
||||
self.socket.sendall(bytes([HDLC.FLAG, HDLC.FLAG]))
|
||||
except Exception as e:
|
||||
RNS.log("An error ocurred while sending I2P keepalive. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
self.shutdown_socket(self.socket)
|
||||
should_run = False
|
||||
|
||||
if (time.time()-self.last_read > I2PInterfacePeer.I2P_READ_TIMEOUT):
|
||||
RNS.log("I2P socket is unresponsive, restarting...", RNS.LOG_WARNING)
|
||||
if self.socket != None:
|
||||
try:
|
||||
self.socket.shutdown(socket.SHUT_RDWR)
|
||||
except Exception as e:
|
||||
RNS.log("Error while shutting down socket for "+str(self)+": "+str(e))
|
||||
|
||||
try:
|
||||
self.socket.close()
|
||||
except Exception as e:
|
||||
RNS.log("Error while closing socket for "+str(self)+": "+str(e))
|
||||
|
||||
should_run = False
|
||||
|
||||
self.wd_reset = False
|
||||
|
||||
finally:
|
||||
self.wd_reset = False
|
||||
|
||||
def read_loop(self):
|
||||
try:
|
||||
self.last_read = time.time()
|
||||
self.last_write = time.time()
|
||||
|
||||
wd_thread = threading.Thread(target=self.read_watchdog, daemon=True).start()
|
||||
|
||||
in_frame = False
|
||||
escape = False
|
||||
data_buffer = b""
|
||||
@@ -653,6 +724,7 @@ class I2PInterfacePeer(Interface):
|
||||
data_in = self.socket.recv(4096)
|
||||
if len(data_in) > 0:
|
||||
pointer = 0
|
||||
self.last_read = time.time()
|
||||
while pointer < len(data_in):
|
||||
byte = data_in[pointer]
|
||||
pointer += 1
|
||||
@@ -705,6 +777,11 @@ class I2PInterfacePeer(Interface):
|
||||
data_buffer = data_buffer+bytes([byte])
|
||||
else:
|
||||
self.online = False
|
||||
|
||||
self.wd_reset = True
|
||||
time.sleep(2)
|
||||
self.wd_reset = False
|
||||
|
||||
if self.initiator and not self.detached:
|
||||
RNS.log("Socket for "+str(self)+" was closed, attempting to reconnect...", RNS.LOG_WARNING)
|
||||
self.reconnect()
|
||||
@@ -754,7 +831,7 @@ class I2PInterfacePeer(Interface):
|
||||
class I2PInterface(Interface):
|
||||
BITRATE_GUESS = 256*1000
|
||||
|
||||
def __init__(self, owner, name, rns_storagepath, peers, connectable = False):
|
||||
def __init__(self, owner, name, rns_storagepath, peers, connectable = False, ifac_size = 16, ifac_netname = None, ifac_netkey = None):
|
||||
self.rxb = 0
|
||||
self.txb = 0
|
||||
|
||||
@@ -780,6 +857,9 @@ class I2PInterface(Interface):
|
||||
self.bind_port = self.i2p.get_free_port()
|
||||
self.address = (self.bind_ip, self.bind_port)
|
||||
self.bitrate = I2PInterface.BITRATE_GUESS
|
||||
self.ifac_size = ifac_size
|
||||
self.ifac_netname = ifac_netname
|
||||
self.ifac_netkey = ifac_netkey
|
||||
|
||||
self.online = False
|
||||
|
||||
@@ -850,9 +930,27 @@ class I2PInterface(Interface):
|
||||
spawned_interface.parent_interface = self
|
||||
spawned_interface.online = True
|
||||
spawned_interface.bitrate = self.bitrate
|
||||
|
||||
spawned_interface.ifac_size = self.ifac_size
|
||||
spawned_interface.ifac_netname = self.ifac_netname
|
||||
spawned_interface.ifac_netkey = self.ifac_netkey
|
||||
if spawned_interface.ifac_netname != None or spawned_interface.ifac_netkey != None:
|
||||
ifac_origin = b""
|
||||
if spawned_interface.ifac_netname != None:
|
||||
ifac_origin += RNS.Identity.full_hash(spawned_interface.ifac_netname.encode("utf-8"))
|
||||
if spawned_interface.ifac_netkey != None:
|
||||
ifac_origin += RNS.Identity.full_hash(spawned_interface.ifac_netkey.encode("utf-8"))
|
||||
|
||||
ifac_origin_hash = RNS.Identity.full_hash(ifac_origin)
|
||||
spawned_interface.ifac_key = RNS.Cryptography.hkdf(
|
||||
length=64,
|
||||
derive_from=ifac_origin_hash,
|
||||
salt=RNS.Reticulum.IFAC_SALT,
|
||||
context=None
|
||||
)
|
||||
spawned_interface.ifac_identity = RNS.Identity.from_bytes(spawned_interface.ifac_key)
|
||||
spawned_interface.ifac_signature = spawned_interface.ifac_identity.sign(RNS.Identity.full_hash(spawned_interface.ifac_key))
|
||||
|
||||
spawned_interface.announce_rate_target = self.announce_rate_target
|
||||
spawned_interface.announce_rate_grace = self.announce_rate_grace
|
||||
spawned_interface.announce_rate_penalty = self.announce_rate_penalty
|
||||
|
||||
@@ -44,6 +44,7 @@ class KISS():
|
||||
CMD_RADIO_STATE = 0x06
|
||||
CMD_RADIO_LOCK = 0x07
|
||||
CMD_DETECT = 0x08
|
||||
CMD_LEAVE = 0x0A
|
||||
CMD_READY = 0x0F
|
||||
CMD_STAT_RX = 0x21
|
||||
CMD_STAT_TX = 0x22
|
||||
@@ -51,6 +52,9 @@ class KISS():
|
||||
CMD_STAT_SNR = 0x24
|
||||
CMD_BLINK = 0x30
|
||||
CMD_RANDOM = 0x40
|
||||
CMD_FB_EXT = 0x41
|
||||
CMD_FB_READ = 0x42
|
||||
CMD_FB_WRITE = 0x43
|
||||
CMD_PLATFORM = 0x48
|
||||
CMD_MCU = 0x49
|
||||
CMD_FW_VERSION = 0x50
|
||||
@@ -90,7 +94,7 @@ class RNodeInterface(Interface):
|
||||
CALLSIGN_MAX_LEN = 32
|
||||
|
||||
REQUIRED_FW_VER_MAJ = 1
|
||||
REQUIRED_FW_VER_MIN = 26
|
||||
REQUIRED_FW_VER_MIN = 52
|
||||
|
||||
RECONNECT_WAIT = 5
|
||||
|
||||
@@ -130,6 +134,7 @@ class RNodeInterface(Interface):
|
||||
self.state = KISS.RADIO_STATE_OFF
|
||||
self.bitrate = 0
|
||||
self.platform = None
|
||||
self.display = None
|
||||
self.mcu = None
|
||||
self.detected = False
|
||||
self.firmware_ok = False
|
||||
@@ -240,8 +245,7 @@ class RNodeInterface(Interface):
|
||||
raise IOError("Could not detect device")
|
||||
else:
|
||||
if self.platform == KISS.PLATFORM_ESP32:
|
||||
RNS.log("Resetting ESP32-based device before configuration...", RNS.LOG_VERBOSE)
|
||||
self.hard_reset()
|
||||
self.display = True
|
||||
|
||||
RNS.log("Serial port "+self.port+" is now open")
|
||||
RNS.log("Configuring RNode interface...", RNS.LOG_VERBOSE)
|
||||
@@ -271,7 +275,51 @@ class RNodeInterface(Interface):
|
||||
kiss_command = bytes([KISS.FEND, KISS.CMD_DETECT, KISS.DETECT_REQ, KISS.FEND, KISS.CMD_FW_VERSION, 0x00, KISS.FEND, KISS.CMD_PLATFORM, 0x00, KISS.FEND, KISS.CMD_MCU, 0x00, KISS.FEND])
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while detecting hardware for "+self(str))
|
||||
raise IOError("An IO error occurred while detecting hardware for "+str(self))
|
||||
|
||||
def leave(self):
|
||||
kiss_command = bytes([KISS.FEND, KISS.CMD_LEAVE, 0xFF, KISS.FEND])
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while sending host left command to device")
|
||||
|
||||
def enable_external_framebuffer(self):
|
||||
if self.display != None:
|
||||
kiss_command = bytes([KISS.FEND, KISS.CMD_FB_EXT, 0x01, KISS.FEND])
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while enabling external framebuffer on device")
|
||||
|
||||
def disable_external_framebuffer(self):
|
||||
if self.display != None:
|
||||
kiss_command = bytes([KISS.FEND, KISS.CMD_FB_EXT, 0x00, KISS.FEND])
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while disabling external framebuffer on device")
|
||||
|
||||
FB_PIXEL_WIDTH = 64
|
||||
FB_BITS_PER_PIXEL = 1
|
||||
FB_PIXELS_PER_BYTE = 8//FB_BITS_PER_PIXEL
|
||||
FB_BYTES_PER_LINE = FB_PIXEL_WIDTH//FB_PIXELS_PER_BYTE
|
||||
def display_image(self, imagedata):
|
||||
if self.display != None:
|
||||
lines = len(imagedata)//8
|
||||
for line in range(lines):
|
||||
line_start = line*RNodeInterface.FB_BYTES_PER_LINE
|
||||
line_end = line_start+RNodeInterface.FB_BYTES_PER_LINE
|
||||
line_data = bytes(imagedata[line_start:line_end])
|
||||
self.write_framebuffer(line, line_data)
|
||||
|
||||
def write_framebuffer(self, line, line_data):
|
||||
if self.display != None:
|
||||
line_byte = line.to_bytes(1, byteorder="big", signed=False)
|
||||
data = line_byte+line_data
|
||||
escaped_data = KISS.escape(data)
|
||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_FB_WRITE])+escaped_data+bytes([KISS.FEND])
|
||||
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while writing framebuffer data device")
|
||||
|
||||
def hard_reset(self):
|
||||
kiss_command = bytes([KISS.FEND, KISS.CMD_RESET, 0xf8, KISS.FEND])
|
||||
@@ -290,7 +338,7 @@ class RNodeInterface(Interface):
|
||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_FREQUENCY])+data+bytes([KISS.FEND])
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while configuring frequency for "+self(str))
|
||||
raise IOError("An IO error occurred while configuring frequency for "+str(self))
|
||||
|
||||
def setBandwidth(self):
|
||||
c1 = self.bandwidth >> 24
|
||||
@@ -302,35 +350,35 @@ class RNodeInterface(Interface):
|
||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_BANDWIDTH])+data+bytes([KISS.FEND])
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while configuring bandwidth for "+self(str))
|
||||
raise IOError("An IO error occurred while configuring bandwidth for "+str(self))
|
||||
|
||||
def setTXPower(self):
|
||||
txp = bytes([self.txpower])
|
||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_TXPOWER])+txp+bytes([KISS.FEND])
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while configuring TX power for "+self(str))
|
||||
raise IOError("An IO error occurred while configuring TX power for "+str(self))
|
||||
|
||||
def setSpreadingFactor(self):
|
||||
sf = bytes([self.sf])
|
||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_SF])+sf+bytes([KISS.FEND])
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while configuring spreading factor for "+self(str))
|
||||
raise IOError("An IO error occurred while configuring spreading factor for "+str(self))
|
||||
|
||||
def setCodingRate(self):
|
||||
cr = bytes([self.cr])
|
||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_CR])+cr+bytes([KISS.FEND])
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while configuring coding rate for "+self(str))
|
||||
raise IOError("An IO error occurred while configuring coding rate for "+str(self))
|
||||
|
||||
def setRadioState(self, state):
|
||||
self.state = state
|
||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_RADIO_STATE])+bytes([state])+bytes([KISS.FEND])
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while configuring radio state for "+self(str))
|
||||
raise IOError("An IO error occurred while configuring radio state for "+str(self))
|
||||
|
||||
def validate_firmware(self):
|
||||
if (self.maj_version >= RNodeInterface.REQUIRED_FW_VER_MAJ):
|
||||
@@ -342,7 +390,7 @@ class RNodeInterface(Interface):
|
||||
|
||||
RNS.log("The firmware version of the connected RNode is "+str(self.maj_version)+"."+str(self.min_version), RNS.LOG_ERROR)
|
||||
RNS.log("This version of Reticulum requires at least version "+str(RNodeInterface.REQUIRED_FW_VER_MAJ)+"."+str(RNodeInterface.REQUIRED_FW_VER_MIN), RNS.LOG_ERROR)
|
||||
RNS.log("Please update your RNode firmware with rnodeconf (https://github.com/markqvist/rnodeconfigutil/)")
|
||||
RNS.log("Please update your RNode firmware with rnodeconf from https://github.com/markqvist/rnodeconfigutil/")
|
||||
RNS.panic()
|
||||
|
||||
|
||||
@@ -634,8 +682,13 @@ class RNodeInterface(Interface):
|
||||
except Exception as e:
|
||||
RNS.log("Error while reconnecting port, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
RNS.log("Reconnected serial port for "+str(self))
|
||||
if self.online:
|
||||
RNS.log("Reconnected serial port for "+str(self))
|
||||
|
||||
def detach(self):
|
||||
self.disable_external_framebuffer()
|
||||
self.setRadioState(KISS.RADIO_STATE_OFF)
|
||||
self.leave()
|
||||
|
||||
def __str__(self):
|
||||
return "RNodeInterface["+str(self.name)+"]"
|
||||
|
||||
|
||||
@@ -483,9 +483,27 @@ class TCPServerInterface(Interface):
|
||||
spawned_interface.target_port = str(handler.client_address[1])
|
||||
spawned_interface.parent_interface = self
|
||||
spawned_interface.bitrate = self.bitrate
|
||||
|
||||
spawned_interface.ifac_size = self.ifac_size
|
||||
spawned_interface.ifac_netname = self.ifac_netname
|
||||
spawned_interface.ifac_netkey = self.ifac_netkey
|
||||
if spawned_interface.ifac_netname != None or spawned_interface.ifac_netkey != None:
|
||||
ifac_origin = b""
|
||||
if spawned_interface.ifac_netname != None:
|
||||
ifac_origin += RNS.Identity.full_hash(spawned_interface.ifac_netname.encode("utf-8"))
|
||||
if spawned_interface.ifac_netkey != None:
|
||||
ifac_origin += RNS.Identity.full_hash(spawned_interface.ifac_netkey.encode("utf-8"))
|
||||
|
||||
ifac_origin_hash = RNS.Identity.full_hash(ifac_origin)
|
||||
spawned_interface.ifac_key = RNS.Cryptography.hkdf(
|
||||
length=64,
|
||||
derive_from=ifac_origin_hash,
|
||||
salt=RNS.Reticulum.IFAC_SALT,
|
||||
context=None
|
||||
)
|
||||
spawned_interface.ifac_identity = RNS.Identity.from_bytes(spawned_interface.ifac_key)
|
||||
spawned_interface.ifac_signature = spawned_interface.ifac_identity.sign(RNS.Identity.full_hash(spawned_interface.ifac_key))
|
||||
|
||||
spawned_interface.announce_rate_target = self.announce_rate_target
|
||||
spawned_interface.announce_rate_grace = self.announce_rate_grace
|
||||
spawned_interface.announce_rate_penalty = self.announce_rate_penalty
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
import os
|
||||
import glob
|
||||
import RNS.Interfaces.Android
|
||||
|
||||
modules = glob.glob(os.path.dirname(__file__)+"/*.py")
|
||||
__all__ = [ os.path.basename(f)[:-3] for f in modules if not f.endswith('__init__.py')]
|
||||
__all__ = [ os.path.basename(f)[:-3] for f in modules if not f.endswith('__init__.py')]
|
||||
|
||||
+9
-2
@@ -272,6 +272,8 @@ class Link:
|
||||
self.rtt = time.time() - self.request_time
|
||||
self.attached_interface = packet.receiving_interface
|
||||
self.__remote_identity = self.destination.identity
|
||||
self.status = Link.ACTIVE
|
||||
self.activated_at = time.time()
|
||||
RNS.Transport.activate_link(self)
|
||||
RNS.log("Link "+str(self)+" established with "+str(self.destination)+", RTT is "+str(round(self.rtt, 3))+"s", RNS.LOG_VERBOSE)
|
||||
rtt_data = umsgpack.packb(self.rtt)
|
||||
@@ -279,8 +281,6 @@ class Link:
|
||||
rtt_packet.send()
|
||||
self.had_outbound()
|
||||
|
||||
self.status = Link.ACTIVE
|
||||
self.activated_at = time.time()
|
||||
if self.callbacks.link_established != None:
|
||||
thread = threading.Thread(target=self.callbacks.link_established, args=(self,))
|
||||
thread.daemon = True
|
||||
@@ -900,6 +900,13 @@ class Link:
|
||||
def register_incoming_resource(self, resource):
|
||||
self.incoming_resources.append(resource)
|
||||
|
||||
def has_incoming_resource(self, resource):
|
||||
for incoming_resource in self.incoming_resources:
|
||||
if incoming_resource.hash == resource.hash:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def cancel_outgoing_resource(self, resource):
|
||||
if resource in self.outgoing_resources:
|
||||
self.outgoing_resources.remove(resource)
|
||||
|
||||
+19
-13
@@ -172,20 +172,26 @@ class Resource:
|
||||
|
||||
resource.consecutive_completed_height = 0
|
||||
|
||||
resource.link.register_incoming_resource(resource)
|
||||
if not resource.link.has_incoming_resource(resource):
|
||||
resource.link.register_incoming_resource(resource)
|
||||
|
||||
RNS.log("Accepting resource advertisement for "+RNS.prettyhexrep(resource.hash), RNS.LOG_DEBUG)
|
||||
if resource.link.callbacks.resource_started != None:
|
||||
try:
|
||||
resource.link.callbacks.resource_started(resource)
|
||||
except Exception as e:
|
||||
RNS.log("Error while executing resource started callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
RNS.log("Accepting resource advertisement for "+RNS.prettyhexrep(resource.hash), RNS.LOG_DEBUG)
|
||||
if resource.link.callbacks.resource_started != None:
|
||||
try:
|
||||
resource.link.callbacks.resource_started(resource)
|
||||
except Exception as e:
|
||||
RNS.log("Error while executing resource started callback from "+str(resource)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
resource.hashmap_update(0, resource.hashmap_raw)
|
||||
resource.hashmap_update(0, resource.hashmap_raw)
|
||||
|
||||
resource.watchdog_job()
|
||||
resource.watchdog_job()
|
||||
|
||||
return resource
|
||||
|
||||
else:
|
||||
RNS.log("Ignoring resource advertisement for "+RNS.prettyhexrep(resource.hash)+", resource already transferring", RNS.LOG_DEBUG)
|
||||
return None
|
||||
|
||||
return resource
|
||||
except Exception as e:
|
||||
RNS.log("Could not decode resource advertisement, dropping resource", RNS.LOG_DEBUG)
|
||||
return None
|
||||
@@ -397,8 +403,7 @@ class Resource:
|
||||
thread.start()
|
||||
|
||||
def __advertise_job(self):
|
||||
data = ResourceAdvertisement(self).pack()
|
||||
self.advertisement_packet = RNS.Packet(self.link, data, context=RNS.Packet.RESOURCE_ADV)
|
||||
self.advertisement_packet = RNS.Packet(self.link, ResourceAdvertisement(self).pack(), context=RNS.Packet.RESOURCE_ADV)
|
||||
while not self.link.ready_for_new_resource():
|
||||
self.status = Resource.QUEUED
|
||||
sleep(0.25)
|
||||
@@ -445,7 +450,8 @@ class Resource:
|
||||
try:
|
||||
RNS.log("No part requests received, retrying resource advertisement...", RNS.LOG_DEBUG)
|
||||
self.retries_left -= 1
|
||||
self.advertisement_packet.resend()
|
||||
self.advertisement_packet = RNS.Packet(self.link, ResourceAdvertisement(self).pack(), context=RNS.Packet.RESOURCE_ADV)
|
||||
self.advertisement_packet.send()
|
||||
self.last_activity = time.time()
|
||||
self.adv_sent = self.last_activity
|
||||
sleep_time = 0.001
|
||||
|
||||
+19
-5
@@ -34,7 +34,6 @@ if get_platform() == "android":
|
||||
from .Interfaces.Android import KISSInterface
|
||||
else:
|
||||
from .Interfaces import *
|
||||
from .Interfaces.Android import *
|
||||
|
||||
from .vendor.configobj import ConfigObj
|
||||
import configparser
|
||||
@@ -632,12 +631,18 @@ class Reticulum:
|
||||
i2p_peers = c.as_list("peers") if "peers" in c else None
|
||||
connectable = c.as_bool("connectable") if "connectable" in c else False
|
||||
|
||||
if ifac_size == None:
|
||||
ifac_size = 16
|
||||
|
||||
interface = I2PInterface.I2PInterface(
|
||||
RNS.Transport,
|
||||
name,
|
||||
Reticulum.storagepath,
|
||||
i2p_peers,
|
||||
connectable = connectable,
|
||||
ifac_size = ifac_size,
|
||||
ifac_netname = ifac_netname,
|
||||
ifac_netkey = ifac_netkey,
|
||||
)
|
||||
|
||||
if "outgoing" in c and c.as_bool("outgoing") == False:
|
||||
@@ -654,10 +659,6 @@ class Reticulum:
|
||||
interface.announce_cap = announce_cap
|
||||
if configured_bitrate:
|
||||
interface.bitrate = configured_bitrate
|
||||
if ifac_size != None:
|
||||
interface.ifac_size = ifac_size
|
||||
else:
|
||||
interface.ifac_size = 16
|
||||
|
||||
if c["type"] == "SerialInterface":
|
||||
port = c["port"] if "port" in c else None
|
||||
@@ -1072,6 +1073,19 @@ class Reticulum:
|
||||
else:
|
||||
ifstats["i2p_b32"] = None
|
||||
|
||||
if hasattr(interface, "i2p_tunnel_state"):
|
||||
if interface.i2p_tunnel_state != None:
|
||||
state_description = "Unknown State"
|
||||
if interface.i2p_tunnel_state == I2PInterface.I2PInterfacePeer.TUNNEL_STATE_ACTIVE:
|
||||
state_description = "Tunnel Active"
|
||||
elif interface.i2p_tunnel_state == I2PInterface.I2PInterfacePeer.TUNNEL_STATE_INIT:
|
||||
state_description = "Creating Tunnel"
|
||||
elif interface.i2p_tunnel_state == I2PInterface.I2PInterfacePeer.TUNNEL_STATE_STALE:
|
||||
state_description = "Tunnel Unresponsive"
|
||||
ifstats["tunnelstate"] = state_description
|
||||
else:
|
||||
ifstats["tunnelstate"] = None
|
||||
|
||||
if hasattr(interface, "bitrate"):
|
||||
if interface.bitrate != None:
|
||||
ifstats["bitrate"] = interface.bitrate
|
||||
|
||||
+156
-67
@@ -66,6 +66,7 @@ class Transport:
|
||||
PATH_REQUEST_TIMEOUT = 15 # Default timuout for client path requests in seconds
|
||||
PATH_REQUEST_GRACE = 0.35 # Grace time before a path announcement is made, allows directly reachable peers to respond first
|
||||
PATH_REQUEST_RW = 2 # Path request random window
|
||||
PATH_REQUEST_MI = 5 # Minimum interval in seconds for automated path requests
|
||||
|
||||
LINK_TIMEOUT = RNS.Link.STALE_TIME * 1.25
|
||||
REVERSE_TIMEOUT = 30*60 # Reverse table entries are removed after 30 minutes
|
||||
@@ -92,6 +93,7 @@ class Transport:
|
||||
announce_handlers = [] # A table storing externally registered announce handlers
|
||||
tunnels = {} # A table storing tunnels to other transport instances
|
||||
announce_rate_table = {} # A table for keeping track of announce rates
|
||||
path_requests = {} # A table for storing path request timestamps
|
||||
|
||||
discovery_path_requests = {} # A table for keeping track of path requests on behalf of other nodes
|
||||
discovery_pr_tags = [] # A table for keeping track of tagged path requests
|
||||
@@ -116,6 +118,8 @@ class Transport:
|
||||
jobs_locked = False
|
||||
jobs_running = False
|
||||
job_interval = 0.250
|
||||
links_last_checked = 0.0
|
||||
links_check_interval = 1.0
|
||||
receipts_last_checked = 0.0
|
||||
receipts_check_interval = 1.0
|
||||
announces_last_checked = 0.0
|
||||
@@ -144,13 +148,14 @@ class Transport:
|
||||
RNS.log("Loaded Transport Identity from storage", RNS.LOG_VERBOSE)
|
||||
|
||||
packet_hashlist_path = RNS.Reticulum.storagepath+"/packet_hashlist"
|
||||
if os.path.isfile(packet_hashlist_path):
|
||||
try:
|
||||
file = open(packet_hashlist_path, "rb")
|
||||
Transport.packet_hashlist = umsgpack.unpackb(file.read())
|
||||
file.close()
|
||||
except Exception as e:
|
||||
RNS.log("Could not load packet hashlist from storage, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
if not Transport.owner.is_connected_to_shared_instance:
|
||||
if os.path.isfile(packet_hashlist_path):
|
||||
try:
|
||||
file = open(packet_hashlist_path, "rb")
|
||||
Transport.packet_hashlist = umsgpack.unpackb(file.read())
|
||||
file.close()
|
||||
except Exception as e:
|
||||
RNS.log("Could not load packet hashlist from storage, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
# Create transport-specific destinations
|
||||
Transport.path_request_destination = RNS.Destination(None, RNS.Destination.IN, RNS.Destination.PLAIN, Transport.APP_NAME, "path", "request")
|
||||
@@ -164,8 +169,7 @@ class Transport:
|
||||
Transport.control_hashes.append(Transport.tunnel_synthesize_destination.hash)
|
||||
|
||||
Transport.jobs_running = False
|
||||
thread = threading.Thread(target=Transport.jobloop)
|
||||
thread.daemon = True
|
||||
thread = threading.Thread(target=Transport.jobloop, daemon=True)
|
||||
thread.start()
|
||||
|
||||
if RNS.Reticulum.transport_enabled():
|
||||
@@ -265,8 +269,6 @@ class Transport:
|
||||
except Exception as e:
|
||||
RNS.log("Could not load tunnel table from storage, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
|
||||
|
||||
RNS.log("Transport instance "+str(Transport.identity)+" started", RNS.LOG_VERBOSE)
|
||||
|
||||
# Synthesize tunnels for any interfaces wanting it
|
||||
@@ -284,10 +286,44 @@ class Transport:
|
||||
@staticmethod
|
||||
def jobs():
|
||||
outgoing = []
|
||||
path_requests = []
|
||||
Transport.jobs_running = True
|
||||
|
||||
try:
|
||||
if not Transport.jobs_locked:
|
||||
|
||||
# Process active and pending link lists
|
||||
if time.time() > Transport.links_last_checked+Transport.links_check_interval:
|
||||
|
||||
for link in Transport.pending_links:
|
||||
if link.status == RNS.Link.CLOSED:
|
||||
# If we are not a Transport Instance, finding a pending link
|
||||
# that was never activated will trigger an expiry of the path
|
||||
# to the destination, and an attempt to rediscover the path.
|
||||
if not RNS.Reticulum.transport_enabled():
|
||||
Transport.expire_path(link.destination.hash)
|
||||
|
||||
# If we are connected to a shared instance, it will take
|
||||
# care of sending out a new path request. If not, we will
|
||||
# send one directly.
|
||||
if not Transport.owner.is_connected_to_shared_instance:
|
||||
last_path_request = 0
|
||||
if link.destination.hash in Transport.path_requests:
|
||||
last_path_request = Transport.path_requests[link.destination.hash]
|
||||
|
||||
if time.time() - last_path_request > Transport.PATH_REQUEST_MI:
|
||||
RNS.log("Trying to rediscover path for "+RNS.prettyhexrep(link.destination.hash)+" since an attempted link was never established", RNS.LOG_DEBUG)
|
||||
if not link.destination.hash in path_requests:
|
||||
path_requests.append(link.destination.hash)
|
||||
|
||||
Transport.pending_links.remove(link)
|
||||
|
||||
for link in Transport.active_links:
|
||||
if link.status == RNS.Link.CLOSED:
|
||||
Transport.active_links.remove(link)
|
||||
|
||||
Transport.links_last_checked = time.time()
|
||||
|
||||
# Process receipts list for timed-out packets
|
||||
if time.time() > Transport.receipts_last_checked+Transport.receipts_check_interval:
|
||||
while len(Transport.receipts) > Transport.MAX_RECEIPTS:
|
||||
@@ -379,8 +415,32 @@ class Transport:
|
||||
stale_links = []
|
||||
for link_id in Transport.link_table:
|
||||
link_entry = Transport.link_table[link_id]
|
||||
if time.time() > link_entry[0] + Transport.LINK_TIMEOUT:
|
||||
stale_links.append(link_id)
|
||||
|
||||
if link_entry[7] == True:
|
||||
if time.time() > link_entry[0] + Transport.LINK_TIMEOUT:
|
||||
stale_links.append(link_id)
|
||||
else:
|
||||
if time.time() > link_entry[8]:
|
||||
stale_links.append(link_id)
|
||||
|
||||
last_path_request = 0
|
||||
if link_entry[6] in Transport.path_requests:
|
||||
last_path_request = Transport.path_requests[link_entry[6]]
|
||||
|
||||
# If this link request was originated from a local client
|
||||
# attempt to rediscover a path to the destination, if this
|
||||
# has not already happened recently.
|
||||
lr_taken_hops = link_entry[5]
|
||||
if lr_taken_hops == 0 and time.time() - last_path_request > Transport.PATH_REQUEST_MI:
|
||||
RNS.log("Trying to rediscover path for "+RNS.prettyhexrep(link_entry[6])+" since an attempted local client link was never established", RNS.LOG_DEBUG)
|
||||
if not link_entry[6] in path_requests:
|
||||
path_requests.append(link_entry[6])
|
||||
|
||||
if not RNS.Reticulum.transport_enabled():
|
||||
# Drop current path if we are not a transport instance, to
|
||||
# allow using higher-hop count paths or reused announces
|
||||
# from newly adjacent transport instances.
|
||||
Transport.expire_path(link_entry[6])
|
||||
|
||||
# Cull the path table
|
||||
stale_paths = []
|
||||
@@ -503,6 +563,10 @@ class Transport:
|
||||
|
||||
Transport.tables_last_culled = time.time()
|
||||
|
||||
else:
|
||||
# Transport jobs were locked, do nothing
|
||||
pass
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("An exception occurred while running Transport jobs.", RNS.LOG_ERROR)
|
||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
@@ -512,6 +576,9 @@ class Transport:
|
||||
for packet in outgoing:
|
||||
packet.send()
|
||||
|
||||
for destination_hash in path_requests:
|
||||
Transport.request_path(destination_hash)
|
||||
|
||||
@staticmethod
|
||||
def transmit(interface, raw):
|
||||
try:
|
||||
@@ -539,9 +606,6 @@ class Transport:
|
||||
|
||||
Transport.jobs_locked = True
|
||||
|
||||
# TODO: This updateHash call might be redundant
|
||||
# packet.update_hash()
|
||||
|
||||
sent = False
|
||||
outbound_time = time.time()
|
||||
|
||||
@@ -833,7 +897,7 @@ class Transport:
|
||||
def inbound(raw, interface=None):
|
||||
# If interface access codes are enabled,
|
||||
# we must authenticate each packet.
|
||||
if len(raw) > 1:
|
||||
if len(raw) > 2:
|
||||
if interface != None and hasattr(interface, "ifac_identity") and interface.ifac_identity != None:
|
||||
# Check that IFAC flag is set
|
||||
if raw[0] & 0x80 == 0x80:
|
||||
@@ -871,6 +935,9 @@ class Transport:
|
||||
# If the flag is set, drop the packet
|
||||
return
|
||||
|
||||
else:
|
||||
return
|
||||
|
||||
while (Transport.jobs_running):
|
||||
sleep(0.0005)
|
||||
|
||||
@@ -994,15 +1061,19 @@ class Transport:
|
||||
outbound_interface = Transport.destination_table[packet.destination_hash][5]
|
||||
|
||||
if packet.packet_type == RNS.Packet.LINKREQUEST:
|
||||
now = time.time()
|
||||
proof_timeout = now + RNS.Link.ESTABLISHMENT_TIMEOUT_PER_HOP * max(1, remaining_hops)
|
||||
|
||||
# Entry format is
|
||||
link_entry = [ time.time(), # 0: Timestamp,
|
||||
link_entry = [ now, # 0: Timestamp,
|
||||
next_hop, # 1: Next-hop transport ID
|
||||
outbound_interface, # 2: Next-hop interface
|
||||
remaining_hops, # 3: Remaining hops
|
||||
packet.receiving_interface, # 4: Received on interface
|
||||
packet.hops, # 5: Taken hops
|
||||
packet.destination_hash, # 6: Original destination hash
|
||||
False] # 7: Validated
|
||||
False, # 7: Validated
|
||||
proof_timeout] # 8: Proof timeout timestamp
|
||||
|
||||
Transport.link_table[packet.getTruncatedHash()] = link_entry
|
||||
|
||||
@@ -1202,9 +1273,9 @@ class Transport:
|
||||
|
||||
retransmit_timeout = now + (RNS.rand() * Transport.PATHFINDER_RW)
|
||||
|
||||
if packet.receiving_interface.mode == RNS.Interfaces.Interface.Interface.MODE_ACCESS_POINT:
|
||||
if hasattr(packet.receiving_interface, "mode") and packet.receiving_interface.mode == RNS.Interfaces.Interface.Interface.MODE_ACCESS_POINT:
|
||||
expires = now + Transport.AP_PATH_TIME
|
||||
elif packet.receiving_interface.mode == RNS.Interfaces.Interface.Interface.MODE_ROAMING:
|
||||
elif hasattr(packet.receiving_interface, "mode") and packet.receiving_interface.mode == RNS.Interfaces.Interface.Interface.MODE_ROAMING:
|
||||
expires = now + Transport.ROAMING_PATH_TIME
|
||||
else:
|
||||
expires = now + Transport.PATHFINDER_E
|
||||
@@ -1377,10 +1448,11 @@ class Transport:
|
||||
|
||||
# Handling for linkrequests to local destinations
|
||||
elif packet.packet_type == RNS.Packet.LINKREQUEST:
|
||||
for destination in Transport.destinations:
|
||||
if destination.hash == packet.destination_hash and destination.type == packet.destination_type:
|
||||
packet.destination = destination
|
||||
destination.receive(packet)
|
||||
if packet.transport_id == None or packet.transport_id == Transport.identity.hash:
|
||||
for destination in Transport.destinations:
|
||||
if destination.hash == packet.destination_hash and destination.type == packet.destination_type:
|
||||
packet.destination = destination
|
||||
destination.receive(packet)
|
||||
|
||||
# Handling for local data packets
|
||||
elif packet.packet_type == RNS.Packet.DATA:
|
||||
@@ -1414,14 +1486,29 @@ class Transport:
|
||||
if (RNS.Reticulum.transport_enabled() or for_local_client_link or from_local_client) and packet.destination_hash in Transport.link_table:
|
||||
link_entry = Transport.link_table[packet.destination_hash]
|
||||
if packet.receiving_interface == link_entry[2]:
|
||||
# TODO: Should we validate the LR proof at each transport
|
||||
# step before transporting it?
|
||||
# RNS.log("Link request proof received on correct interface, transporting it via "+str(link_entry[4]), RNS.LOG_EXTREME)
|
||||
new_raw = packet.raw[0:1]
|
||||
new_raw += struct.pack("!B", packet.hops)
|
||||
new_raw += packet.raw[2:]
|
||||
Transport.link_table[packet.destination_hash][7] = True
|
||||
Transport.transmit(link_entry[4], new_raw)
|
||||
try:
|
||||
if len(packet.data) == RNS.Identity.SIGLENGTH//8+RNS.Link.ECPUBSIZE//2:
|
||||
peer_pub_bytes = packet.data[RNS.Identity.SIGLENGTH//8:RNS.Identity.SIGLENGTH//8+RNS.Link.ECPUBSIZE//2]
|
||||
peer_identity = RNS.Identity.recall(link_entry[6])
|
||||
peer_sig_pub_bytes = peer_identity.get_public_key()[RNS.Link.ECPUBSIZE//2:RNS.Link.ECPUBSIZE]
|
||||
|
||||
signed_data = packet.destination_hash+peer_pub_bytes+peer_sig_pub_bytes
|
||||
signature = packet.data[:RNS.Identity.SIGLENGTH//8]
|
||||
|
||||
if peer_identity.validate(signature, signed_data):
|
||||
RNS.log("Link request proof validated for transport via "+str(link_entry[4]), RNS.LOG_EXTREME)
|
||||
new_raw = packet.raw[0:1]
|
||||
new_raw += struct.pack("!B", packet.hops)
|
||||
new_raw += packet.raw[2:]
|
||||
Transport.link_table[packet.destination_hash][7] = True
|
||||
Transport.transmit(link_entry[4], new_raw)
|
||||
|
||||
else:
|
||||
RNS.log("Invalid link request proof in transport for link "+RNS.prettyhexrep(packet.destination_hash)+", dropping proof.", RNS.LOG_DEBUG)
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Error while transporting link request proof. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
else:
|
||||
RNS.log("Link request proof received on wrong interface, not transporting it.", RNS.LOG_DEBUG)
|
||||
else:
|
||||
@@ -1596,7 +1683,7 @@ class Transport:
|
||||
|
||||
@staticmethod
|
||||
def register_link(link):
|
||||
RNS.log("Registering link "+str(link), RNS.LOG_DEBUG)
|
||||
RNS.log("Registering link "+str(link), RNS.LOG_EXTREME)
|
||||
if link.initiator:
|
||||
Transport.pending_links.append(link)
|
||||
else:
|
||||
@@ -1604,7 +1691,7 @@ class Transport:
|
||||
|
||||
@staticmethod
|
||||
def activate_link(link):
|
||||
RNS.log("Activating link "+str(link), RNS.LOG_DEBUG)
|
||||
RNS.log("Activating link "+str(link), RNS.LOG_EXTREME)
|
||||
if link in Transport.pending_links:
|
||||
Transport.pending_links.remove(link)
|
||||
Transport.active_links.append(link)
|
||||
@@ -1825,6 +1912,7 @@ class Transport:
|
||||
on_interface.announce_allowed_at = now + wait_time
|
||||
|
||||
packet.send()
|
||||
Transport.path_requests[destination_hash] = time.time()
|
||||
|
||||
@staticmethod
|
||||
def path_request_handler(data, packet):
|
||||
@@ -1901,10 +1989,9 @@ class Transport:
|
||||
|
||||
local_destination = next((d for d in Transport.destinations if d.hash == destination_hash), None)
|
||||
if local_destination != None:
|
||||
local_destination.announce(path_response=True)
|
||||
local_destination.announce(path_response=True, tag=tag, attached_interface=attached_interface)
|
||||
RNS.log("Answering path request for "+RNS.prettyhexrep(destination_hash)+interface_str+", destination is local to this system", RNS.LOG_DEBUG)
|
||||
|
||||
|
||||
elif (RNS.Reticulum.transport_enabled() or is_from_local_client) and (destination_hash in Transport.destination_table):
|
||||
packet = Transport.destination_table[destination_hash][6]
|
||||
next_hop = Transport.destination_table[destination_hash][1]
|
||||
@@ -1949,9 +2036,10 @@ class Transport:
|
||||
# Forward path request on all interfaces
|
||||
# except the local client
|
||||
RNS.log("Forwarding path request from local client for "+RNS.prettyhexrep(destination_hash)+interface_str+" to all other interfaces", RNS.LOG_DEBUG)
|
||||
request_tag = RNS.Identity.get_random_hash()
|
||||
for interface in Transport.interfaces:
|
||||
if not interface == attached_interface:
|
||||
Transport.request_path(destination_hash, interface)
|
||||
Transport.request_path(destination_hash, interface, tag = request_tag)
|
||||
|
||||
elif should_search_for_unknown:
|
||||
if destination_hash in Transport.discovery_path_requests:
|
||||
@@ -2078,41 +2166,42 @@ class Transport:
|
||||
|
||||
@staticmethod
|
||||
def save_packet_hashlist():
|
||||
if hasattr(Transport, "saving_packet_hashlist"):
|
||||
wait_interval = 0.2
|
||||
wait_timeout = 5
|
||||
wait_start = time.time()
|
||||
while Transport.saving_packet_hashlist:
|
||||
time.sleep(wait_interval)
|
||||
if time.time() > wait_start+wait_timeout:
|
||||
RNS.log("Could not save packet hashlist to storage, waiting for previous save operation timed out.", RNS.LOG_ERROR)
|
||||
return False
|
||||
if not Transport.owner.is_connected_to_shared_instance:
|
||||
if hasattr(Transport, "saving_packet_hashlist"):
|
||||
wait_interval = 0.2
|
||||
wait_timeout = 5
|
||||
wait_start = time.time()
|
||||
while Transport.saving_packet_hashlist:
|
||||
time.sleep(wait_interval)
|
||||
if time.time() > wait_start+wait_timeout:
|
||||
RNS.log("Could not save packet hashlist to storage, waiting for previous save operation timed out.", RNS.LOG_ERROR)
|
||||
return False
|
||||
|
||||
try:
|
||||
Transport.saving_packet_hashlist = True
|
||||
save_start = time.time()
|
||||
try:
|
||||
Transport.saving_packet_hashlist = True
|
||||
save_start = time.time()
|
||||
|
||||
if not RNS.Reticulum.transport_enabled():
|
||||
Transport.packet_hashlist = []
|
||||
else:
|
||||
RNS.log("Saving packet hashlist to storage...", RNS.LOG_DEBUG)
|
||||
if not RNS.Reticulum.transport_enabled():
|
||||
Transport.packet_hashlist = []
|
||||
else:
|
||||
RNS.log("Saving packet hashlist to storage...", RNS.LOG_DEBUG)
|
||||
|
||||
packet_hashlist_path = RNS.Reticulum.storagepath+"/packet_hashlist"
|
||||
file = open(packet_hashlist_path, "wb")
|
||||
file.write(umsgpack.packb(Transport.packet_hashlist))
|
||||
file.close()
|
||||
packet_hashlist_path = RNS.Reticulum.storagepath+"/packet_hashlist"
|
||||
file = open(packet_hashlist_path, "wb")
|
||||
file.write(umsgpack.packb(Transport.packet_hashlist))
|
||||
file.close()
|
||||
|
||||
save_time = time.time() - save_start
|
||||
if save_time < 1:
|
||||
time_str = str(round(save_time*1000,2))+"ms"
|
||||
else:
|
||||
time_str = str(round(save_time,2))+"s"
|
||||
RNS.log("Saved packet hashlist in "+time_str, RNS.LOG_DEBUG)
|
||||
save_time = time.time() - save_start
|
||||
if save_time < 1:
|
||||
time_str = str(round(save_time*1000,2))+"ms"
|
||||
else:
|
||||
time_str = str(round(save_time,2))+"s"
|
||||
RNS.log("Saved packet hashlist in "+time_str, RNS.LOG_DEBUG)
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Could not save packet hashlist to storage, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
except Exception as e:
|
||||
RNS.log("Could not save packet hashlist to storage, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
Transport.saving_packet_hashlist = False
|
||||
Transport.saving_packet_hashlist = False
|
||||
|
||||
|
||||
@staticmethod
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -135,6 +135,9 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None):
|
||||
if "peers" in ifstat and ifstat["peers"] != None:
|
||||
print(" Peers : {np} reachable".format(np=ifstat["peers"]))
|
||||
|
||||
if "tunnelstate" in ifstat and ifstat["tunnelstate"] != None:
|
||||
print(" I2P : {ts}".format(ts=ifstat["tunnelstate"]))
|
||||
|
||||
if "ifac_signature" in ifstat and ifstat["ifac_signature"] != None:
|
||||
sigstr = "<…"+RNS.hexrep(ifstat["ifac_signature"][-5:], delimit=False)+">"
|
||||
print(" Access : {nb}-bit IFAC by {sig}".format(nb=ifstat["ifac_size"]*8, sig=sigstr))
|
||||
|
||||
+15
-7
@@ -57,10 +57,11 @@ LOG_FILE = 0x92
|
||||
|
||||
LOG_MAXSIZE = 5*1024*1024
|
||||
|
||||
loglevel = LOG_NOTICE
|
||||
logfile = None
|
||||
logdest = LOG_STDOUT
|
||||
logtimefmt = "%Y-%m-%d %H:%M:%S"
|
||||
loglevel = LOG_NOTICE
|
||||
logfile = None
|
||||
logdest = LOG_STDOUT
|
||||
logtimefmt = "%Y-%m-%d %H:%M:%S"
|
||||
compact_log_fmt = False
|
||||
|
||||
instance_random = random.Random()
|
||||
instance_random.seed(os.urandom(10))
|
||||
@@ -101,10 +102,14 @@ def timestamp_str(time_s):
|
||||
return time.strftime(logtimefmt, timestamp)
|
||||
|
||||
def log(msg, level=3, _override_destination = False):
|
||||
global _always_override_destination
|
||||
global _always_override_destination, compact_log_fmt
|
||||
|
||||
if loglevel >= level:
|
||||
logstring = "["+timestamp_str(time.time())+"] ["+loglevelname(level)+"] "+msg
|
||||
if not compact_log_fmt:
|
||||
logstring = "["+timestamp_str(time.time())+"] ["+loglevelname(level)+"] "+msg
|
||||
else:
|
||||
logstring = "["+timestamp_str(time.time())+"] "+msg
|
||||
|
||||
logging_lock.acquire()
|
||||
|
||||
if (logdest == LOG_STDOUT or _always_override_destination or _override_destination):
|
||||
@@ -212,7 +217,10 @@ def prettytime(time, verbose=False):
|
||||
|
||||
tstr += c
|
||||
|
||||
return tstr
|
||||
if tstr == "":
|
||||
return "0s"
|
||||
else:
|
||||
return tstr
|
||||
|
||||
def phyparams():
|
||||
print("Required Physical Layer MTU : "+str(Reticulum.MTU)+" bytes")
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
__version__ = "0.3.16"
|
||||
__version__ = "0.4.6"
|
||||
|
||||
Vendored
+1
-1
@@ -48,4 +48,4 @@ def cryptography_old_api():
|
||||
if cryptography.__version__ == "2.8":
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
return False
|
||||
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
# Reticulum Development Roadmap
|
||||
The development path for Reticulum is currently laid out in five distinct areas: *Comprehensibility*, *Universality*, *Functionality*, *Usability & Utility* and *Interfaceability*. Conceptualising the development of Reticulum into these areas serves to advance the implementation and work towards the Foundational Goals & Values of Reticulum.
|
||||
|
||||
## Comprehensibility
|
||||
These efforts are aimed at improving the ease of which Reticulum is understood, and lowering the barrier to entry for people who wish to start building systems on Reticulum.
|
||||
|
||||
- Improving [the manual](https://markqvist.github.io/Reticulum/manual/) with tutorials specifically for beginners
|
||||
- Updating the documentation to reflect recent changes and improvements
|
||||
- Update descriptions of protocol mechanics
|
||||
- Update announce description
|
||||
- Add in-depth explanation of the IFAC system
|
||||
- Software
|
||||
- Update Sideband screenshots
|
||||
- Update Sideband description
|
||||
- Update NomadNet screenshots
|
||||
- Update Sideband screenshots
|
||||
- Installation
|
||||
- Install docs for fedora, needs `python3-netifaces`
|
||||
- Add a *Reticulum On Raspberry Pi* section
|
||||
- Update *Reticulum On Android* section if necessary
|
||||
- Update Android install documentation.
|
||||
- Communications hardware section
|
||||
- Add information about RNode external displays.
|
||||
- Packet radio modems.
|
||||
- Possibly add other relevant types here as well.
|
||||
- Setup *Best Practices For...* / *Installation Examples* section.
|
||||
- Home or office (example)
|
||||
- Vehicles (example)
|
||||
- No-grid/solar/remote sites (example)
|
||||
|
||||
## Universality
|
||||
These efforts seek to broaden the universality of the Reticulum software and hardware ecosystem by continously diversifying platform support, and by improving the overall availability and ease of deployment of the Reticulum stack.
|
||||
|
||||
- Improved roaming support on Android
|
||||
- OpenWRT support
|
||||
- Create a standalone RNS Daemon app for Android
|
||||
- A lightweight and portable C implementation for microcontrollers, µRNS
|
||||
- A portable, high-performance Reticulum implementation in C/C++, see [#21](https://github.com/markqvist/Reticulum/discussions/21)
|
||||
- Performance and memory optimisations of the Python implementation
|
||||
- Bindings for other programming languages
|
||||
|
||||
## Functionality
|
||||
These efforts aim to expand and improve the core functionality and reliability of Reticulum.
|
||||
|
||||
- Improve storage persist call on local client connect/disconnect
|
||||
- Faster path invalidation on physical topography changes
|
||||
- Better path invalidation on roaming interfaces
|
||||
- Network-wide path balancing
|
||||
- Distributed Destination Naming System
|
||||
- Globally routable multicast
|
||||
- Destination proxying: Create a new random destination, and sign it with the original destination to create verifiable ephemeral destinations. This could actually be a very powerful feature for aggregating routes in the network, and it retains destination owners control over how they are routed
|
||||
- [Metric-based path selection and multiple paths](https://github.com/markqvist/Reticulum/discussions/86)
|
||||
|
||||
## Usability & Utility
|
||||
These effors seek to make Reticulum easier to use and operate, and to expand the utility of the stack on deployed systems.
|
||||
|
||||
- Add bluetooth pairing code output to rnodeconf
|
||||
- Easy way to share interface configurations, see [#19](https://github.com/markqvist/Reticulum/discussions/19)
|
||||
- Transit traffic display in rnstatus
|
||||
- JSON output mode for rnstatus
|
||||
- rnid utility
|
||||
- rnsign utility
|
||||
- rncrypt utility
|
||||
- rnsconfig utility
|
||||
- Expand rnx utility to true interactive remote shell
|
||||
|
||||
## Interfaceability
|
||||
These efforts aim to expand the types of physical and virtual interfaces that Reticulum can natively use to transport data.
|
||||
|
||||
- Filesystem interface
|
||||
- Plain ESP32 devices (ESP-Now, WiFi, Bluetooth, etc.)
|
||||
- More LoRa transceivers
|
||||
- AT-compatible modems
|
||||
- Direct SDR Support
|
||||
- Optical mediums
|
||||
- IR Transceivers
|
||||
- AWDL / OWL
|
||||
- HF Modems
|
||||
- GNU Radio
|
||||
- CAN-bus
|
||||
- Raw SPI
|
||||
- Raw i²c
|
||||
- MQTT
|
||||
- XBee
|
||||
- Tor
|
||||
|
||||
## Active Work Areas
|
||||
For each release cycle of Reticulum, improvements and additions from the five areas are selected as active work areas, and can be expected to be included in the upcoming releases within that cycle. While not entirely set in stone for each release cycle, they serve as a pointer of what to expect in the near future.
|
||||
|
||||
- The current `0.4.x` release cycle aims at completing:
|
||||
- [x] Improve storage persist call on local client connect/disconnect
|
||||
- [x] Improved roaming support on Android
|
||||
- [ ] Updating the documentation to reflect recent changes and improvements
|
||||
- [ ] Add bluetooth pairing code output to rnodeconf
|
||||
- [ ] Improve storage persist call on every local client connect/disconnect
|
||||
- [ ] Transit traffic display in rnstatus
|
||||
- [ ] JSON output mode for rnstatus
|
||||
- [ ] Add `rnid` utility
|
||||
- [ ] Add `rnsign` utility
|
||||
- [ ] Add `rncrypt` utility
|
||||
- [ ] Create a standalone RNS Daemon app for Android
|
||||
- Targets for related applications
|
||||
- [x] Add paper offline & paper message transport to LXMF
|
||||
- [x] Implement paper messaging in Nomad Network
|
||||
- [x] Implement paper messaging in Sideband
|
||||
- [x] Expand device support in Sideband to support older Android devices
|
||||
Binary file not shown.
@@ -1,4 +1,4 @@
|
||||
# Sphinx build info version 1
|
||||
# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
|
||||
config: 7b34654fe45624649b357054c4180ab2
|
||||
config: bb57e47d574f578f08360b71e3487c38
|
||||
tags: 645f666f9bcd5a90fca523b33c5a78b7
|
||||
|
||||
@@ -160,12 +160,13 @@ Installation
|
||||
|
||||
Once you have obtained compatible boards, you can install the `RNode Firmware <https://github.com/markqvist/RNode_Firmware>`_
|
||||
using the `RNode Configuration Utility <https://github.com/markqvist/rnodeconfigutil>`_.
|
||||
Make sure that ``Python3`` and ``pip`` is installed on your system, and then install
|
||||
the config utility with ``pip``:
|
||||
If you have installed Reticulum on your system, the ``rnodeconf`` program will already be
|
||||
available. If not, make sure that ``Python3`` and ``pip`` is installed on your system, and
|
||||
then install Reticulum with with ``pip``:
|
||||
|
||||
.. code::
|
||||
|
||||
pip3 install rnodeconf
|
||||
pip3 install rns
|
||||
|
||||
Once installation has completed, it is time to start installing the firmware on your
|
||||
devices. Run ``rnodeconf`` in auto-install mode like so:
|
||||
@@ -176,12 +177,7 @@ devices. Run ``rnodeconf`` in auto-install mode like so:
|
||||
|
||||
The utility will guide you through the installation process by asking a series of
|
||||
questions about your hardware. Simply follow the guide, and the utility will
|
||||
auto-install and configure your devices
|
||||
|
||||
**Important Note!** It is currently recommended to use the v1.x line of the RNode firmware,
|
||||
even though the v2.x line is available for early testing. The v2.x line should still be
|
||||
considered an experimental pre-release. Only use the v2.x firmware line if you want to test
|
||||
out the absolutely newest version, and don't care about stability.
|
||||
auto-install and configure your devices.
|
||||
|
||||
.. _rnode-usage:
|
||||
|
||||
|
||||
@@ -771,7 +771,7 @@ Wire Format
|
||||
| | | | | | | |
|
||||
00000000 00000111 [HASH1, 16 bytes] [CONTEXT, 1 byte] [DATA]
|
||||
|| | | | |
|
||||
|| | | | +-- Hops = 0
|
||||
|| | | | +-- Hops = 7
|
||||
|| | | +------- Packet Type = DATA
|
||||
|| | +--------- Destination Type = SINGLE
|
||||
|| +----------- Propagation Type = BROADCAST
|
||||
@@ -786,7 +786,7 @@ Wire Format
|
||||
| | | | | | | | | |
|
||||
10000000 00000111 [IFAC, N bytes] [HASH1, 16 bytes] [CONTEXT, 1 byte] [DATA]
|
||||
|| | | | |
|
||||
|| | | | +-- Hops = 0
|
||||
|| | | | +-- Hops = 7
|
||||
|| | | +------- Packet Type = DATA
|
||||
|| | +--------- Destination Type = SINGLE
|
||||
|| +----------- Propagation Type = BROADCAST
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
var DOCUMENTATION_OPTIONS = {
|
||||
URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
|
||||
VERSION: '0.3.16 beta',
|
||||
VERSION: '0.4.6 beta',
|
||||
LANGUAGE: 'en',
|
||||
COLLAPSE_INDEX: false,
|
||||
BUILDER: 'html',
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Support Reticulum" href="support.html" /><link rel="prev" title="Building Networks" href="networks.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
|
||||
<title>Code Examples - Reticulum Network Stack 0.3.16 beta documentation</title>
|
||||
<title>Code Examples - Reticulum Network Stack 0.4.6 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
@@ -141,7 +141,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.3.16 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.4.6 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -167,7 +167,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.3.16 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.4.6 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="#" /><link rel="search" title="Search" href="search.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/><title>Index - Reticulum Network Stack 0.3.16 beta documentation</title>
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/><title>Index - Reticulum Network Stack 0.4.6 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
@@ -139,7 +139,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.3.16 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.4.6 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -165,7 +165,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.3.16 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.4.6 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Using Reticulum on Your System" href="using.html" /><link rel="prev" title="What is Reticulum?" href="whatis.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
|
||||
<title>Getting Started Fast - Reticulum Network Stack 0.3.16 beta documentation</title>
|
||||
<title>Getting Started Fast - Reticulum Network Stack 0.4.6 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
@@ -141,7 +141,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.3.16 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.4.6 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -167,7 +167,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.3.16 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.4.6 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Supported Interfaces" href="interfaces.html" /><link rel="prev" title="Understanding Reticulum" href="understanding.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
|
||||
<title>Communications Hardware - Reticulum Network Stack 0.3.16 beta documentation</title>
|
||||
<title>Communications Hardware - Reticulum Network Stack 0.4.6 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
@@ -141,7 +141,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.3.16 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.4.6 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -167,7 +167,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.3.16 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.4.6 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
@@ -349,9 +349,10 @@ boards. The following boards are supported by the auto-installer.</p>
|
||||
<span id="rnode-installation"></span><h3>Installation<a class="headerlink" href="#installation" title="Permalink to this heading">#</a></h3>
|
||||
<p>Once you have obtained compatible boards, you can install the <a class="reference external" href="https://github.com/markqvist/RNode_Firmware">RNode Firmware</a>
|
||||
using the <a class="reference external" href="https://github.com/markqvist/rnodeconfigutil">RNode Configuration Utility</a>.
|
||||
Make sure that <code class="docutils literal notranslate"><span class="pre">Python3</span></code> and <code class="docutils literal notranslate"><span class="pre">pip</span></code> is installed on your system, and then install
|
||||
the config utility with <code class="docutils literal notranslate"><span class="pre">pip</span></code>:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">pip3</span> <span class="n">install</span> <span class="n">rnodeconf</span>
|
||||
If you have installed Reticulum on your system, the <code class="docutils literal notranslate"><span class="pre">rnodeconf</span></code> program will already be
|
||||
available. If not, make sure that <code class="docutils literal notranslate"><span class="pre">Python3</span></code> and <code class="docutils literal notranslate"><span class="pre">pip</span></code> is installed on your system, and
|
||||
then install Reticulum with with <code class="docutils literal notranslate"><span class="pre">pip</span></code>:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">pip3</span> <span class="n">install</span> <span class="n">rns</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Once installation has completed, it is time to start installing the firmware on your
|
||||
@@ -361,11 +362,7 @@ devices. Run <code class="docutils literal notranslate"><span class="pre">rnodec
|
||||
</div>
|
||||
<p>The utility will guide you through the installation process by asking a series of
|
||||
questions about your hardware. Simply follow the guide, and the utility will
|
||||
auto-install and configure your devices</p>
|
||||
<p><strong>Important Note!</strong> It is currently recommended to use the v1.x line of the RNode firmware,
|
||||
even though the v2.x line is available for early testing. The v2.x line should still be
|
||||
considered an experimental pre-release. Only use the v2.x firmware line if you want to test
|
||||
out the absolutely newest version, and don’t care about stability.</p>
|
||||
auto-install and configure your devices.</p>
|
||||
</section>
|
||||
<section id="usage-with-reticulum">
|
||||
<span id="rnode-usage"></span><h3>Usage with Reticulum<a class="headerlink" href="#usage-with-reticulum" title="Permalink to this heading">#</a></h3>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="What is Reticulum?" href="whatis.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
|
||||
<title>Reticulum Network Stack 0.3.16 beta documentation</title>
|
||||
<title>Reticulum Network Stack 0.4.6 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
@@ -141,7 +141,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="#"><div class="brand">Reticulum Network Stack 0.3.16 beta documentation</div></a>
|
||||
<a href="#"><div class="brand">Reticulum Network Stack 0.4.6 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -167,7 +167,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.3.16 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.4.6 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Building Networks" href="networks.html" /><link rel="prev" title="Communications Hardware" href="hardware.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
|
||||
<title>Supported Interfaces - Reticulum Network Stack 0.3.16 beta documentation</title>
|
||||
<title>Supported Interfaces - Reticulum Network Stack 0.4.6 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
@@ -141,7 +141,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.3.16 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.4.6 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -167,7 +167,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.3.16 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.4.6 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Code Examples" href="examples.html" /><link rel="prev" title="Supported Interfaces" href="interfaces.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
|
||||
<title>Building Networks - Reticulum Network Stack 0.3.16 beta documentation</title>
|
||||
<title>Building Networks - Reticulum Network Stack 0.4.6 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
@@ -141,7 +141,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.3.16 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.4.6 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -167,7 +167,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.3.16 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.4.6 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="prev" title="Support Reticulum" href="support.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
|
||||
<title>API Reference - Reticulum Network Stack 0.3.16 beta documentation</title>
|
||||
<title>API Reference - Reticulum Network Stack 0.4.6 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
@@ -141,7 +141,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.3.16 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.4.6 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -167,7 +167,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.3.16 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.4.6 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
@@ -637,7 +637,7 @@ encrypted communication with it.</p>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.Destination.announce">
|
||||
<span class="sig-name descname"><span class="pre">announce</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">app_data</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">path_response</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">False</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">send</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">True</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Destination.announce" title="Permalink to this definition">#</a></dt>
|
||||
<span class="sig-name descname"><span class="pre">announce</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">app_data</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">path_response</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">False</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">attached_interface</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">tag</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">send</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">True</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Destination.announce" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Creates an announce packet for this destination and broadcasts it on all
|
||||
relevant interfaces. Application specific data can be added to the announce.</p>
|
||||
<dl class="field-list simple">
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="#" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/><title>Search - Reticulum Network Stack 0.3.16 beta documentation</title><link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/><title>Search - Reticulum Network Stack 0.4.6 beta documentation</title><link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?digest=30d1aed668e5c3a91c3e3bf6a60b675221979f0e" />
|
||||
@@ -138,7 +138,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.3.16 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.4.6 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -164,7 +164,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.3.16 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.4.6 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="#" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="API Reference" href="reference.html" /><link rel="prev" title="Code Examples" href="examples.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
|
||||
<title>Support Reticulum - Reticulum Network Stack 0.3.16 beta documentation</title>
|
||||
<title>Support Reticulum - Reticulum Network Stack 0.4.6 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
@@ -141,7 +141,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.3.16 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.4.6 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -167,7 +167,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.3.16 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.4.6 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Communications Hardware" href="hardware.html" /><link rel="prev" title="Using Reticulum on Your System" href="using.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
|
||||
<title>Understanding Reticulum - Reticulum Network Stack 0.3.16 beta documentation</title>
|
||||
<title>Understanding Reticulum - Reticulum Network Stack 0.4.6 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
@@ -141,7 +141,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.3.16 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.4.6 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -167,7 +167,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.3.16 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.4.6 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
@@ -1005,7 +1005,7 @@ proof 11
|
||||
| | | | | | | |
|
||||
00000000 00000111 [HASH1, 16 bytes] [CONTEXT, 1 byte] [DATA]
|
||||
|| | | | |
|
||||
|| | | | +-- Hops = 0
|
||||
|| | | | +-- Hops = 7
|
||||
|| | | +------- Packet Type = DATA
|
||||
|| | +--------- Destination Type = SINGLE
|
||||
|| +----------- Propagation Type = BROADCAST
|
||||
@@ -1020,7 +1020,7 @@ proof 11
|
||||
| | | | | | | | | |
|
||||
10000000 00000111 [IFAC, N bytes] [HASH1, 16 bytes] [CONTEXT, 1 byte] [DATA]
|
||||
|| | | | |
|
||||
|| | | | +-- Hops = 0
|
||||
|| | | | +-- Hops = 7
|
||||
|| | | +------- Packet Type = DATA
|
||||
|| | +--------- Destination Type = SINGLE
|
||||
|| +----------- Propagation Type = BROADCAST
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Understanding Reticulum" href="understanding.html" /><link rel="prev" title="Getting Started Fast" href="gettingstartedfast.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
|
||||
<title>Using Reticulum on Your System - Reticulum Network Stack 0.3.16 beta documentation</title>
|
||||
<title>Using Reticulum on Your System - Reticulum Network Stack 0.4.6 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
@@ -141,7 +141,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.3.16 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.4.6 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -167,7 +167,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.3.16 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.4.6 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Getting Started Fast" href="gettingstartedfast.html" /><link rel="prev" title="Reticulum Network Stack Manual" href="index.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
|
||||
<title>What is Reticulum? - Reticulum Network Stack 0.3.16 beta documentation</title>
|
||||
<title>What is Reticulum? - Reticulum Network Stack 0.4.6 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
@@ -141,7 +141,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.3.16 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.4.6 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -167,7 +167,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.3.16 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.4.6 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
|
||||
@@ -160,12 +160,13 @@ Installation
|
||||
|
||||
Once you have obtained compatible boards, you can install the `RNode Firmware <https://github.com/markqvist/RNode_Firmware>`_
|
||||
using the `RNode Configuration Utility <https://github.com/markqvist/rnodeconfigutil>`_.
|
||||
Make sure that ``Python3`` and ``pip`` is installed on your system, and then install
|
||||
the config utility with ``pip``:
|
||||
If you have installed Reticulum on your system, the ``rnodeconf`` program will already be
|
||||
available. If not, make sure that ``Python3`` and ``pip`` is installed on your system, and
|
||||
then install Reticulum with with ``pip``:
|
||||
|
||||
.. code::
|
||||
|
||||
pip3 install rnodeconf
|
||||
pip3 install rns
|
||||
|
||||
Once installation has completed, it is time to start installing the firmware on your
|
||||
devices. Run ``rnodeconf`` in auto-install mode like so:
|
||||
@@ -176,12 +177,7 @@ devices. Run ``rnodeconf`` in auto-install mode like so:
|
||||
|
||||
The utility will guide you through the installation process by asking a series of
|
||||
questions about your hardware. Simply follow the guide, and the utility will
|
||||
auto-install and configure your devices
|
||||
|
||||
**Important Note!** It is currently recommended to use the v1.x line of the RNode firmware,
|
||||
even though the v2.x line is available for early testing. The v2.x line should still be
|
||||
considered an experimental pre-release. Only use the v2.x firmware line if you want to test
|
||||
out the absolutely newest version, and don't care about stability.
|
||||
auto-install and configure your devices.
|
||||
|
||||
.. _rnode-usage:
|
||||
|
||||
|
||||
@@ -771,7 +771,7 @@ Wire Format
|
||||
| | | | | | | |
|
||||
00000000 00000111 [HASH1, 16 bytes] [CONTEXT, 1 byte] [DATA]
|
||||
|| | | | |
|
||||
|| | | | +-- Hops = 0
|
||||
|| | | | +-- Hops = 7
|
||||
|| | | +------- Packet Type = DATA
|
||||
|| | +--------- Destination Type = SINGLE
|
||||
|| +----------- Propagation Type = BROADCAST
|
||||
@@ -786,7 +786,7 @@ Wire Format
|
||||
| | | | | | | | | |
|
||||
10000000 00000111 [IFAC, N bytes] [HASH1, 16 bytes] [CONTEXT, 1 byte] [DATA]
|
||||
|| | | | |
|
||||
|| | | | +-- Hops = 0
|
||||
|| | | | +-- Hops = 7
|
||||
|| | | +------- Packet Type = DATA
|
||||
|| | +--------- Destination Type = SINGLE
|
||||
|| +----------- Propagation Type = BROADCAST
|
||||
|
||||
Reference in New Issue
Block a user