mirror of
https://github.com/markqvist/Reticulum.git
synced 2026-06-29 21:52:07 -07:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b61fa6ce8d | |||
| 11c741dacb | |||
| 24abb4cfa4 | |||
| 0d069bf1d8 | |||
| fd010fa80c | |||
| 8c2cfb0349 | |||
| 0140cd6eba | |||
| 3819485c20 | |||
| 3ca06bbf3a |
@@ -68,9 +68,10 @@ class RNodeInterface(Interface):
|
|||||||
FREQ_MAX = 1020000000
|
FREQ_MAX = 1020000000
|
||||||
|
|
||||||
RSSI_OFFSET = 157
|
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.serial = None
|
||||||
self.owner = owner
|
self.owner = owner
|
||||||
self.name = name
|
self.name = name
|
||||||
@@ -90,6 +91,8 @@ class RNodeInterface(Interface):
|
|||||||
self.state = KISS.RADIO_STATE_OFF
|
self.state = KISS.RADIO_STATE_OFF
|
||||||
self.bitrate = 0
|
self.bitrate = 0
|
||||||
|
|
||||||
|
self.last_id = 0
|
||||||
|
|
||||||
self.r_frequency = None
|
self.r_frequency = None
|
||||||
self.r_bandwidth = None
|
self.r_bandwidth = None
|
||||||
self.r_txpower = None
|
self.r_txpower = None
|
||||||
@@ -123,10 +126,22 @@ class RNodeInterface(Interface):
|
|||||||
RNS.log("Invalid spreading factor configured for "+str(self), RNS.LOG_ERROR)
|
RNS.log("Invalid spreading factor configured for "+str(self), RNS.LOG_ERROR)
|
||||||
self.validcfg = False
|
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)
|
RNS.log("Invalid coding rate configured for "+str(self), RNS.LOG_ERROR)
|
||||||
self.validcfg = False
|
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):
|
if (not self.validcfg):
|
||||||
raise ValueError("The configuration for "+str(self)+" contains errors, interface is offline")
|
raise ValueError("The configuration for "+str(self)+" contains errors, interface is offline")
|
||||||
|
|
||||||
@@ -177,6 +192,7 @@ class RNodeInterface(Interface):
|
|||||||
self.setBandwidth()
|
self.setBandwidth()
|
||||||
self.setTXPower()
|
self.setTXPower()
|
||||||
self.setSpreadingFactor()
|
self.setSpreadingFactor()
|
||||||
|
self.setCodingRate()
|
||||||
self.setRadioState(KISS.RADIO_STATE_ON)
|
self.setRadioState(KISS.RADIO_STATE_ON)
|
||||||
|
|
||||||
def setFrequency(self):
|
def setFrequency(self):
|
||||||
@@ -205,7 +221,6 @@ class RNodeInterface(Interface):
|
|||||||
|
|
||||||
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):
|
||||||
@@ -213,7 +228,6 @@ class RNodeInterface(Interface):
|
|||||||
|
|
||||||
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):
|
||||||
@@ -221,7 +235,6 @@ class RNodeInterface(Interface):
|
|||||||
|
|
||||||
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):
|
||||||
@@ -257,9 +270,9 @@ class RNodeInterface(Interface):
|
|||||||
|
|
||||||
def updateBitrate(self):
|
def updateBitrate(self):
|
||||||
try:
|
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)
|
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:
|
except:
|
||||||
self.bitrate = 0
|
self.bitrate = 0
|
||||||
|
|
||||||
@@ -273,9 +286,17 @@ class RNodeInterface(Interface):
|
|||||||
if self.flow_control:
|
if self.flow_control:
|
||||||
self.interface_ready = False
|
self.interface_ready = False
|
||||||
|
|
||||||
data = KISS.escape(data)
|
frame = b""
|
||||||
frame = bytes([0xc0])+bytes([0x00])+data+bytes([0xc0])
|
|
||||||
|
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)
|
written = self.serial.write(frame)
|
||||||
|
|
||||||
if written != len(frame):
|
if written != len(frame):
|
||||||
raise IOError("Serial interface only wrote "+str(written)+" bytes of "+str(len(data)))
|
raise IOError("Serial interface only wrote "+str(written)+" bytes of "+str(len(data)))
|
||||||
else:
|
else:
|
||||||
@@ -408,7 +429,7 @@ class RNodeInterface(Interface):
|
|||||||
elif (command == KISS.CMD_STAT_RSSI):
|
elif (command == KISS.CMD_STAT_RSSI):
|
||||||
self.r_stat_rssi = byte-RNodeInterface.RSSI_OFFSET
|
self.r_stat_rssi = byte-RNodeInterface.RSSI_OFFSET
|
||||||
elif (command == KISS.CMD_STAT_SNR):
|
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):
|
elif (command == KISS.CMD_RANDOM):
|
||||||
self.r_random = byte
|
self.r_random = byte
|
||||||
elif (command == KISS.CMD_ERROR):
|
elif (command == KISS.CMD_ERROR):
|
||||||
|
|||||||
+27
-11
@@ -248,8 +248,7 @@ class Reticulum:
|
|||||||
txtail = int(c["txtail"]) if "txtail" in c else None
|
txtail = int(c["txtail"]) if "txtail" in c else None
|
||||||
persistence = int(c["persistence"]) if "persistence" in c else None
|
persistence = int(c["persistence"]) if "persistence" in c else None
|
||||||
slottime = int(c["slottime"]) if "slottime" 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
|
port = c["port"] if "port" in c else None
|
||||||
speed = int(c["speed"]) if "speed" in c else 9600
|
speed = int(c["speed"]) if "speed" in c else 9600
|
||||||
databits = int(c["databits"]) if "databits" in c else 8
|
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
|
txtail = int(c["txtail"]) if "txtail" in c else None
|
||||||
persistence = int(c["persistence"]) if "persistence" in c else None
|
persistence = int(c["persistence"]) if "persistence" in c else None
|
||||||
slottime = int(c["slottime"]) if "slottime" 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
|
port = c["port"] if "port" in c else None
|
||||||
speed = int(c["speed"]) if "speed" in c else 9600
|
speed = int(c["speed"]) if "speed" in c else 9600
|
||||||
databits = int(c["databits"]) if "databits" in c else 8
|
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
|
txpower = int(c["txpower"]) if "txpower" in c else None
|
||||||
spreadingfactor = int(c["spreadingfactor"]) if "spreadingfactor" in c else None
|
spreadingfactor = int(c["spreadingfactor"]) if "spreadingfactor" in c else None
|
||||||
codingrate = int(c["codingrate"]) if "codingrate" 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
|
port = c["port"] if "port" in c else None
|
||||||
|
|
||||||
@@ -341,11 +341,14 @@ class Reticulum:
|
|||||||
RNS.Transport,
|
RNS.Transport,
|
||||||
name,
|
name,
|
||||||
port,
|
port,
|
||||||
frequency,
|
frequency = frequency,
|
||||||
bandwidth,
|
bandwidth = bandwidth,
|
||||||
txpower,
|
txpower = txpower,
|
||||||
spreadingfactor,
|
sf = spreadingfactor,
|
||||||
flow_control
|
cr = codingrate,
|
||||||
|
flow_control = flow_control,
|
||||||
|
id_interval = id_interval,
|
||||||
|
id_callsign = id_callsign
|
||||||
)
|
)
|
||||||
|
|
||||||
if "outgoing" in c and c["outgoing"].lower() == "true":
|
if "outgoing" in c and c["outgoing"].lower() == "true":
|
||||||
@@ -355,7 +358,7 @@ class Reticulum:
|
|||||||
|
|
||||||
RNS.Transport.interfaces.append(interface)
|
RNS.Transport.interfaces.append(interface)
|
||||||
else:
|
else:
|
||||||
RNS.log("Skipping disabled interface \""+name+"\"", RNS.LOG_VERBOSE)
|
RNS.log("Skipping disabled interface \""+name+"\"", RNS.LOG_NOTICE)
|
||||||
|
|
||||||
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)
|
||||||
@@ -533,6 +536,19 @@ loglevel = 4
|
|||||||
# fastest, and 8 the longest range.
|
# fastest, and 8 the longest range.
|
||||||
codingrate = 5
|
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
|
# An example KISS modem interface. Useful for running
|
||||||
# Reticulum over packet radio hardware.
|
# Reticulum over packet radio hardware.
|
||||||
|
|||||||
+47
-6
@@ -324,6 +324,11 @@ class Transport:
|
|||||||
if packet.packet_type != RNS.Packet.ANNOUNCE and packet.destination_hash in Transport.destination_table:
|
if packet.packet_type != RNS.Packet.ANNOUNCE and packet.destination_hash in Transport.destination_table:
|
||||||
outbound_interface = Transport.destination_table[packet.destination_hash][5]
|
outbound_interface = Transport.destination_table[packet.destination_hash][5]
|
||||||
|
|
||||||
|
# If there's more than one hop to the destination, and we know
|
||||||
|
# a path, we insert the packet into transport by adding the next
|
||||||
|
# transport nodes address to the header, and modifying the flags.
|
||||||
|
# This rule applies both for "normal" transport, and when connected
|
||||||
|
# to a local shared Reticulum instance.
|
||||||
if Transport.destination_table[packet.destination_hash][2] > 1:
|
if Transport.destination_table[packet.destination_hash][2] > 1:
|
||||||
if packet.header_type == RNS.Packet.HEADER_1:
|
if packet.header_type == RNS.Packet.HEADER_1:
|
||||||
# Insert packet into transport
|
# Insert packet into transport
|
||||||
@@ -333,19 +338,44 @@ class Transport:
|
|||||||
new_raw += Transport.destination_table[packet.destination_hash][1]
|
new_raw += Transport.destination_table[packet.destination_hash][1]
|
||||||
new_raw += packet.raw[2:]
|
new_raw += packet.raw[2:]
|
||||||
# TODO: Remove at some point
|
# TODO: Remove at some point
|
||||||
RNS.log("Packet was inserted into transport via "+RNS.prettyhexrep(Transport.destination_table[packet.destination_hash][1])+" on: "+str(outbound_interface), RNS.LOG_DEBUG)
|
RNS.log("Packet was inserted into transport via "+RNS.prettyhexrep(Transport.destination_table[packet.destination_hash][1])+" on: "+str(outbound_interface), RNS.LOG_EXTREME)
|
||||||
outbound_interface.processOutgoing(new_raw)
|
outbound_interface.processOutgoing(new_raw)
|
||||||
Transport.destination_table[packet.destination_hash][0] = time.time()
|
Transport.destination_table[packet.destination_hash][0] = time.time()
|
||||||
sent = True
|
sent = True
|
||||||
|
|
||||||
|
# In the special case where we are connected to a local shared
|
||||||
|
# Reticulum instance, and the destination is one hop away, we
|
||||||
|
# also add transport headers to inject the packet into transport
|
||||||
|
# via the shared instance. Normally a packet for a destination
|
||||||
|
# one hop away would just be broadcast directly, but since we
|
||||||
|
# are "behind" a shared instance, we need to get that instance
|
||||||
|
# to transport it onto the network.
|
||||||
|
elif Transport.destination_table[packet.destination_hash][2] == 1 and Transport.owner.is_connected_to_shared_instance:
|
||||||
|
if packet.header_type == RNS.Packet.HEADER_1:
|
||||||
|
# Insert packet into transport
|
||||||
|
new_flags = (RNS.Packet.HEADER_2) << 6 | (Transport.TRANSPORT) << 4 | (packet.flags & 0b00001111)
|
||||||
|
new_raw = struct.pack("!B", new_flags)
|
||||||
|
new_raw += packet.raw[1:2]
|
||||||
|
new_raw += Transport.destination_table[packet.destination_hash][1]
|
||||||
|
new_raw += packet.raw[2:]
|
||||||
|
# TODO: Remove at some point
|
||||||
|
RNS.log("Packet was inserted into transport via "+RNS.prettyhexrep(Transport.destination_table[packet.destination_hash][1])+" on: "+str(outbound_interface), RNS.LOG_EXTREME)
|
||||||
|
outbound_interface.processOutgoing(new_raw)
|
||||||
|
Transport.destination_table[packet.destination_hash][0] = time.time()
|
||||||
|
sent = True
|
||||||
|
|
||||||
|
# If none of the above applies, we know the destination is
|
||||||
|
# directly reachable, and also on which interface, so we
|
||||||
|
# simply transmit the packet directly on that one.
|
||||||
else:
|
else:
|
||||||
# Destination is directly reachable, and we know on
|
|
||||||
# what interface, so transmit only on that one
|
|
||||||
outbound_interface.processOutgoing(packet.raw)
|
outbound_interface.processOutgoing(packet.raw)
|
||||||
sent = True
|
sent = True
|
||||||
|
|
||||||
|
# If we don't have a known path for the destination, we'll
|
||||||
|
# broadcast the packet on all outgoing interfaces, or the
|
||||||
|
# just the relevant interface if the packet has an attached
|
||||||
|
# interface, or belongs to a link.
|
||||||
else:
|
else:
|
||||||
# Broadcast packet on all outgoing interfaces, or the relevant
|
|
||||||
# interface if packet is for a link or has an attached interface
|
|
||||||
for interface in Transport.interfaces:
|
for interface in Transport.interfaces:
|
||||||
if interface.OUT:
|
if interface.OUT:
|
||||||
should_transmit = True
|
should_transmit = True
|
||||||
@@ -961,7 +991,12 @@ class Transport:
|
|||||||
local_rebroadcasts = 0
|
local_rebroadcasts = 0
|
||||||
block_rebroadcasts = True
|
block_rebroadcasts = True
|
||||||
announce_hops = packet.hops
|
announce_hops = packet.hops
|
||||||
retransmit_timeout = now + Transport.PATH_REQUEST_GRACE # + (RNS.rand() * Transport.PATHFINDER_RW)
|
|
||||||
|
if is_from_local_client:
|
||||||
|
retransmit_timeout = now
|
||||||
|
else:
|
||||||
|
# TODO: Look at this timing
|
||||||
|
retransmit_timeout = now + Transport.PATH_REQUEST_GRACE # + (RNS.rand() * Transport.PATHFINDER_RW)
|
||||||
|
|
||||||
# This handles an edge case where a peer sends a past
|
# This handles an edge case where a peer sends a past
|
||||||
# request for a destination just after an announce for
|
# request for a destination just after an announce for
|
||||||
@@ -982,6 +1017,12 @@ class Transport:
|
|||||||
if not interface == attached_interface:
|
if not interface == attached_interface:
|
||||||
Transport.requestPathOnInterface(destination_hash, interface)
|
Transport.requestPathOnInterface(destination_hash, interface)
|
||||||
|
|
||||||
|
elif not is_from_local_client and len(Transport.local_client_interfaces) > 0:
|
||||||
|
# Forward the path request on all local
|
||||||
|
# client interfaces
|
||||||
|
for interface in Transport.local_client_interfaces:
|
||||||
|
Transport.requestPathOnInterface(destination_hash, interface)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
RNS.log("No known path to requested destination, ignoring request", RNS.LOG_DEBUG)
|
RNS.log("No known path to requested destination, ignoring request", RNS.LOG_DEBUG)
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ with open("README.md", "r") as fh:
|
|||||||
|
|
||||||
setuptools.setup(
|
setuptools.setup(
|
||||||
name="rns",
|
name="rns",
|
||||||
version="0.1.1",
|
version="0.1.5",
|
||||||
author="Mark Qvist",
|
author="Mark Qvist",
|
||||||
author_email="mark@unsigned.io",
|
author_email="mark@unsigned.io",
|
||||||
description="Self-configuring, encrypted and resilient mesh networking stack for LoRa, packet radio, WiFi and everything in between",
|
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",
|
"Operating System :: OS Independent",
|
||||||
],
|
],
|
||||||
install_requires=['cryptography', 'pyserial'],
|
install_requires=['cryptography', 'pyserial'],
|
||||||
python_requires='>=3.6',
|
python_requires='>=3.5',
|
||||||
)
|
)
|
||||||
Reference in New Issue
Block a user