mirror of
https://github.com/markqvist/Reticulum.git
synced 2026-06-25 05:14:28 -07:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 843c1a77b7 | |||
| 459f6b792f | |||
| b61fa6ce8d | |||
| 11c741dacb | |||
| 24abb4cfa4 | |||
| 0d069bf1d8 | |||
| fd010fa80c | |||
| 8c2cfb0349 | |||
| 0140cd6eba |
@@ -3,6 +3,17 @@
|
||||
# server and client program. The server will serve a #
|
||||
# directory of files, and the clients can list and #
|
||||
# download files from the server. #
|
||||
# #
|
||||
# Please note that using RNS Resources for large file #
|
||||
# transfers is not recommended, since compression, #
|
||||
# encryption and hashmap sequencing can take a long time #
|
||||
# on systems with slow CPUs, which will probably result #
|
||||
# in the client timing out before the resource sender #
|
||||
# can complete preparing the resource. #
|
||||
# #
|
||||
# If you need to transfer large files, use the Bundle #
|
||||
# class instead, which will automatically slice the data #
|
||||
# into chunks suitable for packing as a Resource. #
|
||||
##########################################################
|
||||
|
||||
import os
|
||||
@@ -20,7 +31,7 @@ import RNS.vendor.umsgpack as umsgpack
|
||||
APP_NAME = "example_utilitites"
|
||||
|
||||
# We'll also define a default timeout, in seconds
|
||||
APP_TIMEOUT = 15.0
|
||||
APP_TIMEOUT = 45.0
|
||||
|
||||
##########################################################
|
||||
#### Server Part #########################################
|
||||
|
||||
+3
-3
@@ -5,15 +5,15 @@ Header Types
|
||||
type 1 00 Two byte header, one 10 byte address field
|
||||
type 2 01 Two byte header, two 10 byte address fields
|
||||
type 3 10 Reserved
|
||||
type 4 11 Reserved for extended header format
|
||||
type 4 11 Reserved
|
||||
|
||||
|
||||
Propagation Types
|
||||
-----------------
|
||||
broadcast 00
|
||||
transport 01
|
||||
relay 10
|
||||
tunnel 11
|
||||
reserved 10
|
||||
reserved 11
|
||||
|
||||
|
||||
Destination Types
|
||||
|
||||
@@ -68,9 +68,10 @@ class RNodeInterface(Interface):
|
||||
FREQ_MAX = 1020000000
|
||||
|
||||
RSSI_OFFSET = 157
|
||||
SNR_OFFSET = 128
|
||||
|
||||
def __init__(self, owner, name, port, frequency = None, bandwidth = None, txpower = None, sf = None, cr = None, flow_control = False):
|
||||
CALLSIGN_MAX_LEN = 32
|
||||
|
||||
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):
|
||||
self.serial = None
|
||||
self.owner = owner
|
||||
self.name = name
|
||||
@@ -90,6 +91,8 @@ class RNodeInterface(Interface):
|
||||
self.state = KISS.RADIO_STATE_OFF
|
||||
self.bitrate = 0
|
||||
|
||||
self.last_id = 0
|
||||
|
||||
self.r_frequency = None
|
||||
self.r_bandwidth = None
|
||||
self.r_txpower = None
|
||||
@@ -123,10 +126,22 @@ class RNodeInterface(Interface):
|
||||
RNS.log("Invalid spreading factor configured for "+str(self), RNS.LOG_ERROR)
|
||||
self.validcfg = False
|
||||
|
||||
if (self.cr < 5 or self.sf > 8):
|
||||
if (self.cr < 5 or self.cr > 8):
|
||||
RNS.log("Invalid coding rate configured for "+str(self), RNS.LOG_ERROR)
|
||||
self.validcfg = False
|
||||
|
||||
if id_interval != None and id_callsign != None:
|
||||
if (len(id_callsign.encode("utf-8")) <= RNodeInterface.CALLSIGN_MAX_LEN):
|
||||
self.should_id = True
|
||||
self.id_callsign = id_callsign
|
||||
self.id_interval = id_interval
|
||||
else:
|
||||
RNS.log("The encoded ID callsign for "+str(self)+" exceeds the max length of "+str(RNodeInterface.CALLSIGN_MAX_LEN)+" bytes.", RNS.LOG_ERROR)
|
||||
self.validcfg = False
|
||||
else:
|
||||
self.id_interval = None
|
||||
self.id_callsign = None
|
||||
|
||||
if (not self.validcfg):
|
||||
raise ValueError("The configuration for "+str(self)+" contains errors, interface is offline")
|
||||
|
||||
@@ -177,6 +192,7 @@ class RNodeInterface(Interface):
|
||||
self.setBandwidth()
|
||||
self.setTXPower()
|
||||
self.setSpreadingFactor()
|
||||
self.setCodingRate()
|
||||
self.setRadioState(KISS.RADIO_STATE_ON)
|
||||
|
||||
def setFrequency(self):
|
||||
@@ -205,7 +221,6 @@ class RNodeInterface(Interface):
|
||||
|
||||
def setTXPower(self):
|
||||
txp = bytes([self.txpower])
|
||||
|
||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_TXPOWER])+txp+bytes([KISS.FEND])
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
@@ -213,7 +228,6 @@ class RNodeInterface(Interface):
|
||||
|
||||
def setSpreadingFactor(self):
|
||||
sf = bytes([self.sf])
|
||||
|
||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_SF])+sf+bytes([KISS.FEND])
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
@@ -221,7 +235,6 @@ class RNodeInterface(Interface):
|
||||
|
||||
def setCodingRate(self):
|
||||
cr = bytes([self.cr])
|
||||
|
||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_CR])+cr+bytes([KISS.FEND])
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
@@ -257,9 +270,9 @@ class RNodeInterface(Interface):
|
||||
|
||||
def updateBitrate(self):
|
||||
try:
|
||||
self.bitrate = self.r_sf * ( (4.0/self.cr) / (math.pow(2,self.r_sf)/(self.r_bandwidth/1000)) ) * 1000
|
||||
self.bitrate = self.r_sf * ( (4.0/self.r_cr) / (math.pow(2,self.r_sf)/(self.r_bandwidth/1000)) ) * 1000
|
||||
self.bitrate_kbps = round(self.bitrate/1000.0, 2)
|
||||
RNS.log(str(self)+" On-air bitrate is now "+str(self.bitrate_kbps)+ " kbps", RNS.LOG_DEBUG)
|
||||
RNS.log(str(self)+" On-air bitrate is now "+str(self.bitrate_kbps)+ " kbps", RNS.LOG_INFO)
|
||||
except:
|
||||
self.bitrate = 0
|
||||
|
||||
@@ -273,9 +286,17 @@ class RNodeInterface(Interface):
|
||||
if self.flow_control:
|
||||
self.interface_ready = False
|
||||
|
||||
data = KISS.escape(data)
|
||||
frame = bytes([0xc0])+bytes([0x00])+data+bytes([0xc0])
|
||||
frame = b""
|
||||
|
||||
if self.id_interval != None and self.id_callsign != None:
|
||||
if self.last_id + self.id_interval < time.time():
|
||||
self.last_id = time.time()
|
||||
frame = bytes([0xc0])+bytes([0x00])+KISS.escape(self.id_callsign.encode("utf-8"))+bytes([0xc0])
|
||||
|
||||
data = KISS.escape(data)
|
||||
frame += bytes([0xc0])+bytes([0x00])+data+bytes([0xc0])
|
||||
written = self.serial.write(frame)
|
||||
|
||||
if written != len(frame):
|
||||
raise IOError("Serial interface only wrote "+str(written)+" bytes of "+str(len(data)))
|
||||
else:
|
||||
@@ -408,7 +429,7 @@ class RNodeInterface(Interface):
|
||||
elif (command == KISS.CMD_STAT_RSSI):
|
||||
self.r_stat_rssi = byte-RNodeInterface.RSSI_OFFSET
|
||||
elif (command == KISS.CMD_STAT_SNR):
|
||||
self.r_stat_snr = byte-RNodeInterface.SNR_OFFSET
|
||||
self.r_stat_snr = int.from_bytes(bytes([byte]), byteorder="big", signed=True) * 0.25
|
||||
elif (command == KISS.CMD_RANDOM):
|
||||
self.r_random = byte
|
||||
elif (command == KISS.CMD_ERROR):
|
||||
|
||||
@@ -11,10 +11,6 @@ class UdpInterface(Interface):
|
||||
def __init__(self, owner, name, bindip=None, bindport=None, forwardip=None, forwardport=None):
|
||||
self.IN = True
|
||||
self.OUT = False
|
||||
|
||||
# TODO: Optimise so this is not needed
|
||||
self.transmit_delay = 0.001
|
||||
|
||||
self.name = name
|
||||
|
||||
if (bindip != None and bindport != None):
|
||||
@@ -45,7 +41,6 @@ class UdpInterface(Interface):
|
||||
self.owner.inbound(data, self)
|
||||
|
||||
def processOutgoing(self,data):
|
||||
time.sleep(self.transmit_delay)
|
||||
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
||||
udp_socket.sendto(data, (self.forward_ip, self.forward_port))
|
||||
|
||||
+42
-18
@@ -15,13 +15,25 @@ class Resource:
|
||||
SDU = RNS.Packet.MDU
|
||||
RANDOM_HASH_SIZE = 4
|
||||
|
||||
# This is an indication of what the
|
||||
# maximum size a resource should be, if
|
||||
# it is to be handled within reasonable
|
||||
# time constraint, even on small systems.
|
||||
#
|
||||
# A small system in this regard is
|
||||
# defined as a Raspberry Pi, which should
|
||||
# be able to compress, encrypt and hasmap
|
||||
# the resource in about 10 seconds.
|
||||
MAX_EFFICIENT_SIZE = 16 * 1024 * 1024
|
||||
|
||||
# The maximum size to auto-compress with
|
||||
# bz2 before sending.
|
||||
AUTO_COMPRESS_MAX_SIZE = 32 * 1024 * 1024
|
||||
AUTO_COMPRESS_MAX_SIZE = MAX_EFFICIENT_SIZE
|
||||
|
||||
# TODO: Should be allocated more
|
||||
# intelligently
|
||||
MAX_RETRIES = 2
|
||||
# TODO: Set higher
|
||||
MAX_RETRIES = 5
|
||||
SENDER_GRACE_TIME = 10
|
||||
RETRY_GRACE_TIME = 0.25
|
||||
|
||||
@@ -72,6 +84,8 @@ class Resource:
|
||||
resource.hashmap_height = 0
|
||||
resource.waiting_for_hmu = False
|
||||
|
||||
resource.receiving_part = False
|
||||
|
||||
resource.consecutive_completed_height = 0
|
||||
|
||||
resource.link.register_incoming_resource(resource)
|
||||
@@ -344,14 +358,10 @@ class Resource:
|
||||
sleep(sleep_time)
|
||||
|
||||
def assemble(self):
|
||||
# TODO: Optimise assembly. It's way too
|
||||
# slow for larger files
|
||||
if not self.status == Resource.FAILED:
|
||||
try:
|
||||
self.status = Resource.ASSEMBLING
|
||||
stream = b""
|
||||
for part in self.parts:
|
||||
stream += part
|
||||
stream = b"".join(self.parts)
|
||||
|
||||
if self.encrypted:
|
||||
data = self.link.decrypt(stream)
|
||||
@@ -412,6 +422,10 @@ class Resource:
|
||||
|
||||
|
||||
def receive_part(self, packet):
|
||||
while self.receiving_part:
|
||||
sleep(0.001)
|
||||
|
||||
self.receiving_part = True
|
||||
self.last_activity = time.time()
|
||||
self.retries_left = self.max_retries
|
||||
|
||||
@@ -439,21 +453,25 @@ class Resource:
|
||||
self.outstanding_parts -= 1
|
||||
|
||||
# Update consecutive completed pointer
|
||||
if i == 0 or self.parts[i-1] != None and i == self.consecutive_completed_height:
|
||||
self.consecutive_completed_height = i+1
|
||||
cp = i+1
|
||||
while cp < len(self.parts) and self.parts[cp] != None:
|
||||
self.consecutive_completed_height = cp
|
||||
cp += 1
|
||||
if i == self.consecutive_completed_height + 1:
|
||||
self.consecutive_completed_height = i
|
||||
|
||||
cp = self.consecutive_completed_height + 1
|
||||
while cp < len(self.parts) and self.parts[cp] != None:
|
||||
self.consecutive_completed_height = cp
|
||||
cp += 1
|
||||
|
||||
i += 1
|
||||
|
||||
self.receiving_part = False
|
||||
|
||||
if self.__progress_callback != None:
|
||||
self.__progress_callback(self)
|
||||
|
||||
if self.outstanding_parts == 0 and self.received_count == self.total_parts:
|
||||
self.assemble()
|
||||
elif self.outstanding_parts == 0:
|
||||
# TODO: Figure out if ther is a mathematically
|
||||
# TODO: Figure out if there is a mathematically
|
||||
# optimal way to adjust windows
|
||||
if self.window < self.window_max:
|
||||
self.window += 1
|
||||
@@ -461,18 +479,25 @@ class Resource:
|
||||
self.window_min += 1
|
||||
|
||||
self.request_next()
|
||||
else:
|
||||
self.receiving_part = False
|
||||
|
||||
# Called on incoming resource to send a request for more data
|
||||
def request_next(self):
|
||||
while self.receiving_part:
|
||||
sleep(0.001)
|
||||
|
||||
if not self.status == Resource.FAILED:
|
||||
if not self.waiting_for_hmu:
|
||||
self.outstanding_parts = 0
|
||||
hashmap_exhausted = Resource.HASHMAP_IS_NOT_EXHAUSTED
|
||||
requested_hashes = b""
|
||||
|
||||
i = 0; pn = self.consecutive_completed_height
|
||||
for part in self.parts[self.consecutive_completed_height:self.consecutive_completed_height+self.window]:
|
||||
|
||||
offset = (1 if self.consecutive_completed_height > 0 else 0)
|
||||
i = 0; pn = self.consecutive_completed_height+offset
|
||||
search_start = pn
|
||||
|
||||
for part in self.parts[search_start:search_start+self.window]:
|
||||
if part == None:
|
||||
part_hash = self.hashmap[pn]
|
||||
if part_hash != None:
|
||||
@@ -622,7 +647,6 @@ class Resource:
|
||||
|
||||
|
||||
class ResourceAdvertisement:
|
||||
# TODO: Can this be allocated dynamically? Keep in mind hashmap_update inference
|
||||
HASHMAP_MAX_LEN = 84
|
||||
COLLISION_GUARD_SIZE = 2*Resource.WINDOW_MAX+HASHMAP_MAX_LEN
|
||||
|
||||
|
||||
+27
-11
@@ -248,8 +248,7 @@ class Reticulum:
|
||||
txtail = int(c["txtail"]) if "txtail" in c else None
|
||||
persistence = int(c["persistence"]) if "persistence" in c else None
|
||||
slottime = int(c["slottime"]) if "slottime" in c else None
|
||||
flow_control = (True if c["flow_control"] == "true" else False) if "flow_control" in c else False
|
||||
|
||||
flow_control = c.as_bool("flow_control") if "flow_control" in c else False
|
||||
port = c["port"] if "port" in c else None
|
||||
speed = int(c["speed"]) if "speed" in c else 9600
|
||||
databits = int(c["databits"]) if "databits" in c else 8
|
||||
@@ -286,8 +285,7 @@ class Reticulum:
|
||||
txtail = int(c["txtail"]) if "txtail" in c else None
|
||||
persistence = int(c["persistence"]) if "persistence" in c else None
|
||||
slottime = int(c["slottime"]) if "slottime" in c else None
|
||||
flow_control = (True if c["flow_control"] == "true" else False) if "flow_control" in c else False
|
||||
|
||||
flow_control = c.as_bool("flow_control") if "flow_control" in c else False
|
||||
port = c["port"] if "port" in c else None
|
||||
speed = int(c["speed"]) if "speed" in c else 9600
|
||||
databits = int(c["databits"]) if "databits" in c else 8
|
||||
@@ -330,7 +328,9 @@ class Reticulum:
|
||||
txpower = int(c["txpower"]) if "txpower" in c else None
|
||||
spreadingfactor = int(c["spreadingfactor"]) if "spreadingfactor" in c else None
|
||||
codingrate = int(c["codingrate"]) if "codingrate" in c else None
|
||||
flow_control = (True if c["flow_control"] == "true" else False) if "flow_control" in c else False
|
||||
flow_control = c.as_bool("flow_control") if "flow_control" in c else False
|
||||
id_interval = int(c["id_interval"]) if "id_interval" in c else None
|
||||
id_callsign = c["id_callsign"] if "id_callsign" in c else None
|
||||
|
||||
port = c["port"] if "port" in c else None
|
||||
|
||||
@@ -341,11 +341,14 @@ class Reticulum:
|
||||
RNS.Transport,
|
||||
name,
|
||||
port,
|
||||
frequency,
|
||||
bandwidth,
|
||||
txpower,
|
||||
spreadingfactor,
|
||||
flow_control
|
||||
frequency = frequency,
|
||||
bandwidth = bandwidth,
|
||||
txpower = txpower,
|
||||
sf = spreadingfactor,
|
||||
cr = codingrate,
|
||||
flow_control = flow_control,
|
||||
id_interval = id_interval,
|
||||
id_callsign = id_callsign
|
||||
)
|
||||
|
||||
if "outgoing" in c and c["outgoing"].lower() == "true":
|
||||
@@ -355,7 +358,7 @@ class Reticulum:
|
||||
|
||||
RNS.Transport.interfaces.append(interface)
|
||||
else:
|
||||
RNS.log("Skipping disabled interface \""+name+"\"", RNS.LOG_VERBOSE)
|
||||
RNS.log("Skipping disabled interface \""+name+"\"", RNS.LOG_NOTICE)
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("The interface \""+name+"\" could not be created. Check your configuration file for errors!", RNS.LOG_ERROR)
|
||||
@@ -533,6 +536,19 @@ loglevel = 4
|
||||
# fastest, and 8 the longest range.
|
||||
codingrate = 5
|
||||
|
||||
# You can configure the RNode to send
|
||||
# out identification on the channel with
|
||||
# a set interval by configuring the
|
||||
# following two parameters. The trans-
|
||||
# ceiver will only ID before making an
|
||||
# actual transmission, and if the set
|
||||
# interval has elapsed since it's last
|
||||
# ID. Interval is configured in seconds
|
||||
# This option is commented out and not
|
||||
# used by default.
|
||||
# id_callsign = MYCALL-0
|
||||
# id_interval = 600
|
||||
|
||||
|
||||
# An example KISS modem interface. Useful for running
|
||||
# Reticulum over packet radio hardware.
|
||||
|
||||
@@ -5,7 +5,7 @@ with open("README.md", "r") as fh:
|
||||
|
||||
setuptools.setup(
|
||||
name="rns",
|
||||
version="0.1.1",
|
||||
version="0.1.6",
|
||||
author="Mark Qvist",
|
||||
author_email="mark@unsigned.io",
|
||||
description="Self-configuring, encrypted and resilient mesh networking stack for LoRa, packet radio, WiFi and everything in between",
|
||||
@@ -19,5 +19,5 @@ setuptools.setup(
|
||||
"Operating System :: OS Independent",
|
||||
],
|
||||
install_requires=['cryptography', 'pyserial'],
|
||||
python_requires='>=3.6',
|
||||
python_requires='>=3.5',
|
||||
)
|
||||
Reference in New Issue
Block a user