Compare commits

...

82 Commits

Author SHA1 Message Date
Mark Qvist 469947dab9 Updated manual 2022-12-22 15:49:47 +01:00
Mark Qvist 2386fc3635 Updated documentation and manual 2022-12-22 15:11:53 +01:00
Mark Qvist e9e98a00c2 Updated version 2022-12-22 15:07:36 +01:00
Mark Qvist b305eb8e0a Improved path response handling. Prepared destination path response handling for multi-path Transport. 2022-12-22 11:28:56 +01:00
Mark Qvist dd7931d421 Added signal quality stats to announce log output 2022-12-22 11:26:59 +01:00
Mark Qvist 191dce1301 Updated manual 2022-12-20 21:13:23 +01:00
Mark Qvist 3b5a27ba60 Updated readme 2022-12-20 21:08:08 +01:00
Mark Qvist 3c91f7f18b Updated documentation 2022-12-20 20:57:49 +01:00
Mark Qvist 171457713b Improved RNode hotplug over Bluetooth on Android 2022-12-20 15:17:46 +01:00
Mark Qvist 67ee8d6aab Added originator check to path rediscovery on failed links 2022-12-19 01:31:00 +01:00
Mark Qvist 13fa7d49d9 Added automatic path rediscovery on failed link establishments 2022-12-19 01:15:49 +01:00
Mark Qvist 66d921e669 Improved resource advertisement retry handling 2022-12-19 01:10:34 +01:00
Mark Qvist 85f60ea04e Added check for already transferring resource to Link class 2022-12-19 01:04:49 +01:00
Mark Qvist 4870e741f6 Added link request proof signature validation for every transport hop 2022-12-18 21:27:14 +01:00
Mark Qvist f71c1986af Added Heltec USB issue notice to autoinstaller 2022-12-16 23:34:31 +01:00
Mark Qvist 30d8e351dd Updated version 2022-12-16 23:21:22 +01:00
Mark Qvist 5e62e3bc22 Merge branch 'master' of https://git.unsigned.io/markqvist/Reticulum 2022-12-15 21:17:16 +01:00
Mark Qvist 1a67e276ad Updated broken link. Fixes #174. Thanks @mkinney! 2022-12-15 21:16:20 +01:00
Mark Qvist df37a4a884 Updated broken link 2022-12-15 21:15:47 +01:00
Mark Qvist d26bbbd59f Merge branch 'master' of https://git.unsigned.io/markqvist/Reticulum 2022-12-15 17:14:15 +01:00
Mark Qvist 2a264fa7d6 Fixed invalid driver proxy for Qinheng CH34x chips on Android 2022-12-15 17:14:09 +01:00
Mark Qvist d5e0a461cf Fixed invalid check for None 2022-11-25 00:42:22 +01:00
Mark Qvist e28dbd4afa Updated manual 2022-11-24 17:48:04 +01:00
Mark Qvist 8626dcd69f Updated roadmap 2022-11-24 17:30:01 +01:00
Mark Qvist e34f21f4dc Updated roadmap 2022-11-24 17:29:25 +01:00
Mark Qvist f692e81b8e Fixed AutoInterface roaming on Android devices that rotate Ethernet/WiFi MAC addresses on reconnect 2022-11-24 17:19:01 +01:00
Mark Qvist 28e43b52f9 Updated manual 2022-11-24 17:16:43 +01:00
Mark Qvist 680d17fb98 Improved startup time for instances and programs connected to a shared instance 2022-11-24 13:28:22 +01:00
Mark Qvist 1e477c976c Updated documentation 2022-11-24 12:32:43 +01:00
Mark Qvist ab301cdb79 Updated version 2022-11-24 10:45:45 +01:00
Mark Qvist cecb4b3acb Fixed buffered input stream reader not working on Android API levels < 30 2022-11-23 20:39:49 +01:00
Mark Qvist de53a105a4 Improved time pretty-print function 2022-11-23 17:15:46 +01:00
Mark Qvist 9e4ae3c6fe Updated roadmap 2022-11-22 20:20:23 +01:00
Mark Qvist 3482d84bc0 Updated manual 2022-11-17 18:19:42 +01:00
Mark Qvist 51c5c85fcd Updated readme 2022-11-17 16:51:59 +01:00
Mark Qvist 57aeab43a2 Updated readme and roadmap 2022-11-17 12:39:09 +01:00
Mark Qvist 92cccddaab Updated readme and roadmap 2022-11-17 12:36:41 +01:00
Mark Qvist 3de182192a Updated readme and roadmap 2022-11-17 12:35:21 +01:00
Mark Qvist aca6b0c110 Added roadmap 2022-11-17 12:25:48 +01:00
Mark Qvist 3d6e7a9597 Updated docs 2022-11-14 11:25:47 +01:00
markqvist 21da55dd39 Merge pull request #154 from thatv/master
Fixed Hop-number in docs
2022-11-14 11:23:10 +01:00
thatv 9e664af1c6 Update understanding.html 2022-11-12 21:37:27 +01:00
Mark Qvist 7736ed589e Updated manual 2022-11-03 23:08:37 +01:00
Mark Qvist f22504d080 Improved I2P recovery time on unresponsive tunnels 2022-11-03 22:47:08 +01:00
Mark Qvist f22e5cc200 Fixed socket references. Closes #146. 2022-11-03 19:51:04 +01:00
Mark Qvist 87b73b6c67 Updated docs 2022-11-03 19:48:39 +01:00
Mark Qvist 36906f6567 Updated version 2022-11-03 18:05:13 +01:00
Mark Qvist 52edb54d21 Updated readme 2022-11-03 18:05:04 +01:00
Mark Qvist 88b88b9b64 Fixed missing check for socket state 2022-11-03 18:03:00 +01:00
Mark Qvist 76fcad0b53 Added better I2P state visibility to rnstatus util 2022-11-03 17:49:25 +01:00
Mark Qvist 01e520b082 Adjusted I2P interface timings 2022-11-03 16:30:07 +01:00
Mark Qvist 1d2a0fe4c8 Improved I2P tunnel state detection. Fixed missing IFAC init on spawned I2P interfaces. 2022-11-03 15:22:34 +01:00
Mark Qvist 0f19ced9d3 Fixed missing IFAC identity init on spawned TCP clients. Closes #137. 2022-11-03 14:16:00 +01:00
Mark Qvist 4ca32c039d Updated documentation 2022-11-03 12:08:23 +01:00
Mark Qvist 81ec701240 Updated version 2022-11-03 12:05:10 +01:00
Mark Qvist b16d614495 Updated readme 2022-11-03 12:04:54 +01:00
Mark Qvist 5f7e37187f Fixed local firmware cache location for rnodeconf 2022-11-03 12:03:26 +01:00
Mark Qvist 622fd6cf46 Updated docs 2022-11-03 00:45:53 +01:00
Mark Qvist b9d73518dd Improved rnodeconf firmware install 2022-11-03 00:42:46 +01:00
Mark Qvist 17bdf45ac1 Updated documentation 2022-11-02 22:46:47 +01:00
Mark Qvist 36052e2c61 Updated version 2022-11-02 22:34:52 +01:00
Mark Qvist 06d232f889 Added Bluetooth control interface for RNode interfaces on Android 2022-11-02 22:34:07 +01:00
Mark Qvist f9b3c749e0 Improved cleanup on device disconnect 2022-11-02 20:44:09 +01:00
Mark Qvist 63a59753af Implemented Bluetooth support for RNode interfaces on Android. Added Bluetooth/USB multiplexing and Bluetooth manager to interface. 2022-11-02 20:43:46 +01:00
Mark Qvist 20696e7827 Bluetooth support for RNode interfaces on Linux (via standard rfcomm driver) 2022-11-02 20:42:45 +01:00
Mark Qvist 127c9862da Updated manual 2022-11-02 01:31:32 +01:00
Mark Qvist fee9473cac Improved rnodeconf timings 2022-11-02 01:23:23 +01:00
Mark Qvist 5337b72853 Updated manual 2022-11-01 23:54:28 +01:00
Mark Qvist 9bc5d91106 Added rnodeconf to package 2022-11-01 22:40:09 +01:00
Mark Qvist 45ae66e9bf Updated bluetooth control commands for RNode interface 2022-11-01 20:27:41 +01:00
Mark Qvist f03cf34370 Updated documentation 2022-11-01 20:27:11 +01:00
Mark Qvist 47db2a3bd5 Added log output control options 2022-11-01 20:26:55 +01:00
Mark Qvist 40cd961eab Added better teardown handling on RNodeInterfaces 2022-10-30 23:13:44 +01:00
Mark Qvist 34cdd4bf0f Improved RNode error reporting and teardown 2022-10-29 16:41:47 +02:00
Mark Qvist b0ef58e5ca Added support for writing to display framebuffer of connected RNodes 2022-10-29 14:28:53 +02:00
Mark Qvist b6020b5ea8 Updated version 2022-10-29 14:28:06 +02:00
Mark Qvist ee544fcf31 Updated documentation 2022-10-22 01:43:51 +02:00
Mark Qvist 886b0ac0ca Fixed Android interfaces import 2022-10-22 01:38:38 +02:00
Mark Qvist ed4070a3d1 Removed stray import. Fixes #125. 2022-10-22 01:05:08 +02:00
Mark Qvist 6d6568852a Updated docs and manual 2022-10-20 20:15:31 +02:00
Mark Qvist b479e14ca5 Improved handling of Android interfaces in apps without hardware access 2022-10-20 20:10:50 +02:00
Mark Qvist 8fec5cedbe Updated readme 2022-10-20 14:52:11 +02:00
45 changed files with 3603 additions and 374 deletions
+7 -36
View File
@@ -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) 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 ## Notable Features
- Coordination-less globally unique addressing and identification - 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 - 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 - 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 ## Examples of Reticulum Applications
If you want to quickly get an idea of what Reticulum can do, take a look at the If you want to quickly get an idea of what Reticulum can do, take a look at the
following resources. following resources.
@@ -177,40 +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 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. 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
- Support for radio and modem interfaces on Android
- 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 ## Dependencies
The installation of the default `rns` package requires the dependencies listed The installation of the default `rns` package requires the dependencies listed
below. Almost all systems and distributions have readily available packages for below. Almost all systems and distributions have readily available packages for
@@ -274,7 +245,7 @@ file:
[[RNS Testnet I2P Hub A]] [[RNS Testnet I2P Hub A]]
type = I2PInterface type = I2PInterface
enabled = yes enabled = yes
peers = mrwqlsioq4hoo2lmeeud7dkfscnm7yxak7dmiyvsrnpfag3z5tsq.b32.i2p peers = pmlm3l5rpympihoy2o5ago43kluei2jjjzsalcuiuylbve3mwi2a.b32.i2p
# Interface to I2P Hub B # Interface to I2P Hub B
[[RNS Testnet I2P Hub B]] [[RNS Testnet I2P Hub B]]
+1 -1
View File
@@ -33,7 +33,7 @@ def hkdf(length=None, derive_from=None, salt=None, context=None):
if length == None or length < 1: if length == None or length < 1:
raise ValueError("Invalid output key length") 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") raise ValueError("Cannot derive key from empty input material")
if salt == None or len(salt) == 0: if salt == None or len(salt) == 0:
+46 -18
View File
@@ -69,6 +69,8 @@ class Destination:
OUT = 0x12; OUT = 0x12;
directions = [IN, OUT] directions = [IN, OUT]
PR_TAG_WINDOW = 30
@staticmethod @staticmethod
def expand_name(identity, app_name, *aspects): def expand_name(identity, app_name, *aspects):
""" """
@@ -132,6 +134,7 @@ class Destination:
self.proof_strategy = Destination.PROVE_NONE self.proof_strategy = Destination.PROVE_NONE
self.mtu = 0 self.mtu = 0
self.path_responses = {}
self.links = [] self.links = []
if identity == None and direction == Destination.IN and self.type != Destination.PLAIN: if identity == None and direction == Destination.IN and self.type != Destination.PLAIN:
@@ -163,7 +166,7 @@ class Destination:
return "<"+self.name+"/"+self.hexhash+">" 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 Creates an announce packet for this destination and broadcasts it on all
relevant interfaces. Application specific data can be added to the announce. relevant interfaces. Application specific data can be added to the announce.
@@ -174,34 +177,59 @@ class Destination:
if self.type != Destination.SINGLE: if self.type != Destination.SINGLE:
raise TypeError("Only SINGLE destination types can be announced") raise TypeError("Only SINGLE destination types can be announced")
destination_hash = self.hash now = time.time()
random_hash = RNS.Identity.get_random_hash()[0:5]+int(time.time()).to_bytes(5, "big") 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)
if app_data == None and self.default_app_data != None: for entry_tag in stale_responses:
if isinstance(self.default_app_data, bytes): self.path_responses.pop(entry_tag)
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 (path_response == True and tag != None) and tag in self.path_responses:
if app_data != None: # This code is currently not used, since Transport will block duplicate
signed_data += app_data # 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]
signature = self.identity.sign(signed_data) else:
destination_hash = self.hash
random_hash = RNS.Identity.get_random_hash()[0:5]+int(time.time()).to_bytes(5, "big")
announce_data = self.identity.get_public_key()+self.name_hash+random_hash+signature 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
if app_data != None: signed_data = self.hash+self.identity.get_public_key()+self.name_hash+random_hash
announce_data += app_data 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: if path_response:
announce_context = RNS.Packet.PATH_RESPONSE announce_context = RNS.Packet.PATH_RESPONSE
else: else:
announce_context = RNS.Packet.NONE 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: if send:
announce_packet.send() announce_packet.send()
+15 -3
View File
@@ -255,10 +255,22 @@ class Identity:
RNS.Identity.remember(packet.get_hash(), destination_hash, public_key, app_data) RNS.Identity.remember(packet.get_hash(), destination_hash, public_key, app_data)
del announced_identity del announced_identity
if hasattr(packet, "transport_id") and packet.transport_id != None: if packet.rssi != None or packet.snr != 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) 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: 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 return True
+8
View File
@@ -66,10 +66,18 @@ class KISSInterface(Interface):
if RNS.vendor.platformutils.is_android(): if RNS.vendor.platformutils.is_android():
self.on_android = True self.on_android = True
if importlib.util.find_spec('usbserial4a') != None: if importlib.util.find_spec('usbserial4a') != None:
if importlib.util.find_spec('jnius') == None:
RNS.log("Could not load jnius API wrapper for Android, KISS interface cannot be created.", RNS.LOG_CRITICAL)
RNS.log("This probably means you are trying to use an USB-based interface from within Termux or similar.", RNS.LOG_CRITICAL)
RNS.log("This is currently not possible, due to this environment limiting access to the native Android APIs.", RNS.LOG_CRITICAL)
RNS.panic()
from usbserial4a import serial4a as serial from usbserial4a import serial4a as serial
self.parity = "N" self.parity = "N"
else: else:
RNS.log("Could not load USB serial module for Android, KISS interface cannot be created.", RNS.LOG_CRITICAL) RNS.log("Could not load USB serial module for Android, KISS interface cannot be created.", RNS.LOG_CRITICAL)
RNS.log("You can install this module by issuing: pip install usbserial4a", RNS.LOG_CRITICAL)
RNS.panic() RNS.panic()
else: else:
raise SystemError("Android-specific interface was used on non-Android OS") raise SystemError("Android-specific interface was used on non-Android OS")
+509 -104
View File
@@ -44,6 +44,8 @@ class KISS():
CMD_RADIO_STATE = 0x06 CMD_RADIO_STATE = 0x06
CMD_RADIO_LOCK = 0x07 CMD_RADIO_LOCK = 0x07
CMD_DETECT = 0x08 CMD_DETECT = 0x08
CMD_IMPLICIT = 0x09
CMD_LEAVE = 0x0A
CMD_READY = 0x0F CMD_READY = 0x0F
CMD_STAT_RX = 0x21 CMD_STAT_RX = 0x21
CMD_STAT_TX = 0x22 CMD_STAT_TX = 0x22
@@ -51,6 +53,11 @@ class KISS():
CMD_STAT_SNR = 0x24 CMD_STAT_SNR = 0x24
CMD_BLINK = 0x30 CMD_BLINK = 0x30
CMD_RANDOM = 0x40 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_PLATFORM = 0x48
CMD_MCU = 0x49 CMD_MCU = 0x49
CMD_FW_VERSION = 0x50 CMD_FW_VERSION = 0x50
@@ -68,6 +75,7 @@ class KISS():
ERROR_INITRADIO = 0x01 ERROR_INITRADIO = 0x01
ERROR_TXFAILED = 0x02 ERROR_TXFAILED = 0x02
ERROR_EEPROM_LOCKED = 0x03 ERROR_EEPROM_LOCKED = 0x03
ERROR_INVALID_FIRMWARE = 0x10
PLATFORM_AVR = 0x90 PLATFORM_AVR = 0x90
PLATFORM_ESP32 = 0x80 PLATFORM_ESP32 = 0x80
@@ -78,6 +86,134 @@ class KISS():
data = data.replace(bytes([0xc0]), bytes([0xdb, 0xdc])) data = data.replace(bytes([0xc0]), bytes([0xdb, 0xdc]))
return data 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): class RNodeInterface(Interface):
MAX_CHUNK = 32768 MAX_CHUNK = 32768
@@ -90,19 +226,124 @@ class RNodeInterface(Interface):
CALLSIGN_MAX_LEN = 32 CALLSIGN_MAX_LEN = 32
REQUIRED_FW_VER_MAJ = 1 REQUIRED_FW_VER_MAJ = 1
REQUIRED_FW_VER_MIN = 26 REQUIRED_FW_VER_MIN = 52
RECONNECT_WAIT = 5 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 import importlib
if RNS.vendor.platformutils.is_android(): if RNS.vendor.platformutils.is_android():
self.on_android = True self.on_android = True
if importlib.util.find_spec('usbserial4a') != None: if importlib.util.find_spec('usbserial4a') != None:
if importlib.util.find_spec('jnius') == None:
RNS.log("Could not load jnius API wrapper for Android, RNode interface cannot be created.", RNS.LOG_CRITICAL)
RNS.log("This probably means you are trying to use an USB-based interface from within Termux or similar.", RNS.LOG_CRITICAL)
RNS.log("This is currently not possible, due to this environment limiting access to the native Android APIs.", RNS.LOG_CRITICAL)
RNS.panic()
from usbserial4a import serial4a as serial from usbserial4a import serial4a as serial
self.parity = "N" 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: else:
RNS.log("Could not load USB serial module for Android, RNode interface cannot be created.", RNS.LOG_CRITICAL) RNS.log("Could not load USB serial module for Android, RNode interface cannot be created.", RNS.LOG_CRITICAL)
RNS.log("You can install this module by issuing: pip install usbserial4a", RNS.LOG_CRITICAL)
RNS.panic() RNS.panic()
else: else:
raise SystemError("Android-specific interface was used on non-Android OS") raise SystemError("Android-specific interface was used on non-Android OS")
@@ -122,6 +363,8 @@ class RNodeInterface(Interface):
self.stopbits = 1 self.stopbits = 1
self.timeout = 150 self.timeout = 150
self.online = False self.online = False
self.hw_errors = []
self.allow_bluetooth = allow_bluetooth
self.frequency = frequency self.frequency = frequency
self.bandwidth = bandwidth self.bandwidth = bandwidth
@@ -131,6 +374,7 @@ class RNodeInterface(Interface):
self.state = KISS.RADIO_STATE_OFF self.state = KISS.RADIO_STATE_OFF
self.bitrate = 0 self.bitrate = 0
self.platform = None self.platform = None
self.display = None
self.mcu = None self.mcu = None
self.detected = False self.detected = False
self.firmware_ok = False self.firmware_ok = False
@@ -157,6 +401,9 @@ class RNodeInterface(Interface):
self.flow_control = flow_control self.flow_control = flow_control
self.interface_ready = False self.interface_ready = False
self.announce_rate_target = None 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 self.validcfg = True
if (self.frequency < RNodeInterface.FREQ_MIN or self.frequency > RNodeInterface.FREQ_MAX): if (self.frequency < RNodeInterface.FREQ_MIN or self.frequency > RNodeInterface.FREQ_MAX):
@@ -197,75 +444,117 @@ class RNodeInterface(Interface):
try: try:
self.open_port() self.open_port()
if self.serial.is_open: if self.serial != None:
self.configure_device() 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: else:
raise IOError("Could not open serial port") raise IOError("Neither serial port nor Bluetooth devices available")
except Exception as e: except Exception as e:
RNS.log("Could not open serial port for interface "+str(self), RNS.LOG_ERROR) 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("The contained exception was: "+str(e), RNS.LOG_ERROR)
RNS.log("Reticulum will attempt to bring up this interface periodically", RNS.LOG_ERROR) if len(self.hw_errors) == 0:
thread = threading.Thread(target=self.reconnect_port) RNS.log("Reticulum will attempt to bring up this interface periodically", RNS.LOG_ERROR)
thread.daemon = True thread = threading.Thread(target=self.reconnect_port)
thread.start() 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): def open_port(self):
RNS.log("Opening serial port "+self.port+"...") if self.port != None:
# Get device parameters RNS.log("Opening serial port "+self.port+"...")
from usb4a import usb # Get device parameters
device = usb.get_usb_device(self.port) from usb4a import usb
if device: device = usb.get_usb_device(self.port)
vid = device.getVendorId() if device:
pid = device.getProductId() vid = device.getVendorId()
pid = device.getProductId()
# Driver overrides for speficic chips # Driver overrides for speficic chips
proxy = self.pyserial.get_serial_port proxy = self.pyserial.get_serial_port
if vid == 0x1A86 and pid == 0x55D4: if vid == 0x1A86 and pid == 0x55D4:
# Force CDC driver for Qinheng CH34x # Force CDC driver for Qinheng CH34x
RNS.log(str(self)+" using CDC driver for "+RNS.hexrep(vid)+":"+RNS.hexrep(pid), RNS.LOG_DEBUG) RNS.log(str(self)+" using CDC driver for "+RNS.hexrep(vid)+":"+RNS.hexrep(pid), RNS.LOG_DEBUG)
from usbserial4a.cdcacmserial4a import CdcAcmSerial from usbserial4a.cdcacmserial4a import CdcAcmSerial
proxy = CdcAcmSerial proxy = CdcAcmSerial
self.serial = proxy( self.serial = proxy(
self.port, self.port,
baudrate = self.speed, baudrate = self.speed,
bytesize = self.databits, bytesize = self.databits,
parity = self.parity, parity = self.parity,
stopbits = self.stopbits, stopbits = self.stopbits,
xonxoff = False, xonxoff = False,
rtscts = False, rtscts = False,
timeout = None, timeout = None,
inter_byte_timeout = None, inter_byte_timeout = None,
# write_timeout = wtimeout, # write_timeout = wtimeout,
dsrdtr = False, dsrdtr = False,
) )
if vid == 0x0403: if vid == 0x0403:
# Hardware parameters for FTDI devices @ 115200 baud # Hardware parameters for FTDI devices @ 115200 baud
self.serial.DEFAULT_READ_BUFFER_SIZE = 16 * 1024 self.serial.DEFAULT_READ_BUFFER_SIZE = 16 * 1024
self.serial.USB_READ_TIMEOUT_MILLIS = 100 self.serial.USB_READ_TIMEOUT_MILLIS = 100
self.serial.timeout = 0.1 self.serial.timeout = 0.1
elif vid == 0x10C4: elif vid == 0x10C4:
# Hardware parameters for SiLabs CP210x @ 115200 baud # Hardware parameters for SiLabs CP210x @ 115200 baud
self.serial.DEFAULT_READ_BUFFER_SIZE = 64 self.serial.DEFAULT_READ_BUFFER_SIZE = 64
self.serial.USB_READ_TIMEOUT_MILLIS = 12 self.serial.USB_READ_TIMEOUT_MILLIS = 12
self.serial.timeout = 0.012 self.serial.timeout = 0.012
elif vid == 0x1A86 and pid == 0x55D4: elif vid == 0x1A86 and pid == 0x55D4:
# Hardware parameters for Qinheng CH34x @ 115200 baud # Hardware parameters for Qinheng CH34x @ 115200 baud
self.serial.DEFAULT_READ_BUFFER_SIZE = 64 self.serial.DEFAULT_READ_BUFFER_SIZE = 64
self.serial.USB_READ_TIMEOUT_MILLIS = 12 self.serial.USB_READ_TIMEOUT_MILLIS = 12
self.serial.timeout = 0.1 self.serial.timeout = 0.1
else: else:
# Default values # Default values
self.serial.DEFAULT_READ_BUFFER_SIZE = 1 * 1024 self.serial.DEFAULT_READ_BUFFER_SIZE = 1 * 1024
self.serial.USB_READ_TIMEOUT_MILLIS = 100 self.serial.USB_READ_TIMEOUT_MILLIS = 100
self.serial.timeout = 0.1 self.serial.timeout = 0.1
RNS.log(str(self)+" USB read buffer size set to "+RNS.prettysize(self.serial.DEFAULT_READ_BUFFER_SIZE), RNS.LOG_DEBUG) RNS.log(str(self)+" USB read buffer size set to "+RNS.prettysize(self.serial.DEFAULT_READ_BUFFER_SIZE), RNS.LOG_DEBUG)
RNS.log(str(self)+" USB read timeout set to "+str(self.serial.USB_READ_TIMEOUT_MILLIS)+"ms", RNS.LOG_DEBUG) RNS.log(str(self)+" USB read timeout set to "+str(self.serial.USB_READ_TIMEOUT_MILLIS)+"ms", RNS.LOG_DEBUG)
RNS.log(str(self)+" USB write timeout set to "+str(self.serial.USB_WRITE_TIMEOUT_MILLIS)+"ms", RNS.LOG_DEBUG) RNS.log(str(self)+" USB write timeout set to "+str(self.serial.USB_WRITE_TIMEOUT_MILLIS)+"ms", RNS.LOG_DEBUG)
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): def configure_device(self):
@@ -275,18 +564,22 @@ class RNodeInterface(Interface):
thread.start() thread.start()
self.detect() self.detect()
sleep(0.4) sleep(0.5)
if not self.detected: if not self.detected:
raise IOError("Could not detect device") raise IOError("Could not detect device")
# else: else:
# if self.platform == KISS.PLATFORM_ESP32: if self.platform == KISS.PLATFORM_ESP32:
# TODO: Consider reintroducing this self.display = True
# RNS.log("Resetting ESP32-based device before configuration...", RNS.LOG_VERBOSE)
# self.hard_reset() 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) RNS.log("Configuring RNode interface...", RNS.LOG_VERBOSE)
self.initRadio() self.initRadio()
if (self.validateRadioState()): if (self.validateRadioState()):
@@ -298,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("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("Make sure that your hardware actually supports the parameters specified in the configuration", RNS.LOG_ERROR)
RNS.log("Aborting RNode startup", 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") raise IOError("RNode interface did not pass configuration validation")
def initRadio(self): def initRadio(self):
self.setFrequency() self.setFrequency()
time.sleep(0.1) time.sleep(0.15)
self.setBandwidth() self.setBandwidth()
time.sleep(0.1) time.sleep(0.15)
self.setTXPower() self.setTXPower()
time.sleep(0.1) time.sleep(0.15)
self.setSpreadingFactor() self.setSpreadingFactor()
time.sleep(0.1) time.sleep(0.15)
self.setCodingRate() self.setCodingRate()
time.sleep(0.1) time.sleep(0.15)
self.setRadioState(KISS.RADIO_STATE_ON) self.setRadioState(KISS.RADIO_STATE_ON)
time.sleep(0.1) time.sleep(0.15)
def detect(self): 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]) 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): 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): def hard_reset(self):
kiss_command = bytes([KISS.FEND, KISS.CMD_RESET, 0xf8, KISS.FEND]) 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): if written != len(kiss_command):
raise IOError("An IO error occurred while restarting device") raise IOError("An IO error occurred while restarting device")
sleep(4.0); sleep(4.0);
@@ -342,9 +703,9 @@ class RNodeInterface(Interface):
data = KISS.escape(bytes([c1])+bytes([c2])+bytes([c3])+bytes([c4])) data = KISS.escape(bytes([c1])+bytes([c2])+bytes([c3])+bytes([c4]))
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_FREQUENCY])+data+bytes([KISS.FEND]) 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): 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): def setBandwidth(self):
c1 = self.bandwidth >> 24 c1 = self.bandwidth >> 24
@@ -354,37 +715,37 @@ class RNodeInterface(Interface):
data = KISS.escape(bytes([c1])+bytes([c2])+bytes([c3])+bytes([c4])) data = KISS.escape(bytes([c1])+bytes([c2])+bytes([c3])+bytes([c4]))
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_BANDWIDTH])+data+bytes([KISS.FEND]) 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): 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): def setTXPower(self):
txp = bytes([self.txpower]) txp = bytes([self.txpower])
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_TXPOWER])+txp+bytes([KISS.FEND]) 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): 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): def setSpreadingFactor(self):
sf = bytes([self.sf]) sf = bytes([self.sf])
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_SF])+sf+bytes([KISS.FEND]) 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): 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): def setCodingRate(self):
cr = bytes([self.cr]) cr = bytes([self.cr])
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_CR])+cr+bytes([KISS.FEND]) 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): 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): def setRadioState(self, state):
self.state = state self.state = state
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_RADIO_STATE])+bytes([state])+bytes([KISS.FEND]) 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): 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): def validate_firmware(self):
if (self.maj_version >= RNodeInterface.REQUIRED_FW_VER_MAJ): if (self.maj_version >= RNodeInterface.REQUIRED_FW_VER_MAJ):
@@ -396,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("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("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() 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): def validateRadioState(self):
@@ -465,7 +829,7 @@ class RNodeInterface(Interface):
data = KISS.escape(data) data = KISS.escape(data)
frame = bytes([0xc0])+bytes([0x00])+data+bytes([0xc0]) frame = bytes([0xc0])+bytes([0x00])+data+bytes([0xc0])
written = self.serial.write(frame) written = self.write_mux(frame)
self.txb += datalen self.txb += datalen
if written != len(frame): if written != len(frame):
@@ -493,11 +857,14 @@ class RNodeInterface(Interface):
command_buffer = b"" command_buffer = b""
last_read_ms = int(time.time()*1000) last_read_ms = int(time.time()*1000)
# TODO: Ensure hotplug support # TODO: Ensure hotplug support for serial drivers
while self.serial.is_open: # This should work now with the new time-based
# TODO: Check multibyte reads # detect polling.
serial_bytes = self.serial.read() 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) got = len(serial_bytes)
if got > 0:
self.last_port_io = time.time()
for byte in serial_bytes: for byte in serial_bytes:
last_read_ms = int(time.time()*1000) last_read_ms = int(time.time()*1000)
@@ -670,11 +1037,19 @@ class RNodeInterface(Interface):
if time.time() > self.first_tx + self.id_interval: 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) RNS.log("Interface "+str(self)+" is transmitting beacon data: "+str(self.id_callsign.decode("utf-8")), RNS.LOG_DEBUG)
self.processOutgoing(self.id_callsign) 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: except Exception as e:
self.online = False 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) RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is now offline.", RNS.LOG_ERROR)
if RNS.Reticulum.panic_on_interface_error: if RNS.Reticulum.panic_on_interface_error:
@@ -683,21 +1058,51 @@ class RNodeInterface(Interface):
RNS.log("Reticulum will attempt to reconnect the interface periodically.", RNS.LOG_ERROR) RNS.log("Reticulum will attempt to reconnect the interface periodically.", RNS.LOG_ERROR)
self.online = False 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() self.reconnect_port()
def reconnect_port(self): def reconnect_port(self):
while not self.online: while not self.online and len(self.hw_errors) == 0:
try: try:
time.sleep(self.reconnect_w) 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_VERBOSE)
if self.bt_manager != None:
RNS.log("Attempting to reconnect Bluetooth device for "+str(self)+"...", RNS.LOG_VERBOSE)
self.open_port() self.open_port()
if hasattr(self, "serial") and self.serial != None and self.serial.is_open: if hasattr(self, "serial") and self.serial != None and self.serial.is_open:
self.configure_device() self.configure_device()
except Exception as e: if self.online:
RNS.log("Error while reconnecting port, the contained exception was: "+str(e), RNS.LOG_ERROR) if self.last_imagedata != None:
self.display_image(self.last_imagedata)
self.enable_external_framebuffer()
RNS.log("Reconnected serial port for "+str(self)) 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()
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): def __str__(self):
return "RNodeInterface["+str(self.name)+"]" return "RNodeInterface["+str(self.name)+"]"
@@ -56,10 +56,18 @@ class SerialInterface(Interface):
if RNS.vendor.platformutils.is_android(): if RNS.vendor.platformutils.is_android():
self.on_android = True self.on_android = True
if importlib.util.find_spec('usbserial4a') != None: if importlib.util.find_spec('usbserial4a') != None:
if importlib.util.find_spec('jnius') == None:
RNS.log("Could not load jnius API wrapper for Android, Serial interface cannot be created.", RNS.LOG_CRITICAL)
RNS.log("This probably means you are trying to use an USB-based interface from within Termux or similar.", RNS.LOG_CRITICAL)
RNS.log("This is currently not possible, due to this environment limiting access to the native Android APIs.", RNS.LOG_CRITICAL)
RNS.panic()
from usbserial4a import serial4a as serial from usbserial4a import serial4a as serial
self.parity = "N" self.parity = "N"
else: else:
RNS.log("Could not load USB serial module for Android, Serial interface cannot be created.", RNS.LOG_CRITICAL) RNS.log("Could not load USB serial module for Android, Serial interface cannot be created.", RNS.LOG_CRITICAL)
RNS.log("You can install this module by issuing: pip install usbserial4a", RNS.LOG_CRITICAL)
RNS.panic() RNS.panic()
else: else:
raise SystemError("Android-specific interface was used on non-Android OS") raise SystemError("Android-specific interface was used on non-Android OS")
+27
View File
@@ -0,0 +1,27 @@
# MIT License
#
# Copyright (c) 2016-2022 Mark Qvist / unsigned.io
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import os
import glob
modules = glob.glob(os.path.dirname(__file__)+"/*.py")
__all__ = [ os.path.basename(f)[:-3] for f in modules if not f.endswith('__init__.py')]
+33 -7
View File
@@ -48,6 +48,11 @@ class AutoInterface(Interface):
BITRATE_GUESS = 10*1000*1000 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): 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 import importlib
if importlib.util.find_spec('netifaces') != None: if importlib.util.find_spec('netifaces') != None:
@@ -70,6 +75,7 @@ class AutoInterface(Interface):
self.peers = {} self.peers = {}
self.link_local_addresses = [] self.link_local_addresses = []
self.adopted_interfaces = {} self.adopted_interfaces = {}
self.interface_servers = {}
self.multicast_echoes = {} self.multicast_echoes = {}
self.timed_out_interfaces = {} self.timed_out_interfaces = {}
@@ -199,11 +205,6 @@ class AutoInterface(Interface):
peering_wait = self.announce_interval*1.2 peering_wait = self.announce_interval*1.2
RNS.log(str(self)+" discovering peers for "+str(round(peering_wait, 2))+" seconds...", RNS.LOG_VERBOSE) 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 self.owner = owner
socketserver.UDPServer.address_family = socket.AF_INET6 socketserver.UDPServer.address_family = socket.AF_INET6
@@ -212,9 +213,10 @@ class AutoInterface(Interface):
addr_info = socket.getaddrinfo(local_addr, self.data_port, socket.AF_INET6, socket.SOCK_DGRAM) addr_info = socket.getaddrinfo(local_addr, self.data_port, socket.AF_INET6, socket.SOCK_DGRAM)
address = addr_info[0][4] 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.daemon = True
thread.start() thread.start()
@@ -277,8 +279,32 @@ class AutoInterface(Interface):
if address["addr"].startswith("fe80:"): if address["addr"].startswith("fe80:"):
link_local_addr = address["addr"].split("%")[0] link_local_addr = address["addr"].split("%")[0]
if link_local_addr != self.adopted_interfaces[ifname]: if link_local_addr != self.adopted_interfaces[ifname]:
# TODO: Remove
# RNS.log("Replacing link-local address for "+str(ifname), RNS.LOG_DEBUG)
self.adopted_interfaces[ifname] = link_local_addr self.adopted_interfaces[ifname] = link_local_addr
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:
# TODO: Remove
# RNS.log("Shutting down previous UDP socket server for "+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()
# TODO: Remove
# RNS.log("Starting new UDP socket server for "+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: 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) RNS.log("Could not get device information while updating link-local addresses for "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
+125 -27
View File
@@ -161,8 +161,6 @@ class I2PController:
raise tn.status["exception"] raise tn.status["exception"]
else: else:
self.client_tunnels[i2p_destination] = True
owner.awaiting_i2p_tunnel = False
if owner.socket != None: if owner.socket != None:
if hasattr(owner.socket, "close"): if hasattr(owner.socket, "close"):
if callable(owner.socket.close): if callable(owner.socket.close):
@@ -175,6 +173,8 @@ class I2PController:
owner.socket.close() owner.socket.close()
except Exception as e: except Exception as e:
RNS.log("Error while closing socket for "+str(owner)+": "+str(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) RNS.log(str(owner)+" tunnel setup complete", RNS.LOG_VERBOSE)
@@ -383,6 +383,11 @@ class I2PInterfacePeer(Interface):
I2P_PROBE_AFTER = 10 I2P_PROBE_AFTER = 10
I2P_PROBE_INTERVAL = 9 I2P_PROBE_INTERVAL = 9
I2P_PROBES = 5 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): def __init__(self, parent_interface, owner, name, target_i2p_dest=None, connected_socket=None, max_reconnect_tries=None):
self.rxb = 0 self.rxb = 0
@@ -409,6 +414,30 @@ class I2PInterfacePeer(Interface):
self.i2p_tunnel_ready = False self.i2p_tunnel_ready = False
self.mode = RNS.Interfaces.Interface.Interface.MODE_FULL self.mode = RNS.Interfaces.Interface.Interface.MODE_FULL
self.bitrate = I2PInterface.BITRATE_GUESS 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_target = None
self.announce_rate_grace = 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("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) 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 = threading.Thread(target=tunnel_job)
thread.daemon = True thread.daemon = True
@@ -463,6 +492,7 @@ class I2PInterfacePeer(Interface):
def wait_job(): def wait_job():
while self.awaiting_i2p_tunnel: while self.awaiting_i2p_tunnel:
time.sleep(0.25) time.sleep(0.25)
time.sleep(2)
if not self.kiss_framing: if not self.kiss_framing:
self.wants_tunnel = True self.wants_tunnel = True
@@ -482,18 +512,11 @@ class I2PInterfacePeer(Interface):
def set_timeouts_linux(self): def set_timeouts_linux(self):
if not self.i2p_tunneled: self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_USER_TIMEOUT, int(I2PInterfacePeer.I2P_USER_TIMEOUT * 1000))
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.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_KEEPIDLE, int(I2PInterfacePeer.TCP_PROBE_AFTER)) self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, int(I2PInterfacePeer.I2P_PROBE_INTERVAL))
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.I2P_PROBES))
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))
def set_timeouts_osx(self): def set_timeouts_osx(self):
if hasattr(socket, "TCP_KEEPALIVE"): if hasattr(socket, "TCP_KEEPALIVE"):
@@ -502,22 +525,19 @@ class I2PInterfacePeer(Interface):
TCP_KEEPIDLE = 0x10 TCP_KEEPIDLE = 0x10
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
self.socket.setsockopt(socket.IPPROTO_TCP, TCP_KEEPIDLE, int(I2PInterfacePeer.I2P_PROBE_AFTER))
if not self.i2p_tunneled: def shutdown_socket(self, target_socket):
self.socket.setsockopt(socket.IPPROTO_TCP, TCP_KEEPIDLE, int(I2PInterfacePeer.TCP_PROBE_AFTER)) if callable(target_socket.close):
else:
self.socket.setsockopt(socket.IPPROTO_TCP, TCP_KEEPIDLE, int(I2PInterfacePeer.I2P_PROBE_AFTER))
def shutdown_socket(self, socket):
if callable(socket.close):
try: try:
socket.shutdown(socket.SHUT_RDWR) if socket != None:
target_socket.shutdown(socket.SHUT_RDWR)
except Exception as e: except Exception as e:
RNS.log("Error while shutting down socket for "+str(self)+": "+str(e)) RNS.log("Error while shutting down socket for "+str(self)+": "+str(e))
try: try:
socket.close() if socket != None:
target_socket.close()
except Exception as e: except Exception as e:
RNS.log("Error while closing socket for "+str(self)+": "+str(e)) RNS.log("Error while closing socket for "+str(self)+": "+str(e))
@@ -571,7 +591,6 @@ class I2PInterfacePeer(Interface):
return True return True
def reconnect(self): def reconnect(self):
if self.initiator: if self.initiator:
if not self.reconnecting: if not self.reconnecting:
@@ -632,6 +651,7 @@ class I2PInterfacePeer(Interface):
self.socket.sendall(data) self.socket.sendall(data)
self.writing = False self.writing = False
self.txb += len(data) self.txb += len(data)
self.last_write = time.time()
if hasattr(self, "parent_interface") and self.parent_interface != None and self.parent_count: if hasattr(self, "parent_interface") and self.parent_interface != None and self.parent_count:
self.parent_interface.txb += len(data) self.parent_interface.txb += len(data)
@@ -642,8 +662,59 @@ class I2PInterfacePeer(Interface):
self.teardown() 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): def read_loop(self):
try: 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 in_frame = False
escape = False escape = False
data_buffer = b"" data_buffer = b""
@@ -653,6 +724,7 @@ class I2PInterfacePeer(Interface):
data_in = self.socket.recv(4096) data_in = self.socket.recv(4096)
if len(data_in) > 0: if len(data_in) > 0:
pointer = 0 pointer = 0
self.last_read = time.time()
while pointer < len(data_in): while pointer < len(data_in):
byte = data_in[pointer] byte = data_in[pointer]
pointer += 1 pointer += 1
@@ -705,6 +777,11 @@ class I2PInterfacePeer(Interface):
data_buffer = data_buffer+bytes([byte]) data_buffer = data_buffer+bytes([byte])
else: else:
self.online = False self.online = False
self.wd_reset = True
time.sleep(2)
self.wd_reset = False
if self.initiator and not self.detached: if self.initiator and not self.detached:
RNS.log("Socket for "+str(self)+" was closed, attempting to reconnect...", RNS.LOG_WARNING) RNS.log("Socket for "+str(self)+" was closed, attempting to reconnect...", RNS.LOG_WARNING)
self.reconnect() self.reconnect()
@@ -754,7 +831,7 @@ class I2PInterfacePeer(Interface):
class I2PInterface(Interface): class I2PInterface(Interface):
BITRATE_GUESS = 256*1000 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.rxb = 0
self.txb = 0 self.txb = 0
@@ -780,6 +857,9 @@ class I2PInterface(Interface):
self.bind_port = self.i2p.get_free_port() self.bind_port = self.i2p.get_free_port()
self.address = (self.bind_ip, self.bind_port) self.address = (self.bind_ip, self.bind_port)
self.bitrate = I2PInterface.BITRATE_GUESS self.bitrate = I2PInterface.BITRATE_GUESS
self.ifac_size = ifac_size
self.ifac_netname = ifac_netname
self.ifac_netkey = ifac_netkey
self.online = False self.online = False
@@ -850,9 +930,27 @@ class I2PInterface(Interface):
spawned_interface.parent_interface = self spawned_interface.parent_interface = self
spawned_interface.online = True spawned_interface.online = True
spawned_interface.bitrate = self.bitrate spawned_interface.bitrate = self.bitrate
spawned_interface.ifac_size = self.ifac_size spawned_interface.ifac_size = self.ifac_size
spawned_interface.ifac_netname = self.ifac_netname spawned_interface.ifac_netname = self.ifac_netname
spawned_interface.ifac_netkey = self.ifac_netkey 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_target = self.announce_rate_target
spawned_interface.announce_rate_grace = self.announce_rate_grace spawned_interface.announce_rate_grace = self.announce_rate_grace
spawned_interface.announce_rate_penalty = self.announce_rate_penalty spawned_interface.announce_rate_penalty = self.announce_rate_penalty
+66 -13
View File
@@ -44,6 +44,7 @@ class KISS():
CMD_RADIO_STATE = 0x06 CMD_RADIO_STATE = 0x06
CMD_RADIO_LOCK = 0x07 CMD_RADIO_LOCK = 0x07
CMD_DETECT = 0x08 CMD_DETECT = 0x08
CMD_LEAVE = 0x0A
CMD_READY = 0x0F CMD_READY = 0x0F
CMD_STAT_RX = 0x21 CMD_STAT_RX = 0x21
CMD_STAT_TX = 0x22 CMD_STAT_TX = 0x22
@@ -51,6 +52,9 @@ class KISS():
CMD_STAT_SNR = 0x24 CMD_STAT_SNR = 0x24
CMD_BLINK = 0x30 CMD_BLINK = 0x30
CMD_RANDOM = 0x40 CMD_RANDOM = 0x40
CMD_FB_EXT = 0x41
CMD_FB_READ = 0x42
CMD_FB_WRITE = 0x43
CMD_PLATFORM = 0x48 CMD_PLATFORM = 0x48
CMD_MCU = 0x49 CMD_MCU = 0x49
CMD_FW_VERSION = 0x50 CMD_FW_VERSION = 0x50
@@ -90,7 +94,7 @@ class RNodeInterface(Interface):
CALLSIGN_MAX_LEN = 32 CALLSIGN_MAX_LEN = 32
REQUIRED_FW_VER_MAJ = 1 REQUIRED_FW_VER_MAJ = 1
REQUIRED_FW_VER_MIN = 26 REQUIRED_FW_VER_MIN = 52
RECONNECT_WAIT = 5 RECONNECT_WAIT = 5
@@ -130,6 +134,7 @@ class RNodeInterface(Interface):
self.state = KISS.RADIO_STATE_OFF self.state = KISS.RADIO_STATE_OFF
self.bitrate = 0 self.bitrate = 0
self.platform = None self.platform = None
self.display = None
self.mcu = None self.mcu = None
self.detected = False self.detected = False
self.firmware_ok = False self.firmware_ok = False
@@ -240,8 +245,7 @@ class RNodeInterface(Interface):
raise IOError("Could not detect device") raise IOError("Could not detect device")
else: else:
if self.platform == KISS.PLATFORM_ESP32: if self.platform == KISS.PLATFORM_ESP32:
RNS.log("Resetting ESP32-based device before configuration...", RNS.LOG_VERBOSE) self.display = True
self.hard_reset()
RNS.log("Serial port "+self.port+" is now open") RNS.log("Serial port "+self.port+" is now open")
RNS.log("Configuring RNode interface...", RNS.LOG_VERBOSE) 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]) 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.serial.write(kiss_command)
if written != len(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): def hard_reset(self):
kiss_command = bytes([KISS.FEND, KISS.CMD_RESET, 0xf8, KISS.FEND]) 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]) kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_FREQUENCY])+data+bytes([KISS.FEND])
written = self.serial.write(kiss_command) written = self.serial.write(kiss_command)
if written != len(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): def setBandwidth(self):
c1 = self.bandwidth >> 24 c1 = self.bandwidth >> 24
@@ -302,35 +350,35 @@ class RNodeInterface(Interface):
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_BANDWIDTH])+data+bytes([KISS.FEND]) kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_BANDWIDTH])+data+bytes([KISS.FEND])
written = self.serial.write(kiss_command) written = self.serial.write(kiss_command)
if written != len(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): def setTXPower(self):
txp = bytes([self.txpower]) txp = bytes([self.txpower])
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_TXPOWER])+txp+bytes([KISS.FEND]) kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_TXPOWER])+txp+bytes([KISS.FEND])
written = self.serial.write(kiss_command) written = self.serial.write(kiss_command)
if written != len(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): def setSpreadingFactor(self):
sf = bytes([self.sf]) sf = bytes([self.sf])
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_SF])+sf+bytes([KISS.FEND]) kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_SF])+sf+bytes([KISS.FEND])
written = self.serial.write(kiss_command) written = self.serial.write(kiss_command)
if written != len(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): def setCodingRate(self):
cr = bytes([self.cr]) cr = bytes([self.cr])
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_CR])+cr+bytes([KISS.FEND]) kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_CR])+cr+bytes([KISS.FEND])
written = self.serial.write(kiss_command) written = self.serial.write(kiss_command)
if written != len(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): def setRadioState(self, state):
self.state = state self.state = state
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_RADIO_STATE])+bytes([state])+bytes([KISS.FEND]) kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_RADIO_STATE])+bytes([state])+bytes([KISS.FEND])
written = self.serial.write(kiss_command) written = self.serial.write(kiss_command)
if written != len(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): def validate_firmware(self):
if (self.maj_version >= RNodeInterface.REQUIRED_FW_VER_MAJ): 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("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("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() RNS.panic()
@@ -634,8 +682,13 @@ class RNodeInterface(Interface):
except Exception as e: except Exception as e:
RNS.log("Error while reconnecting port, the contained exception was: "+str(e), RNS.LOG_ERROR) 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): def __str__(self):
return "RNodeInterface["+str(self.name)+"]" return "RNodeInterface["+str(self.name)+"]"
+18
View File
@@ -483,9 +483,27 @@ class TCPServerInterface(Interface):
spawned_interface.target_port = str(handler.client_address[1]) spawned_interface.target_port = str(handler.client_address[1])
spawned_interface.parent_interface = self spawned_interface.parent_interface = self
spawned_interface.bitrate = self.bitrate spawned_interface.bitrate = self.bitrate
spawned_interface.ifac_size = self.ifac_size spawned_interface.ifac_size = self.ifac_size
spawned_interface.ifac_netname = self.ifac_netname spawned_interface.ifac_netname = self.ifac_netname
spawned_interface.ifac_netkey = self.ifac_netkey 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_target = self.announce_rate_target
spawned_interface.announce_rate_grace = self.announce_rate_grace spawned_interface.announce_rate_grace = self.announce_rate_grace
spawned_interface.announce_rate_penalty = self.announce_rate_penalty spawned_interface.announce_rate_penalty = self.announce_rate_penalty
+1
View File
@@ -22,6 +22,7 @@
import os import os
import glob import glob
import RNS.Interfaces.Android
modules = glob.glob(os.path.dirname(__file__)+"/*.py") 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')]
+7
View File
@@ -900,6 +900,13 @@ class Link:
def register_incoming_resource(self, resource): def register_incoming_resource(self, resource):
self.incoming_resources.append(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): def cancel_outgoing_resource(self, resource):
if resource in self.outgoing_resources: if resource in self.outgoing_resources:
self.outgoing_resources.remove(resource) self.outgoing_resources.remove(resource)
+19 -13
View File
@@ -172,20 +172,26 @@ class Resource:
resource.consecutive_completed_height = 0 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) RNS.log("Accepting resource advertisement for "+RNS.prettyhexrep(resource.hash), RNS.LOG_DEBUG)
if resource.link.callbacks.resource_started != None: if resource.link.callbacks.resource_started != None:
try: try:
resource.link.callbacks.resource_started(resource) resource.link.callbacks.resource_started(resource)
except Exception as e: 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("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: except Exception as e:
RNS.log("Could not decode resource advertisement, dropping resource", RNS.LOG_DEBUG) RNS.log("Could not decode resource advertisement, dropping resource", RNS.LOG_DEBUG)
return None return None
@@ -397,8 +403,7 @@ class Resource:
thread.start() thread.start()
def __advertise_job(self): def __advertise_job(self):
data = ResourceAdvertisement(self).pack() self.advertisement_packet = RNS.Packet(self.link, ResourceAdvertisement(self).pack(), context=RNS.Packet.RESOURCE_ADV)
self.advertisement_packet = RNS.Packet(self.link, data, context=RNS.Packet.RESOURCE_ADV)
while not self.link.ready_for_new_resource(): while not self.link.ready_for_new_resource():
self.status = Resource.QUEUED self.status = Resource.QUEUED
sleep(0.25) sleep(0.25)
@@ -445,7 +450,8 @@ class Resource:
try: try:
RNS.log("No part requests received, retrying resource advertisement...", RNS.LOG_DEBUG) RNS.log("No part requests received, retrying resource advertisement...", RNS.LOG_DEBUG)
self.retries_left -= 1 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.last_activity = time.time()
self.adv_sent = self.last_activity self.adv_sent = self.last_activity
sleep_time = 0.001 sleep_time = 0.001
+19 -4
View File
@@ -631,12 +631,18 @@ class Reticulum:
i2p_peers = c.as_list("peers") if "peers" in c else None i2p_peers = c.as_list("peers") if "peers" in c else None
connectable = c.as_bool("connectable") if "connectable" in c else False connectable = c.as_bool("connectable") if "connectable" in c else False
if ifac_size == None:
ifac_size = 16
interface = I2PInterface.I2PInterface( interface = I2PInterface.I2PInterface(
RNS.Transport, RNS.Transport,
name, name,
Reticulum.storagepath, Reticulum.storagepath,
i2p_peers, i2p_peers,
connectable = connectable, connectable = connectable,
ifac_size = ifac_size,
ifac_netname = ifac_netname,
ifac_netkey = ifac_netkey,
) )
if "outgoing" in c and c.as_bool("outgoing") == False: if "outgoing" in c and c.as_bool("outgoing") == False:
@@ -653,10 +659,6 @@ class Reticulum:
interface.announce_cap = announce_cap interface.announce_cap = announce_cap
if configured_bitrate: if configured_bitrate:
interface.bitrate = configured_bitrate interface.bitrate = configured_bitrate
if ifac_size != None:
interface.ifac_size = ifac_size
else:
interface.ifac_size = 16
if c["type"] == "SerialInterface": if c["type"] == "SerialInterface":
port = c["port"] if "port" in c else None port = c["port"] if "port" in c else None
@@ -1071,6 +1073,19 @@ class Reticulum:
else: else:
ifstats["i2p_b32"] = None 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 hasattr(interface, "bitrate"):
if interface.bitrate != None: if interface.bitrate != None:
ifstats["bitrate"] = interface.bitrate ifstats["bitrate"] = interface.bitrate
+106 -58
View File
@@ -66,6 +66,7 @@ class Transport:
PATH_REQUEST_TIMEOUT = 15 # Default timuout for client path requests in seconds 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_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_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 LINK_TIMEOUT = RNS.Link.STALE_TIME * 1.25
REVERSE_TIMEOUT = 30*60 # Reverse table entries are removed after 30 minutes 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 announce_handlers = [] # A table storing externally registered announce handlers
tunnels = {} # A table storing tunnels to other transport instances tunnels = {} # A table storing tunnels to other transport instances
announce_rate_table = {} # A table for keeping track of announce rates 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_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 discovery_pr_tags = [] # A table for keeping track of tagged path requests
@@ -144,13 +146,14 @@ class Transport:
RNS.log("Loaded Transport Identity from storage", RNS.LOG_VERBOSE) RNS.log("Loaded Transport Identity from storage", RNS.LOG_VERBOSE)
packet_hashlist_path = RNS.Reticulum.storagepath+"/packet_hashlist" packet_hashlist_path = RNS.Reticulum.storagepath+"/packet_hashlist"
if os.path.isfile(packet_hashlist_path): if not Transport.owner.is_connected_to_shared_instance:
try: if os.path.isfile(packet_hashlist_path):
file = open(packet_hashlist_path, "rb") try:
Transport.packet_hashlist = umsgpack.unpackb(file.read()) file = open(packet_hashlist_path, "rb")
file.close() Transport.packet_hashlist = umsgpack.unpackb(file.read())
except Exception as e: file.close()
RNS.log("Could not load packet hashlist from storage, the contained exception was: "+str(e), RNS.LOG_ERROR) 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 # Create transport-specific destinations
Transport.path_request_destination = RNS.Destination(None, RNS.Destination.IN, RNS.Destination.PLAIN, Transport.APP_NAME, "path", "request") Transport.path_request_destination = RNS.Destination(None, RNS.Destination.IN, RNS.Destination.PLAIN, Transport.APP_NAME, "path", "request")
@@ -284,6 +287,7 @@ class Transport:
@staticmethod @staticmethod
def jobs(): def jobs():
outgoing = [] outgoing = []
path_requests = []
Transport.jobs_running = True Transport.jobs_running = True
try: try:
@@ -379,8 +383,24 @@ class Transport:
stale_links = [] stale_links = []
for link_id in Transport.link_table: for link_id in Transport.link_table:
link_entry = Transport.link_table[link_id] link_entry = Transport.link_table[link_id]
if time.time() > link_entry[0] + Transport.LINK_TIMEOUT: if link_entry[7] == True:
stale_links.append(link_id) 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 this instance
# or a local client, attempt to rediscover a path to the
# destination, if it 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 link was never established", RNS.LOG_DEBUG)
path_requests.append(link_entry[6])
# Cull the path table # Cull the path table
stale_paths = [] stale_paths = []
@@ -512,6 +532,9 @@ class Transport:
for packet in outgoing: for packet in outgoing:
packet.send() packet.send()
for destination_hash in path_requests:
Transport.request_path(destination_hash)
@staticmethod @staticmethod
def transmit(interface, raw): def transmit(interface, raw):
try: try:
@@ -833,7 +856,7 @@ class Transport:
def inbound(raw, interface=None): def inbound(raw, interface=None):
# If interface access codes are enabled, # If interface access codes are enabled,
# we must authenticate each packet. # 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: if interface != None and hasattr(interface, "ifac_identity") and interface.ifac_identity != None:
# Check that IFAC flag is set # Check that IFAC flag is set
if raw[0] & 0x80 == 0x80: if raw[0] & 0x80 == 0x80:
@@ -871,6 +894,9 @@ class Transport:
# If the flag is set, drop the packet # If the flag is set, drop the packet
return return
else:
return
while (Transport.jobs_running): while (Transport.jobs_running):
sleep(0.0005) sleep(0.0005)
@@ -994,15 +1020,19 @@ class Transport:
outbound_interface = Transport.destination_table[packet.destination_hash][5] outbound_interface = Transport.destination_table[packet.destination_hash][5]
if packet.packet_type == RNS.Packet.LINKREQUEST: 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 # Entry format is
link_entry = [ time.time(), # 0: Timestamp, link_entry = [ now, # 0: Timestamp,
next_hop, # 1: Next-hop transport ID next_hop, # 1: Next-hop transport ID
outbound_interface, # 2: Next-hop interface outbound_interface, # 2: Next-hop interface
remaining_hops, # 3: Remaining hops remaining_hops, # 3: Remaining hops
packet.receiving_interface, # 4: Received on interface packet.receiving_interface, # 4: Received on interface
packet.hops, # 5: Taken hops packet.hops, # 5: Taken hops
packet.destination_hash, # 6: Original destination hash 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 Transport.link_table[packet.getTruncatedHash()] = link_entry
@@ -1202,9 +1232,9 @@ class Transport:
retransmit_timeout = now + (RNS.rand() * Transport.PATHFINDER_RW) 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 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 expires = now + Transport.ROAMING_PATH_TIME
else: else:
expires = now + Transport.PATHFINDER_E expires = now + Transport.PATHFINDER_E
@@ -1377,10 +1407,11 @@ class Transport:
# Handling for linkrequests to local destinations # Handling for linkrequests to local destinations
elif packet.packet_type == RNS.Packet.LINKREQUEST: elif packet.packet_type == RNS.Packet.LINKREQUEST:
for destination in Transport.destinations: if packet.transport_id == None or packet.transport_id == Transport.identity.hash:
if destination.hash == packet.destination_hash and destination.type == packet.destination_type: for destination in Transport.destinations:
packet.destination = destination if destination.hash == packet.destination_hash and destination.type == packet.destination_type:
destination.receive(packet) packet.destination = destination
destination.receive(packet)
# Handling for local data packets # Handling for local data packets
elif packet.packet_type == RNS.Packet.DATA: elif packet.packet_type == RNS.Packet.DATA:
@@ -1414,14 +1445,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: 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] link_entry = Transport.link_table[packet.destination_hash]
if packet.receiving_interface == link_entry[2]: if packet.receiving_interface == link_entry[2]:
# TODO: Should we validate the LR proof at each transport try:
# step before transporting it? if len(packet.data) == RNS.Identity.SIGLENGTH//8+RNS.Link.ECPUBSIZE//2:
# RNS.log("Link request proof received on correct interface, transporting it via "+str(link_entry[4]), RNS.LOG_EXTREME) peer_pub_bytes = packet.data[RNS.Identity.SIGLENGTH//8:RNS.Identity.SIGLENGTH//8+RNS.Link.ECPUBSIZE//2]
new_raw = packet.raw[0:1] peer_identity = RNS.Identity.recall(link_entry[6])
new_raw += struct.pack("!B", packet.hops) peer_sig_pub_bytes = peer_identity.get_public_key()[RNS.Link.ECPUBSIZE//2:RNS.Link.ECPUBSIZE]
new_raw += packet.raw[2:]
Transport.link_table[packet.destination_hash][7] = True signed_data = packet.destination_hash+peer_pub_bytes+peer_sig_pub_bytes
Transport.transmit(link_entry[4], new_raw) 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: else:
RNS.log("Link request proof received on wrong interface, not transporting it.", RNS.LOG_DEBUG) RNS.log("Link request proof received on wrong interface, not transporting it.", RNS.LOG_DEBUG)
else: else:
@@ -1825,6 +1871,7 @@ class Transport:
on_interface.announce_allowed_at = now + wait_time on_interface.announce_allowed_at = now + wait_time
packet.send() packet.send()
Transport.path_requests[destination_hash] = time.time()
@staticmethod @staticmethod
def path_request_handler(data, packet): def path_request_handler(data, packet):
@@ -1901,10 +1948,9 @@ class Transport:
local_destination = next((d for d in Transport.destinations if d.hash == destination_hash), None) local_destination = next((d for d in Transport.destinations if d.hash == destination_hash), None)
if local_destination != 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) 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): elif (RNS.Reticulum.transport_enabled() or is_from_local_client) and (destination_hash in Transport.destination_table):
packet = Transport.destination_table[destination_hash][6] packet = Transport.destination_table[destination_hash][6]
next_hop = Transport.destination_table[destination_hash][1] next_hop = Transport.destination_table[destination_hash][1]
@@ -1949,9 +1995,10 @@ class Transport:
# Forward path request on all interfaces # Forward path request on all interfaces
# except the local client # 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) 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: for interface in Transport.interfaces:
if not interface == attached_interface: 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: elif should_search_for_unknown:
if destination_hash in Transport.discovery_path_requests: if destination_hash in Transport.discovery_path_requests:
@@ -2078,41 +2125,42 @@ class Transport:
@staticmethod @staticmethod
def save_packet_hashlist(): def save_packet_hashlist():
if hasattr(Transport, "saving_packet_hashlist"): if not Transport.owner.is_connected_to_shared_instance:
wait_interval = 0.2 if hasattr(Transport, "saving_packet_hashlist"):
wait_timeout = 5 wait_interval = 0.2
wait_start = time.time() wait_timeout = 5
while Transport.saving_packet_hashlist: wait_start = time.time()
time.sleep(wait_interval) while Transport.saving_packet_hashlist:
if time.time() > wait_start+wait_timeout: time.sleep(wait_interval)
RNS.log("Could not save packet hashlist to storage, waiting for previous save operation timed out.", RNS.LOG_ERROR) if time.time() > wait_start+wait_timeout:
return False RNS.log("Could not save packet hashlist to storage, waiting for previous save operation timed out.", RNS.LOG_ERROR)
return False
try: try:
Transport.saving_packet_hashlist = True Transport.saving_packet_hashlist = True
save_start = time.time() save_start = time.time()
if not RNS.Reticulum.transport_enabled(): if not RNS.Reticulum.transport_enabled():
Transport.packet_hashlist = [] Transport.packet_hashlist = []
else: else:
RNS.log("Saving packet hashlist to storage...", RNS.LOG_DEBUG) RNS.log("Saving packet hashlist to storage...", RNS.LOG_DEBUG)
packet_hashlist_path = RNS.Reticulum.storagepath+"/packet_hashlist" packet_hashlist_path = RNS.Reticulum.storagepath+"/packet_hashlist"
file = open(packet_hashlist_path, "wb") file = open(packet_hashlist_path, "wb")
file.write(umsgpack.packb(Transport.packet_hashlist)) file.write(umsgpack.packb(Transport.packet_hashlist))
file.close() file.close()
save_time = time.time() - save_start save_time = time.time() - save_start
if save_time < 1: if save_time < 1:
time_str = str(round(save_time*1000,2))+"ms" time_str = str(round(save_time*1000,2))+"ms"
else: else:
time_str = str(round(save_time,2))+"s" time_str = str(round(save_time,2))+"s"
RNS.log("Saved packet hashlist in "+time_str, RNS.LOG_DEBUG) RNS.log("Saved packet hashlist in "+time_str, RNS.LOG_DEBUG)
except Exception as e: except Exception as e:
RNS.log("Could not save packet hashlist to storage, the contained exception was: "+str(e), RNS.LOG_ERROR) 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 @staticmethod
File diff suppressed because it is too large Load Diff
+3
View File
@@ -135,6 +135,9 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None):
if "peers" in ifstat and ifstat["peers"] != None: if "peers" in ifstat and ifstat["peers"] != None:
print(" Peers : {np} reachable".format(np=ifstat["peers"])) print(" Peers : {np} reachable".format(np=ifstat["peers"]))
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: if "ifac_signature" in ifstat and ifstat["ifac_signature"] != None:
sigstr = "<…"+RNS.hexrep(ifstat["ifac_signature"][-5:], delimit=False)+">" sigstr = "<…"+RNS.hexrep(ifstat["ifac_signature"][-5:], delimit=False)+">"
print(" Access : {nb}-bit IFAC by {sig}".format(nb=ifstat["ifac_size"]*8, sig=sigstr)) print(" Access : {nb}-bit IFAC by {sig}".format(nb=ifstat["ifac_size"]*8, sig=sigstr))
+15 -7
View File
@@ -57,10 +57,11 @@ LOG_FILE = 0x92
LOG_MAXSIZE = 5*1024*1024 LOG_MAXSIZE = 5*1024*1024
loglevel = LOG_NOTICE loglevel = LOG_NOTICE
logfile = None logfile = None
logdest = LOG_STDOUT logdest = LOG_STDOUT
logtimefmt = "%Y-%m-%d %H:%M:%S" logtimefmt = "%Y-%m-%d %H:%M:%S"
compact_log_fmt = False
instance_random = random.Random() instance_random = random.Random()
instance_random.seed(os.urandom(10)) instance_random.seed(os.urandom(10))
@@ -101,10 +102,14 @@ def timestamp_str(time_s):
return time.strftime(logtimefmt, timestamp) return time.strftime(logtimefmt, timestamp)
def log(msg, level=3, _override_destination = False): def log(msg, level=3, _override_destination = False):
global _always_override_destination global _always_override_destination, compact_log_fmt
if loglevel >= level: 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() logging_lock.acquire()
if (logdest == LOG_STDOUT or _always_override_destination or _override_destination): if (logdest == LOG_STDOUT or _always_override_destination or _override_destination):
@@ -212,7 +217,10 @@ def prettytime(time, verbose=False):
tstr += c tstr += c
return tstr if tstr == "":
return "0s"
else:
return tstr
def phyparams(): def phyparams():
print("Required Physical Layer MTU : "+str(Reticulum.MTU)+" bytes") print("Required Physical Layer MTU : "+str(Reticulum.MTU)+" bytes")
+1 -1
View File
@@ -1 +1 @@
__version__ = "0.3.15" __version__ = "0.4.4"
+106
View File
@@ -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 -1
View File
@@ -1,4 +1,4 @@
# Sphinx build info version 1 # 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. # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
config: 219ffd660ee191f2dd34df826586d2a3 config: d8e478ae4e8ed171147d8a9f3611f9c3
tags: 645f666f9bcd5a90fca523b33c5a78b7 tags: 645f666f9bcd5a90fca523b33c5a78b7
+5 -9
View File
@@ -160,12 +160,13 @@ Installation
Once you have obtained compatible boards, you can install the `RNode Firmware <https://github.com/markqvist/RNode_Firmware>`_ 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>`_. using the `RNode Configuration Utility <https://github.com/markqvist/rnodeconfigutil>`_.
Make sure that ``Python3`` and ``pip`` is installed on your system, and then install If you have installed Reticulum on your system, the ``rnodeconf`` program will already be
the config utility with ``pip``: available. If not, make sure that ``Python3`` and ``pip`` is installed on your system, and
then install Reticulum with with ``pip``:
.. code:: .. code::
pip3 install rnodeconf pip3 install rns
Once installation has completed, it is time to start installing the firmware on your Once installation has completed, it is time to start installing the firmware on your
devices. Run ``rnodeconf`` in auto-install mode like so: 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 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 questions about your hardware. Simply follow the guide, and the utility will
auto-install and configure your devices 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.
.. _rnode-usage: .. _rnode-usage:
+2 -2
View File
@@ -771,7 +771,7 @@ Wire Format
| | | | | | | | | | | | | | | |
00000000 00000111 [HASH1, 16 bytes] [CONTEXT, 1 byte] [DATA] 00000000 00000111 [HASH1, 16 bytes] [CONTEXT, 1 byte] [DATA]
|| | | | | || | | | |
|| | | | +-- Hops = 0 || | | | +-- Hops = 7
|| | | +------- Packet Type = DATA || | | +------- Packet Type = DATA
|| | +--------- Destination Type = SINGLE || | +--------- Destination Type = SINGLE
|| +----------- Propagation Type = BROADCAST || +----------- Propagation Type = BROADCAST
@@ -786,7 +786,7 @@ Wire Format
| | | | | | | | | | | | | | | | | | | |
10000000 00000111 [IFAC, N bytes] [HASH1, 16 bytes] [CONTEXT, 1 byte] [DATA] 10000000 00000111 [IFAC, N bytes] [HASH1, 16 bytes] [CONTEXT, 1 byte] [DATA]
|| | | | | || | | | |
|| | | | +-- Hops = 0 || | | | +-- Hops = 7
|| | | +------- Packet Type = DATA || | | +------- Packet Type = DATA
|| | +--------- Destination Type = SINGLE || | +--------- Destination Type = SINGLE
|| +----------- Propagation Type = BROADCAST || +----------- Propagation Type = BROADCAST
+1 -1
View File
@@ -1,6 +1,6 @@
var DOCUMENTATION_OPTIONS = { var DOCUMENTATION_OPTIONS = {
URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
VERSION: '0.3.15 beta', VERSION: '0.4.4 beta',
LANGUAGE: 'en', LANGUAGE: 'en',
COLLAPSE_INDEX: false, COLLAPSE_INDEX: false,
BUILDER: 'html', BUILDER: 'html',
+3 -3
View File
@@ -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" /> <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"/> <meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
<title>Code Examples - Reticulum Network Stack 0.3.15 beta documentation</title> <title>Code Examples - Reticulum Network Stack 0.4.4 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" /> <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/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" /> <link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -141,7 +141,7 @@
</label> </label>
</div> </div>
<div class="header-center"> <div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.3.15 beta documentation</div></a> <a href="index.html"><div class="brand">Reticulum Network Stack 0.4.4 beta documentation</div></a>
</div> </div>
<div class="header-right"> <div class="header-right">
<div class="theme-toggle-container theme-toggle-header"> <div class="theme-toggle-container theme-toggle-header">
@@ -167,7 +167,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/> <img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div> </div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.3.15 beta documentation</span> <span class="sidebar-brand-text">Reticulum Network Stack 0.4.4 beta documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search"> </a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search"> <input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
+3 -3
View File
@@ -4,7 +4,7 @@
<meta name="viewport" content="width=device-width,initial-scale=1"/> <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="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.15 beta documentation</title> <meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/><title>Index - Reticulum Network Stack 0.4.4 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" /> <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/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" /> <link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -139,7 +139,7 @@
</label> </label>
</div> </div>
<div class="header-center"> <div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.3.15 beta documentation</div></a> <a href="index.html"><div class="brand">Reticulum Network Stack 0.4.4 beta documentation</div></a>
</div> </div>
<div class="header-right"> <div class="header-right">
<div class="theme-toggle-container theme-toggle-header"> <div class="theme-toggle-container theme-toggle-header">
@@ -165,7 +165,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/> <img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div> </div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.3.15 beta documentation</span> <span class="sidebar-brand-text">Reticulum Network Stack 0.4.4 beta documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search"> </a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search"> <input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
+3 -3
View File
@@ -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" /> <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"/> <meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
<title>Getting Started Fast - Reticulum Network Stack 0.3.15 beta documentation</title> <title>Getting Started Fast - Reticulum Network Stack 0.4.4 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" /> <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/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" /> <link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -141,7 +141,7 @@
</label> </label>
</div> </div>
<div class="header-center"> <div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.3.15 beta documentation</div></a> <a href="index.html"><div class="brand">Reticulum Network Stack 0.4.4 beta documentation</div></a>
</div> </div>
<div class="header-right"> <div class="header-right">
<div class="theme-toggle-container theme-toggle-header"> <div class="theme-toggle-container theme-toggle-header">
@@ -167,7 +167,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/> <img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div> </div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.3.15 beta documentation</span> <span class="sidebar-brand-text">Reticulum Network Stack 0.4.4 beta documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search"> </a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search"> <input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
+8 -11
View File
@@ -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" /> <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"/> <meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
<title>Communications Hardware - Reticulum Network Stack 0.3.15 beta documentation</title> <title>Communications Hardware - Reticulum Network Stack 0.4.4 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" /> <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/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" /> <link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -141,7 +141,7 @@
</label> </label>
</div> </div>
<div class="header-center"> <div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.3.15 beta documentation</div></a> <a href="index.html"><div class="brand">Reticulum Network Stack 0.4.4 beta documentation</div></a>
</div> </div>
<div class="header-right"> <div class="header-right">
<div class="theme-toggle-container theme-toggle-header"> <div class="theme-toggle-container theme-toggle-header">
@@ -167,7 +167,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/> <img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div> </div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.3.15 beta documentation</span> <span class="sidebar-brand-text">Reticulum Network Stack 0.4.4 beta documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search"> </a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="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> <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> <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>. 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 If you have installed Reticulum on your system, the <code class="docutils literal notranslate"><span class="pre">rnodeconf</span></code> program will already be
the config utility with <code class="docutils literal notranslate"><span class="pre">pip</span></code>:</p> 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
<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> 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> </pre></div>
</div> </div>
<p>Once installation has completed, it is time to start installing the firmware on your <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> </div>
<p>The utility will guide you through the installation process by asking a series of <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 questions about your hardware. Simply follow the guide, and the utility will
auto-install and configure your devices</p> 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 dont care about stability.</p>
</section> </section>
<section id="usage-with-reticulum"> <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> <span id="rnode-usage"></span><h3>Usage with Reticulum<a class="headerlink" href="#usage-with-reticulum" title="Permalink to this heading">#</a></h3>
+3 -3
View File
@@ -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" /> <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"/> <meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
<title>Reticulum Network Stack 0.3.15 beta documentation</title> <title>Reticulum Network Stack 0.4.4 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" /> <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/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" /> <link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -141,7 +141,7 @@
</label> </label>
</div> </div>
<div class="header-center"> <div class="header-center">
<a href="#"><div class="brand">Reticulum Network Stack 0.3.15 beta documentation</div></a> <a href="#"><div class="brand">Reticulum Network Stack 0.4.4 beta documentation</div></a>
</div> </div>
<div class="header-right"> <div class="header-right">
<div class="theme-toggle-container theme-toggle-header"> <div class="theme-toggle-container theme-toggle-header">
@@ -167,7 +167,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/> <img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div> </div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.3.15 beta documentation</span> <span class="sidebar-brand-text">Reticulum Network Stack 0.4.4 beta documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search"> </a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search"> <input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
+3 -3
View File
@@ -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" /> <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"/> <meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
<title>Supported Interfaces - Reticulum Network Stack 0.3.15 beta documentation</title> <title>Supported Interfaces - Reticulum Network Stack 0.4.4 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" /> <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/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" /> <link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -141,7 +141,7 @@
</label> </label>
</div> </div>
<div class="header-center"> <div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.3.15 beta documentation</div></a> <a href="index.html"><div class="brand">Reticulum Network Stack 0.4.4 beta documentation</div></a>
</div> </div>
<div class="header-right"> <div class="header-right">
<div class="theme-toggle-container theme-toggle-header"> <div class="theme-toggle-container theme-toggle-header">
@@ -167,7 +167,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/> <img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div> </div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.3.15 beta documentation</span> <span class="sidebar-brand-text">Reticulum Network Stack 0.4.4 beta documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search"> </a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search"> <input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
+3 -3
View File
@@ -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" /> <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"/> <meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
<title>Building Networks - Reticulum Network Stack 0.3.15 beta documentation</title> <title>Building Networks - Reticulum Network Stack 0.4.4 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" /> <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/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" /> <link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -141,7 +141,7 @@
</label> </label>
</div> </div>
<div class="header-center"> <div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.3.15 beta documentation</div></a> <a href="index.html"><div class="brand">Reticulum Network Stack 0.4.4 beta documentation</div></a>
</div> </div>
<div class="header-right"> <div class="header-right">
<div class="theme-toggle-container theme-toggle-header"> <div class="theme-toggle-container theme-toggle-header">
@@ -167,7 +167,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/> <img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div> </div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.3.15 beta documentation</span> <span class="sidebar-brand-text">Reticulum Network Stack 0.4.4 beta documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search"> </a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search"> <input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
+4 -4
View File
@@ -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" /> <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"/> <meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
<title>API Reference - Reticulum Network Stack 0.3.15 beta documentation</title> <title>API Reference - Reticulum Network Stack 0.4.4 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" /> <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/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" /> <link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -141,7 +141,7 @@
</label> </label>
</div> </div>
<div class="header-center"> <div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.3.15 beta documentation</div></a> <a href="index.html"><div class="brand">Reticulum Network Stack 0.4.4 beta documentation</div></a>
</div> </div>
<div class="header-right"> <div class="header-right">
<div class="theme-toggle-container theme-toggle-header"> <div class="theme-toggle-container theme-toggle-header">
@@ -167,7 +167,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/> <img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div> </div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.3.15 beta documentation</span> <span class="sidebar-brand-text">Reticulum Network Stack 0.4.4 beta documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search"> </a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="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"> <dl class="py method">
<dt class="sig sig-object py" id="RNS.Destination.announce"> <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 <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> relevant interfaces. Application specific data can be added to the announce.</p>
<dl class="field-list simple"> <dl class="field-list simple">
+3 -3
View File
@@ -4,7 +4,7 @@
<meta name="viewport" content="width=device-width,initial-scale=1"/> <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="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.15 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.4 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/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" /> <link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?digest=30d1aed668e5c3a91c3e3bf6a60b675221979f0e" /> <link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?digest=30d1aed668e5c3a91c3e3bf6a60b675221979f0e" />
@@ -138,7 +138,7 @@
</label> </label>
</div> </div>
<div class="header-center"> <div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.3.15 beta documentation</div></a> <a href="index.html"><div class="brand">Reticulum Network Stack 0.4.4 beta documentation</div></a>
</div> </div>
<div class="header-right"> <div class="header-right">
<div class="theme-toggle-container theme-toggle-header"> <div class="theme-toggle-container theme-toggle-header">
@@ -164,7 +164,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/> <img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div> </div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.3.15 beta documentation</span> <span class="sidebar-brand-text">Reticulum Network Stack 0.4.4 beta documentation</span>
</a><form class="sidebar-search-container" method="get" action="#" role="search"> </a><form class="sidebar-search-container" method="get" action="#" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search"> <input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
File diff suppressed because one or more lines are too long
+3 -3
View File
@@ -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" /> <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"/> <meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
<title>Support Reticulum - Reticulum Network Stack 0.3.15 beta documentation</title> <title>Support Reticulum - Reticulum Network Stack 0.4.4 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" /> <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/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" /> <link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -141,7 +141,7 @@
</label> </label>
</div> </div>
<div class="header-center"> <div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.3.15 beta documentation</div></a> <a href="index.html"><div class="brand">Reticulum Network Stack 0.4.4 beta documentation</div></a>
</div> </div>
<div class="header-right"> <div class="header-right">
<div class="theme-toggle-container theme-toggle-header"> <div class="theme-toggle-container theme-toggle-header">
@@ -167,7 +167,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/> <img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div> </div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.3.15 beta documentation</span> <span class="sidebar-brand-text">Reticulum Network Stack 0.4.4 beta documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search"> </a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search"> <input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
+5 -5
View File
@@ -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" /> <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"/> <meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
<title>Understanding Reticulum - Reticulum Network Stack 0.3.15 beta documentation</title> <title>Understanding Reticulum - Reticulum Network Stack 0.4.4 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" /> <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/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" /> <link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -141,7 +141,7 @@
</label> </label>
</div> </div>
<div class="header-center"> <div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.3.15 beta documentation</div></a> <a href="index.html"><div class="brand">Reticulum Network Stack 0.4.4 beta documentation</div></a>
</div> </div>
<div class="header-right"> <div class="header-right">
<div class="theme-toggle-container theme-toggle-header"> <div class="theme-toggle-container theme-toggle-header">
@@ -167,7 +167,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/> <img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div> </div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.3.15 beta documentation</span> <span class="sidebar-brand-text">Reticulum Network Stack 0.4.4 beta documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search"> </a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="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] 00000000 00000111 [HASH1, 16 bytes] [CONTEXT, 1 byte] [DATA]
|| | | | | || | | | |
|| | | | +-- Hops = 0 || | | | +-- Hops = 7
|| | | +------- Packet Type = DATA || | | +------- Packet Type = DATA
|| | +--------- Destination Type = SINGLE || | +--------- Destination Type = SINGLE
|| +----------- Propagation Type = BROADCAST || +----------- Propagation Type = BROADCAST
@@ -1020,7 +1020,7 @@ proof 11
| | | | | | | | | | | | | | | | | | | |
10000000 00000111 [IFAC, N bytes] [HASH1, 16 bytes] [CONTEXT, 1 byte] [DATA] 10000000 00000111 [IFAC, N bytes] [HASH1, 16 bytes] [CONTEXT, 1 byte] [DATA]
|| | | | | || | | | |
|| | | | +-- Hops = 0 || | | | +-- Hops = 7
|| | | +------- Packet Type = DATA || | | +------- Packet Type = DATA
|| | +--------- Destination Type = SINGLE || | +--------- Destination Type = SINGLE
|| +----------- Propagation Type = BROADCAST || +----------- Propagation Type = BROADCAST
+3 -3
View File
@@ -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" /> <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"/> <meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
<title>Using Reticulum on Your System - Reticulum Network Stack 0.3.15 beta documentation</title> <title>Using Reticulum on Your System - Reticulum Network Stack 0.4.4 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" /> <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/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" /> <link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -141,7 +141,7 @@
</label> </label>
</div> </div>
<div class="header-center"> <div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.3.15 beta documentation</div></a> <a href="index.html"><div class="brand">Reticulum Network Stack 0.4.4 beta documentation</div></a>
</div> </div>
<div class="header-right"> <div class="header-right">
<div class="theme-toggle-container theme-toggle-header"> <div class="theme-toggle-container theme-toggle-header">
@@ -167,7 +167,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/> <img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div> </div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.3.15 beta documentation</span> <span class="sidebar-brand-text">Reticulum Network Stack 0.4.4 beta documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search"> </a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search"> <input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
+3 -3
View File
@@ -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" /> <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"/> <meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
<title>What is Reticulum? - Reticulum Network Stack 0.3.15 beta documentation</title> <title>What is Reticulum? - Reticulum Network Stack 0.4.4 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" /> <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/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" /> <link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -141,7 +141,7 @@
</label> </label>
</div> </div>
<div class="header-center"> <div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.3.15 beta documentation</div></a> <a href="index.html"><div class="brand">Reticulum Network Stack 0.4.4 beta documentation</div></a>
</div> </div>
<div class="header-right"> <div class="header-right">
<div class="theme-toggle-container theme-toggle-header"> <div class="theme-toggle-container theme-toggle-header">
@@ -167,7 +167,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/> <img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div> </div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.3.15 beta documentation</span> <span class="sidebar-brand-text">Reticulum Network Stack 0.4.4 beta documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search"> </a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search"> <input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
+5 -9
View File
@@ -160,12 +160,13 @@ Installation
Once you have obtained compatible boards, you can install the `RNode Firmware <https://github.com/markqvist/RNode_Firmware>`_ 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>`_. using the `RNode Configuration Utility <https://github.com/markqvist/rnodeconfigutil>`_.
Make sure that ``Python3`` and ``pip`` is installed on your system, and then install If you have installed Reticulum on your system, the ``rnodeconf`` program will already be
the config utility with ``pip``: available. If not, make sure that ``Python3`` and ``pip`` is installed on your system, and
then install Reticulum with with ``pip``:
.. code:: .. code::
pip3 install rnodeconf pip3 install rns
Once installation has completed, it is time to start installing the firmware on your Once installation has completed, it is time to start installing the firmware on your
devices. Run ``rnodeconf`` in auto-install mode like so: 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 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 questions about your hardware. Simply follow the guide, and the utility will
auto-install and configure your devices 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.
.. _rnode-usage: .. _rnode-usage:
+2 -2
View File
@@ -771,7 +771,7 @@ Wire Format
| | | | | | | | | | | | | | | |
00000000 00000111 [HASH1, 16 bytes] [CONTEXT, 1 byte] [DATA] 00000000 00000111 [HASH1, 16 bytes] [CONTEXT, 1 byte] [DATA]
|| | | | | || | | | |
|| | | | +-- Hops = 0 || | | | +-- Hops = 7
|| | | +------- Packet Type = DATA || | | +------- Packet Type = DATA
|| | +--------- Destination Type = SINGLE || | +--------- Destination Type = SINGLE
|| +----------- Propagation Type = BROADCAST || +----------- Propagation Type = BROADCAST
@@ -786,7 +786,7 @@ Wire Format
| | | | | | | | | | | | | | | | | | | |
10000000 00000111 [IFAC, N bytes] [HASH1, 16 bytes] [CONTEXT, 1 byte] [DATA] 10000000 00000111 [IFAC, N bytes] [HASH1, 16 bytes] [CONTEXT, 1 byte] [DATA]
|| | | | | || | | | |
|| | | | +-- Hops = 0 || | | | +-- Hops = 7
|| | | +------- Packet Type = DATA || | | +------- Packet Type = DATA
|| | +--------- Destination Type = SINGLE || | +--------- Destination Type = SINGLE
|| +----------- Propagation Type = BROADCAST || +----------- Propagation Type = BROADCAST
+1
View File
@@ -45,6 +45,7 @@ setuptools.setup(
'rnpath=RNS.Utilities.rnpath:main', 'rnpath=RNS.Utilities.rnpath:main',
'rncp=RNS.Utilities.rncp:main', 'rncp=RNS.Utilities.rncp:main',
'rnx=RNS.Utilities.rnx:main', 'rnx=RNS.Utilities.rnx:main',
'rnodeconf=RNS.Utilities.rnodeconf:main',
] ]
}, },
install_requires=requirements, install_requires=requirements,