Compare commits

...

11 Commits

Author SHA1 Message Date
markqvist 6b20a98adc Update README.md 2022-01-12 12:53:18 +01:00
Mark Qvist f3d04ba90f Improved AutoInterface handling on Android 2022-01-12 12:12:04 +01:00
Mark Qvist 1d2564cedb Interface import on Android 2022-01-12 12:02:00 +01:00
Mark Qvist bec8473695 Better Android detection 2022-01-12 11:50:03 +01:00
Mark Qvist 25620415a0 Updated platform utils 2022-01-12 11:18:24 +01:00
Mark Qvist b6df952995 Platform version check for Windows 2022-01-12 10:16:59 +01:00
Mark Qvist a72aaf12ca Platform version check for Windows 2022-01-12 10:07:44 +01:00
Mark Qvist b978a993b2 Version update 2022-01-11 03:07:34 +01:00
Mark Qvist 5ae00264e8 Preliminaly ESP32 support for RNodeInterface 2022-01-11 03:07:03 +01:00
Mark Qvist 5396b80e80 Updated example 2022-01-11 03:06:35 +01:00
Mark Qvist fdaa58a6fa Improved malformed packet detection 2022-01-11 03:06:16 +01:00
10 changed files with 164 additions and 50 deletions
+6
View File
@@ -1,6 +1,12 @@
########################################################## ##########################################################
# This RNS example demonstrates a simple speedtest # # This RNS example demonstrates a simple speedtest #
# program to measure link throughput. # # program to measure link throughput. #
# #
# The current configuration is suited for testing fast #
# links. If you want to measure slow links like LoRa or #
# packet radio, you must significantly lower the #
# data_cap variable, which defines how much data is sent #
# for each test. #
########################################################## ##########################################################
import os import os
+1
View File
@@ -9,6 +9,7 @@ Having no dependencies on traditional networking stacks free up overhead that ha
No kernel modules or drivers are required. Reticulum runs completely in userland, and can run on practically any system that runs Python 3. No kernel modules or drivers are required. Reticulum runs completely in userland, and can run on practically any system that runs Python 3.
## Read The Manual
The full documentation for Reticulum is available at [markqvist.github.io/Reticulum/manual/](https://markqvist.github.io/Reticulum/manual/). The full documentation for Reticulum is available at [markqvist.github.io/Reticulum/manual/](https://markqvist.github.io/Reticulum/manual/).
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)
+5 -2
View File
@@ -22,6 +22,7 @@ class AutoInterface(Interface):
PEERING_TIMEOUT = 6.0 PEERING_TIMEOUT = 6.0
DARWIN_IGNORE_IFS = ["awdl0", "llw0", "lo0", "en5"] DARWIN_IGNORE_IFS = ["awdl0", "llw0", "lo0", "en5"]
ANDROID_IGNORE_IFS = ["dummy0", "lo", "tun0"]
def __init__(self, owner, name, group_id=None, discovery_scope=None, discovery_port=None, data_port=None, allowed_interfaces=None, ignored_interfaces=None): def __init__(self, owner, name, group_id=None, discovery_scope=None, discovery_port=None, data_port=None, allowed_interfaces=None, ignored_interfaces=None):
import importlib import importlib
@@ -101,10 +102,12 @@ class AutoInterface(Interface):
suitable_interfaces = 0 suitable_interfaces = 0
for ifname in self.netifaces.interfaces(): for ifname in self.netifaces.interfaces():
if RNS.vendor.platformutils.get_platform() == "darwin" and ifname in AutoInterface.DARWIN_IGNORE_IFS: if RNS.vendor.platformutils.is_darwin() and ifname in AutoInterface.DARWIN_IGNORE_IFS and not ifname in self.allowed_interfaces:
RNS.log(str(self)+" skipping Darwin AWDL or tethering interface "+str(ifname), RNS.LOG_EXTREME) RNS.log(str(self)+" skipping Darwin AWDL or tethering interface "+str(ifname), RNS.LOG_EXTREME)
elif RNS.vendor.platformutils.get_platform() == "darwin" and ifname == "lo0": elif RNS.vendor.platformutils.is_darwin() and ifname == "lo0":
RNS.log(str(self)+" skipping Darwin loopback interface "+str(ifname), RNS.LOG_EXTREME) RNS.log(str(self)+" skipping Darwin loopback interface "+str(ifname), RNS.LOG_EXTREME)
elif RNS.vendor.platformutils.is_android() and ifname in AutoInterface.ANDROID_IGNORE_IFS and not ifname in self.allowed_interfaces:
RNS.log(str(self)+" skipping Android system interface "+str(ifname), RNS.LOG_EXTREME)
elif ifname in self.ignored_interfaces: elif ifname in self.ignored_interfaces:
RNS.log(str(self)+" ignoring disallowed interface "+str(ifname), RNS.LOG_EXTREME) RNS.log(str(self)+" ignoring disallowed interface "+str(ifname), RNS.LOG_EXTREME)
else: else:
+54 -1
View File
@@ -30,8 +30,11 @@ class KISS():
CMD_STAT_SNR = 0x24 CMD_STAT_SNR = 0x24
CMD_BLINK = 0x30 CMD_BLINK = 0x30
CMD_RANDOM = 0x40 CMD_RANDOM = 0x40
CMD_PLATFORM = 0x48
CMD_MCU = 0x49
CMD_FW_VERSION = 0x50 CMD_FW_VERSION = 0x50
CMD_ROM_READ = 0x51 CMD_ROM_READ = 0x51
CMD_RESET = 0x55
DETECT_REQ = 0x73 DETECT_REQ = 0x73
DETECT_RESP = 0x46 DETECT_RESP = 0x46
@@ -45,6 +48,9 @@ class KISS():
ERROR_TXFAILED = 0x02 ERROR_TXFAILED = 0x02
ERROR_EEPROM_LOCKED = 0x03 ERROR_EEPROM_LOCKED = 0x03
PLATFORM_AVR = 0x90
PLATFORM_ESP32 = 0x80
@staticmethod @staticmethod
def escape(data): def escape(data):
data = data.replace(bytes([0xdb]), bytes([0xdb, 0xdd])) data = data.replace(bytes([0xdb]), bytes([0xdb, 0xdd]))
@@ -101,6 +107,9 @@ class RNodeInterface(Interface):
self.cr = cr self.cr = cr
self.state = KISS.RADIO_STATE_OFF self.state = KISS.RADIO_STATE_OFF
self.bitrate = 0 self.bitrate = 0
self.platform = None
self.mcu = None
self.detected = False
self.last_id = 0 self.last_id = 0
self.first_tx = None self.first_tx = None
@@ -190,6 +199,17 @@ class RNodeInterface(Interface):
thread = threading.Thread(target=self.readLoop) thread = threading.Thread(target=self.readLoop)
thread.setDaemon(True) thread.setDaemon(True)
thread.start() thread.start()
self.detect()
sleep(0.1)
if not self.detected:
raise IOError("Could not detect device")
else:
if self.platform == KISS.PLATFORM_ESP32:
RNS.log("Resetting ESP32-based device before configuration...", RNS.LOG_VERBOSE)
self.hard_reset()
self.online = True self.online = True
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)
@@ -214,6 +234,19 @@ class RNodeInterface(Interface):
self.setCodingRate() self.setCodingRate()
self.setRadioState(KISS.RADIO_STATE_ON) self.setRadioState(KISS.RADIO_STATE_ON)
def detect(self):
kiss_command = bytes([KISS.FEND, KISS.CMD_DETECT, KISS.DETECT_REQ, KISS.FEND, KISS.CMD_FW_VERSION, 0x00, KISS.FEND, KISS.CMD_PLATFORM, 0x00, KISS.FEND, KISS.CMD_MCU, 0x00, KISS.FEND])
written = self.serial.write(kiss_command)
if written != len(kiss_command):
raise IOError("An IO error occurred while detecting hardware for "+self(str))
def hard_reset(self):
kiss_command = bytes([KISS.FEND, KISS.CMD_RESET, 0xf8, KISS.FEND])
written = self.serial.write(kiss_command)
if written != len(kiss_command):
raise IOError("An IO error occurred while restarting device")
sleep(2);
def setFrequency(self): def setFrequency(self):
c1 = self.frequency >> 24 c1 = self.frequency >> 24
c2 = self.frequency >> 16 & 0xFF c2 = self.frequency >> 16 & 0xFF
@@ -420,6 +453,11 @@ class RNodeInterface(Interface):
self.updateBitrate() self.updateBitrate()
elif (command == KISS.CMD_RADIO_STATE): elif (command == KISS.CMD_RADIO_STATE):
self.r_state = byte self.r_state = byte
# if self.r_state:
# RNS.log(str(self)+" Radio reporting state is online ("+RNS.hexrep([self.r_state])+")", RNS.LOG_DEBUG)
# else:
# RNS.log(str(self)+" Radio reporting state is offline ("+RNS.hexrep([self.r_state])+")", RNS.LOG_DEBUG)
elif (command == KISS.CMD_RADIO_LOCK): elif (command == KISS.CMD_RADIO_LOCK):
self.r_lock = byte self.r_lock = byte
elif (command == KISS.CMD_STAT_RX): elif (command == KISS.CMD_STAT_RX):
@@ -456,6 +494,10 @@ class RNodeInterface(Interface):
self.r_stat_snr = int.from_bytes(bytes([byte]), byteorder="big", signed=True) * 0.25 self.r_stat_snr = int.from_bytes(bytes([byte]), byteorder="big", signed=True) * 0.25
elif (command == KISS.CMD_RANDOM): elif (command == KISS.CMD_RANDOM):
self.r_random = byte self.r_random = byte
elif (command == KISS.CMD_PLATFORM):
self.platform = byte
elif (command == KISS.CMD_MCU):
self.mcu = byte
elif (command == KISS.CMD_ERROR): elif (command == KISS.CMD_ERROR):
if (byte == KISS.ERROR_INITRADIO): if (byte == KISS.ERROR_INITRADIO):
RNS.log(str(self)+" hardware initialisation error (code "+RNS.hexrep(byte)+")", RNS.LOG_ERROR) RNS.log(str(self)+" hardware initialisation error (code "+RNS.hexrep(byte)+")", RNS.LOG_ERROR)
@@ -463,8 +505,19 @@ class RNodeInterface(Interface):
RNS.log(str(self)+" hardware TX error (code "+RNS.hexrep(byte)+")", RNS.LOG_ERROR) RNS.log(str(self)+" hardware TX error (code "+RNS.hexrep(byte)+")", RNS.LOG_ERROR)
else: else:
RNS.log(str(self)+" hardware error (code "+RNS.hexrep(byte)+")", RNS.LOG_ERROR) RNS.log(str(self)+" hardware error (code "+RNS.hexrep(byte)+")", RNS.LOG_ERROR)
elif (command == KISS.CMD_RESET):
if (byte == 0xF8):
if self.platform == KISS.PLATFORM_ESP32:
if self.online:
RNS.log("Detected reset while device was online, reinitialising device...", RNS.LOG_ERROR)
raise IOError("ESP32 reset")
elif (command == KISS.CMD_READY): elif (command == KISS.CMD_READY):
self.process_queue() self.process_queue()
elif (command == KISS.CMD_DETECT):
if byte == KISS.DETECT_RESP:
self.detected = True
else:
self.detected = False
else: else:
time_since_last = int(time.time()*1000) - last_read_ms time_since_last = int(time.time()*1000) - last_read_ms
@@ -500,7 +553,7 @@ class RNodeInterface(Interface):
def reconnect_port(self): def reconnect_port(self):
while not self.online: while not self.online:
try: try:
time.sleep(5) time.sleep(3.5)
RNS.log("Attempting to reconnect serial port "+str(self.port)+" for "+str(self)+"...", RNS.LOG_VERBOSE) RNS.log("Attempting to reconnect serial port "+str(self.port)+" for "+str(self)+"...", RNS.LOG_VERBOSE)
self.open_port() self.open_port()
if self.serial.is_open: if self.serial.is_open:
+1 -1
View File
@@ -765,7 +765,7 @@ class Link:
return plaintext return plaintext
except Exception as e: except Exception as e:
RNS.log("Decryption failed on link "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR) RNS.log("Decryption failed on link "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
RNS.log(traceback.format_exc(), RNS.LOG_ERROR) # RNS.log(traceback.format_exc(), RNS.LOG_ERROR)
# TODO: Think long about implications here # TODO: Think long about implications here
# self.teardown() # self.teardown()
+24 -18
View File
@@ -185,27 +185,33 @@ class Packet:
def unpack(self): def unpack(self):
self.flags = self.raw[0] try:
self.hops = self.raw[1] self.flags = self.raw[0]
self.hops = self.raw[1]
self.header_type = (self.flags & 0b11000000) >> 6 self.header_type = (self.flags & 0b11000000) >> 6
self.transport_type = (self.flags & 0b00110000) >> 4 self.transport_type = (self.flags & 0b00110000) >> 4
self.destination_type = (self.flags & 0b00001100) >> 2 self.destination_type = (self.flags & 0b00001100) >> 2
self.packet_type = (self.flags & 0b00000011) self.packet_type = (self.flags & 0b00000011)
if self.header_type == Packet.HEADER_2: if self.header_type == Packet.HEADER_2:
self.transport_id = self.raw[2:12] self.transport_id = self.raw[2:12]
self.destination_hash = self.raw[12:22] self.destination_hash = self.raw[12:22]
self.context = ord(self.raw[22:23]) self.context = ord(self.raw[22:23])
self.data = self.raw[23:] self.data = self.raw[23:]
else: else:
self.transport_id = None self.transport_id = None
self.destination_hash = self.raw[2:12] self.destination_hash = self.raw[2:12]
self.context = ord(self.raw[12:13]) self.context = ord(self.raw[12:13])
self.data = self.raw[13:] self.data = self.raw[13:]
self.packed = False self.packed = False
self.update_hash() self.update_hash()
return True
except Exception as e:
RNS.log("Received malformed packet, dropping it. The contained exception was: "+str(e), RNS.LOG_EXTREME)
return False
def send(self): def send(self):
""" """
+35 -23
View File
@@ -1,8 +1,11 @@
from .vendor.platformutils import get_platform from .vendor.platformutils import get_platform
if get_platform() == "android": if get_platform() == "android":
# TODO: Selectively import Android-relevant interfaces from .Interfaces import Interface
pass from .Interfaces import LocalInterface
from .Interfaces import AutoInterface
from .Interfaces import TCPInterface
from .Interfaces import UDPInterface
else: else:
from .Interfaces import * from .Interfaces import *
@@ -116,6 +119,8 @@ class Reticulum:
:param configdir: Full path to a Reticulum configuration directory. :param configdir: Full path to a Reticulum configuration directory.
""" """
RNS.vendor.platformutils.platform_checks()
if configdir != None: if configdir != None:
Reticulum.configdir = configdir Reticulum.configdir = configdir
@@ -276,30 +281,35 @@ class Reticulum:
try: try:
if ("interface_enabled" in c) and c.as_bool("interface_enabled") == True: if ("interface_enabled" in c) and c.as_bool("interface_enabled") == True:
if c["type"] == "AutoInterface": if c["type"] == "AutoInterface":
group_id = c["group_id"] if "group_id" in c else None if not RNS.vendor.platformutils.is_windows():
discovery_scope = c["discovery_scope"] if "discovery_scope" in c else None group_id = c["group_id"] if "group_id" in c else None
discovery_port = int(c["discovery_port"]) if "discovery_port" in c else None discovery_scope = c["discovery_scope"] if "discovery_scope" in c else None
data_port = int(c["data_port"]) if "data_port" in c else None discovery_port = int(c["discovery_port"]) if "discovery_port" in c else None
allowed_interfaces = c.as_list("devices") if "devices" in c else None data_port = int(c["data_port"]) if "data_port" in c else None
ignored_interfaces = c.as_list("ignored_devices") if "ignored_devices" in c else None allowed_interfaces = c.as_list("devices") if "devices" in c else None
ignored_interfaces = c.as_list("ignored_devices") if "ignored_devices" in c else None
interface = AutoInterface.AutoInterface( interface = AutoInterface.AutoInterface(
RNS.Transport, RNS.Transport,
name, name,
group_id, group_id,
discovery_scope, discovery_scope,
discovery_port, discovery_port,
data_port, data_port,
allowed_interfaces, allowed_interfaces,
ignored_interfaces ignored_interfaces
) )
if "outgoing" in c and c.as_bool("outgoing") == True: if "outgoing" in c and c.as_bool("outgoing") == True:
interface.OUT = True interface.OUT = True
else:
interface.OUT = False
RNS.Transport.interfaces.append(interface)
else: else:
interface.OUT = False RNS.log("AutoInterface is not currently supported on Windows, disabling interface.", RNS.LOG_ERROR);
RNS.log("Please remove this AutoInterface instance from your configuration file.", RNS.LOG_ERROR);
RNS.Transport.interfaces.append(interface) RNS.log("You will have to manually configure other interfaces for connectivity.", RNS.LOG_ERROR);
if c["type"] == "UDPInterface": if c["type"] == "UDPInterface":
@@ -531,6 +541,8 @@ class Reticulum:
except Exception as e: except Exception as e:
RNS.log("The interface \""+name+"\" could not be created. Check your configuration file for errors!", RNS.LOG_ERROR) RNS.log("The interface \""+name+"\" could not be created. Check your configuration file for errors!", 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)
# TODO: Remove
raise e
RNS.panic() RNS.panic()
else: else:
RNS.log("The interface name \""+name+"\" was already used. Check your configuration file for errors!", RNS.LOG_ERROR) RNS.log("The interface name \""+name+"\" was already used. Check your configuration file for errors!", RNS.LOG_ERROR)
+3 -1
View File
@@ -585,7 +585,9 @@ class Transport:
Transport.jobs_locked = True Transport.jobs_locked = True
packet = RNS.Packet(None, raw) packet = RNS.Packet(None, raw)
packet.unpack() if not packet.unpack():
return
packet.receiving_interface = interface packet.receiving_interface = interface
packet.hops += 1 packet.hops += 1
+1 -1
View File
@@ -1 +1 @@
__version__ = "0.3.0" __version__ = "0.3.1"
+33 -2
View File
@@ -1,7 +1,38 @@
def get_platform(): def get_platform():
from os import environ from os import environ
if 'ANDROID_ARGUMENT' in environ: if "ANDROID_ARGUMENT" in environ:
return 'android' return "android"
elif "ANDROID_ROOT" in environ:
return "android"
else: else:
import sys import sys
return sys.platform return sys.platform
def is_darwin():
if get_platform() == "darwin":
return True
else:
return False
def is_android():
if get_platform() == "android":
return True
else:
return False
def is_windows():
if str(get_platform()).startswith("win"):
return True
else:
return False
def platform_checks():
if is_windows():
import sys
if sys.version_info.major >= 3 and sys.version_info.minor >= 8:
pass
else:
import RNS
RNS.log("On Windows, Reticulum requires Python 3.8 or higher.", RNS.LOG_ERROR)
RNS.log("Please update Python to run Reticulum.", RNS.LOG_ERROR)
RNS.panic()