2022-10-12 15:11:02 +02:00
# 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.
2022-10-12 16:08:48 +02:00
from RNS . Interfaces . Interface import Interface
2022-10-12 15:11:02 +02:00
from time import sleep
import sys
import threading
import time
import math
import RNS
class KISS ( ) :
FEND = 0xC0
FESC = 0xDB
TFEND = 0xDC
TFESC = 0xDD
CMD_UNKNOWN = 0xFE
CMD_DATA = 0x00
CMD_FREQUENCY = 0x01
CMD_BANDWIDTH = 0x02
CMD_TXPOWER = 0x03
CMD_SF = 0x04
CMD_CR = 0x05
CMD_RADIO_STATE = 0x06
CMD_RADIO_LOCK = 0x07
CMD_DETECT = 0x08
CMD_READY = 0x0F
CMD_STAT_RX = 0x21
CMD_STAT_TX = 0x22
CMD_STAT_RSSI = 0x23
CMD_STAT_SNR = 0x24
CMD_BLINK = 0x30
CMD_RANDOM = 0x40
CMD_PLATFORM = 0x48
CMD_MCU = 0x49
CMD_FW_VERSION = 0x50
CMD_ROM_READ = 0x51
CMD_RESET = 0x55
DETECT_REQ = 0x73
DETECT_RESP = 0x46
RADIO_STATE_OFF = 0x00
RADIO_STATE_ON = 0x01
RADIO_STATE_ASK = 0xFF
CMD_ERROR = 0x90
ERROR_INITRADIO = 0x01
ERROR_TXFAILED = 0x02
ERROR_EEPROM_LOCKED = 0x03
PLATFORM_AVR = 0x90
PLATFORM_ESP32 = 0x80
@staticmethod
def escape ( data ) :
data = data . replace ( bytes ( [ 0xdb ] ) , bytes ( [ 0xdb , 0xdd ] ) )
data = data . replace ( bytes ( [ 0xc0 ] ) , bytes ( [ 0xdb , 0xdc ] ) )
return data
class RNodeInterface ( Interface ) :
MAX_CHUNK = 32768
FREQ_MIN = 137000000
FREQ_MAX = 1020000000
RSSI_OFFSET = 157
CALLSIGN_MAX_LEN = 32
REQUIRED_FW_VER_MAJ = 1
REQUIRED_FW_VER_MIN = 26
RECONNECT_WAIT = 5
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 ) :
import importlib
if RNS . vendor . platformutils . is_android ( ) :
self . on_android = True
if importlib . util . find_spec ( ' usbserial4a ' ) != None :
2022-10-20 20:10:50 +02:00
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 ( )
2022-10-12 15:11:02 +02:00
from usbserial4a import serial4a as serial
self . parity = " N "
2022-10-20 20:10:50 +02:00
2022-10-12 15:11:02 +02:00
else :
RNS . log ( " Could not load USB serial module for Android, RNode interface cannot be created. " , RNS . LOG_CRITICAL )
2022-10-20 20:10:50 +02:00
RNS . log ( " You can install this module by issuing: pip install usbserial4a " , RNS . LOG_CRITICAL )
2022-10-12 15:11:02 +02:00
RNS . panic ( )
else :
raise SystemError ( " Android-specific interface was used on non-Android OS " )
self . rxb = 0
self . txb = 0
self . HW_MTU = 508
self . pyserial = serial
self . serial = None
self . owner = owner
self . name = name
self . port = port
self . speed = 115200
self . databits = 8
self . stopbits = 1
self . timeout = 150
self . online = False
self . frequency = frequency
self . bandwidth = bandwidth
self . txpower = txpower
self . sf = sf
self . cr = cr
self . state = KISS . RADIO_STATE_OFF
self . bitrate = 0
self . platform = None
self . mcu = None
self . detected = False
self . firmware_ok = False
self . maj_version = 0
self . min_version = 0
self . last_id = 0
self . first_tx = None
self . reconnect_w = RNodeInterface . RECONNECT_WAIT
self . r_frequency = None
self . r_bandwidth = None
self . r_txpower = None
self . r_sf = None
self . r_cr = None
self . r_state = None
self . r_lock = None
self . r_stat_rx = None
self . r_stat_tx = None
self . r_stat_rssi = None
self . r_random = None
self . packet_queue = [ ]
self . flow_control = flow_control
self . interface_ready = False
self . announce_rate_target = None
self . validcfg = True
if ( self . frequency < RNodeInterface . FREQ_MIN or self . frequency > RNodeInterface . FREQ_MAX ) :
RNS . log ( " Invalid frequency configured for " + str ( self ) , RNS . LOG_ERROR )
self . validcfg = False
if ( self . txpower < 0 or self . txpower > 17 ) :
RNS . log ( " Invalid TX power configured for " + str ( self ) , RNS . LOG_ERROR )
self . validcfg = False
if ( self . bandwidth < 7800 or self . bandwidth > 500000 ) :
RNS . log ( " Invalid bandwidth configured for " + str ( self ) , RNS . LOG_ERROR )
self . validcfg = False
if ( self . sf < 7 or self . sf > 12 ) :
RNS . log ( " Invalid spreading factor configured for " + str ( self ) , RNS . LOG_ERROR )
self . validcfg = False
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 . encode ( " utf-8 " )
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 " )
try :
self . open_port ( )
if self . serial . is_open :
self . configure_device ( )
else :
raise IOError ( " Could not open serial port " )
except Exception as e :
RNS . log ( " Could not open serial port for interface " + str ( self ) , RNS . LOG_ERROR )
RNS . log ( " The contained exception was: " + str ( e ) , RNS . LOG_ERROR )
RNS . log ( " Reticulum will attempt to bring up this interface periodically " , RNS . LOG_ERROR )
thread = threading . Thread ( target = self . reconnect_port )
thread . daemon = True
thread . start ( )
def open_port ( self ) :
RNS . log ( " Opening serial port " + self . port + " ... " )
# Get device parameters
from usb4a import usb
device = usb . get_usb_device ( self . port )
if device :
vid = device . getVendorId ( )
pid = device . getProductId ( )
# Driver overrides for speficic chips
proxy = self . pyserial . get_serial_port
if vid == 0x1A86 and pid == 0x55D4 :
# Force CDC driver for Qinheng CH34x
RNS . log ( str ( self ) + " using CDC driver for " + RNS . hexrep ( vid ) + " : " + RNS . hexrep ( pid ) , RNS . LOG_DEBUG )
from usbserial4a . cdcacmserial4a import CdcAcmSerial
proxy = CdcAcmSerial
self . serial = proxy (
self . port ,
baudrate = self . speed ,
bytesize = self . databits ,
parity = self . parity ,
stopbits = self . stopbits ,
xonxoff = False ,
rtscts = False ,
timeout = None ,
inter_byte_timeout = None ,
# write_timeout = wtimeout,
dsrdtr = False ,
)
if vid == 0x0403 :
# Hardware parameters for FTDI devices @ 115200 baud
self . serial . DEFAULT_READ_BUFFER_SIZE = 16 * 1024
self . serial . USB_READ_TIMEOUT_MILLIS = 100
self . serial . timeout = 0.1
elif vid == 0x10C4 :
# Hardware parameters for SiLabs CP210x @ 115200 baud
self . serial . DEFAULT_READ_BUFFER_SIZE = 64
self . serial . USB_READ_TIMEOUT_MILLIS = 12
self . serial . timeout = 0.012
elif vid == 0x1A86 and pid == 0x55D4 :
# Hardware parameters for Qinheng CH34x @ 115200 baud
self . serial . DEFAULT_READ_BUFFER_SIZE = 64
self . serial . USB_READ_TIMEOUT_MILLIS = 12
self . serial . timeout = 0.1
else :
# Default values
self . serial . DEFAULT_READ_BUFFER_SIZE = 1 * 1024
self . serial . USB_READ_TIMEOUT_MILLIS = 100
self . serial . timeout = 0.1
2022-10-12 16:08:48 +02:00
RNS . log ( str ( self ) + " USB read buffer size set to " + RNS . prettysize ( self . serial . DEFAULT_READ_BUFFER_SIZE ) , RNS . LOG_DEBUG )
2022-10-12 15:11:02 +02:00
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 )
def configure_device ( self ) :
sleep ( 2.0 )
thread = threading . Thread ( target = self . readLoop )
thread . daemon = True
thread . start ( )
self . detect ( )
sleep ( 0.4 )
if not self . detected :
raise IOError ( " Could not detect device " )
# else:
# if self.platform == KISS.PLATFORM_ESP32:
# TODO: Consider reintroducing this
# RNS.log("Resetting ESP32-based device before configuration...", RNS.LOG_VERBOSE)
# self.hard_reset()
RNS . log ( " Serial port " + self . port + " is now open " )
RNS . log ( " Configuring RNode interface... " , RNS . LOG_VERBOSE )
self . initRadio ( )
if ( self . validateRadioState ( ) ) :
self . interface_ready = True
RNS . log ( str ( self ) + " is configured and powered up " )
2022-10-12 18:06:21 +02:00
sleep ( 0.3 )
self . online = True
2022-10-12 15:11:02 +02:00
else :
RNS . log ( " After configuring " + str ( self ) + " , the reported radio parameters did not match your configuration. " , RNS . LOG_ERROR )
RNS . log ( " Make sure that your hardware actually supports the parameters specified in the configuration " , RNS . LOG_ERROR )
RNS . log ( " Aborting RNode startup " , RNS . LOG_ERROR )
self . serial . close ( )
raise IOError ( " RNode interface did not pass configuration validation " )
def initRadio ( self ) :
self . setFrequency ( )
time . sleep ( 0.1 )
self . setBandwidth ( )
time . sleep ( 0.1 )
self . setTXPower ( )
time . sleep ( 0.1 )
self . setSpreadingFactor ( )
time . sleep ( 0.1 )
self . setCodingRate ( )
time . sleep ( 0.1 )
self . setRadioState ( KISS . RADIO_STATE_ON )
time . sleep ( 0.1 )
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 ( 4.0 ) ;
def setFrequency ( self ) :
c1 = self . frequency >> 24
c2 = self . frequency >> 16 & 0xFF
c3 = self . frequency >> 8 & 0xFF
c4 = self . frequency & 0xFF
data = KISS . escape ( bytes ( [ c1 ] ) + bytes ( [ c2 ] ) + bytes ( [ c3 ] ) + bytes ( [ c4 ] ) )
kiss_command = bytes ( [ KISS . FEND ] ) + bytes ( [ KISS . CMD_FREQUENCY ] ) + data + bytes ( [ KISS . FEND ] )
written = self . serial . write ( kiss_command )
if written != len ( kiss_command ) :
raise IOError ( " An IO error occurred while configuring frequency for " + self ( str ) )
def setBandwidth ( self ) :
c1 = self . bandwidth >> 24
c2 = self . bandwidth >> 16 & 0xFF
c3 = self . bandwidth >> 8 & 0xFF
c4 = self . bandwidth & 0xFF
data = KISS . escape ( bytes ( [ c1 ] ) + bytes ( [ c2 ] ) + bytes ( [ c3 ] ) + bytes ( [ c4 ] ) )
kiss_command = bytes ( [ KISS . FEND ] ) + bytes ( [ KISS . CMD_BANDWIDTH ] ) + data + bytes ( [ KISS . FEND ] )
written = self . serial . write ( kiss_command )
if written != len ( kiss_command ) :
raise IOError ( " An IO error occurred while configuring bandwidth for " + self ( str ) )
def setTXPower ( self ) :
txp = bytes ( [ self . txpower ] )
kiss_command = bytes ( [ KISS . FEND ] ) + bytes ( [ KISS . CMD_TXPOWER ] ) + txp + bytes ( [ KISS . FEND ] )
written = self . serial . write ( kiss_command )
if written != len ( kiss_command ) :
raise IOError ( " An IO error occurred while configuring TX power for " + self ( str ) )
def setSpreadingFactor ( self ) :
sf = bytes ( [ self . sf ] )
kiss_command = bytes ( [ KISS . FEND ] ) + bytes ( [ KISS . CMD_SF ] ) + sf + bytes ( [ KISS . FEND ] )
written = self . serial . write ( kiss_command )
if written != len ( kiss_command ) :
raise IOError ( " An IO error occurred while configuring spreading factor for " + self ( str ) )
def setCodingRate ( self ) :
cr = bytes ( [ self . cr ] )
kiss_command = bytes ( [ KISS . FEND ] ) + bytes ( [ KISS . CMD_CR ] ) + cr + bytes ( [ KISS . FEND ] )
written = self . serial . write ( kiss_command )
if written != len ( kiss_command ) :
raise IOError ( " An IO error occurred while configuring coding rate for " + self ( str ) )
def setRadioState ( self , state ) :
self . state = state
kiss_command = bytes ( [ KISS . FEND ] ) + bytes ( [ KISS . CMD_RADIO_STATE ] ) + bytes ( [ state ] ) + bytes ( [ KISS . FEND ] )
written = self . serial . write ( kiss_command )
if written != len ( kiss_command ) :
raise IOError ( " An IO error occurred while configuring radio state for " + self ( str ) )
def validate_firmware ( self ) :
if ( self . maj_version > = RNodeInterface . REQUIRED_FW_VER_MAJ ) :
if ( self . min_version > = RNodeInterface . REQUIRED_FW_VER_MIN ) :
self . firmware_ok = True
if self . firmware_ok :
return
RNS . log ( " The firmware version of the connected RNode is " + str ( self . maj_version ) + " . " + str ( self . min_version ) , RNS . LOG_ERROR )
RNS . log ( " This version of Reticulum requires at least version " + str ( RNodeInterface . REQUIRED_FW_VER_MAJ ) + " . " + str ( RNodeInterface . REQUIRED_FW_VER_MIN ) , RNS . LOG_ERROR )
RNS . log ( " Please update your RNode firmware with rnodeconf (https://github.com/markqvist/rnodeconfigutil/) " )
RNS . panic ( )
def validateRadioState ( self ) :
RNS . log ( " Wating for radio configuration validation for " + str ( self ) + " ... " , RNS . LOG_VERBOSE )
if not self . platform == KISS . PLATFORM_ESP32 :
sleep ( 1.00 ) ;
else :
sleep ( 2.00 ) ;
self . validcfg = True
if ( self . r_frequency != None and abs ( self . frequency - int ( self . r_frequency ) ) > 100 ) :
RNS . log ( " Frequency mismatch " , RNS . LOG_ERROR )
self . validcfg = False
if ( self . bandwidth != self . r_bandwidth ) :
RNS . log ( " Bandwidth mismatch " , RNS . LOG_ERROR )
self . validcfg = False
if ( self . txpower != self . r_txpower ) :
RNS . log ( " TX power mismatch " , RNS . LOG_ERROR )
self . validcfg = False
if ( self . sf != self . r_sf ) :
RNS . log ( " Spreading factor mismatch " , RNS . LOG_ERROR )
self . validcfg = False
if ( self . state != self . r_state ) :
RNS . log ( " Radio state mismatch " , RNS . LOG_ERROR )
self . validcfg = False
if ( self . validcfg ) :
return True
else :
return False
def updateBitrate ( self ) :
try :
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_VERBOSE )
except :
self . bitrate = 0
def processIncoming ( self , data ) :
self . rxb + = len ( data )
def af ( ) :
self . owner . inbound ( data , self )
threading . Thread ( target = af , daemon = True ) . start ( )
self . r_stat_rssi = None
self . r_stat_snr = None
def processOutgoing ( self , data ) :
datalen = len ( data )
if self . online :
if self . interface_ready :
if self . flow_control :
self . interface_ready = False
if data == self . id_callsign :
self . first_tx = None
else :
if self . first_tx == None :
self . first_tx = time . time ( )
data = KISS . escape ( data )
frame = bytes ( [ 0xc0 ] ) + bytes ( [ 0x00 ] ) + data + bytes ( [ 0xc0 ] )
written = self . serial . write ( frame )
self . txb + = datalen
if written != len ( frame ) :
raise IOError ( " Serial interface only wrote " + str ( written ) + " bytes of " + str ( len ( data ) ) )
else :
self . queue ( data )
def queue ( self , data ) :
self . packet_queue . append ( data )
def process_queue ( self ) :
if len ( self . packet_queue ) > 0 :
data = self . packet_queue . pop ( 0 )
self . interface_ready = True
self . processOutgoing ( data )
elif len ( self . packet_queue ) == 0 :
self . interface_ready = True
def readLoop ( self ) :
try :
in_frame = False
escape = False
command = KISS . CMD_UNKNOWN
data_buffer = b " "
command_buffer = b " "
last_read_ms = int ( time . time ( ) * 1000 )
# TODO: Ensure hotplug support
while self . serial . is_open :
2022-10-15 11:31:34 +02:00
# TODO: Check multibyte reads
serial_bytes = self . serial . read ( )
got = len ( serial_bytes )
for byte in serial_bytes :
last_read_ms = int ( time . time ( ) * 1000 )
if ( in_frame and byte == KISS . FEND and command == KISS . CMD_DATA ) :
in_frame = False
self . processIncoming ( data_buffer )
data_buffer = b " "
command_buffer = b " "
elif ( byte == KISS . FEND ) :
in_frame = True
command = KISS . CMD_UNKNOWN
data_buffer = b " "
command_buffer = b " "
elif ( in_frame and len ( data_buffer ) < self . HW_MTU ) :
if ( len ( data_buffer ) == 0 and command == KISS . CMD_UNKNOWN ) :
command = byte
elif ( command == KISS . CMD_DATA ) :
if ( byte == KISS . FESC ) :
escape = True
else :
if ( escape ) :
if ( byte == KISS . TFEND ) :
byte = KISS . FEND
if ( byte == KISS . TFESC ) :
byte = KISS . FESC
escape = False
data_buffer = data_buffer + bytes ( [ byte ] )
elif ( command == KISS . CMD_FREQUENCY ) :
if ( byte == KISS . FESC ) :
escape = True
else :
if ( escape ) :
if ( byte == KISS . TFEND ) :
byte = KISS . FEND
if ( byte == KISS . TFESC ) :
byte = KISS . FESC
escape = False
command_buffer = command_buffer + bytes ( [ byte ] )
if ( len ( command_buffer ) == 4 ) :
self . r_frequency = command_buffer [ 0 ] << 24 | command_buffer [ 1 ] << 16 | command_buffer [ 2 ] << 8 | command_buffer [ 3 ]
RNS . log ( str ( self ) + " Radio reporting frequency is " + str ( self . r_frequency / 1000000.0 ) + " MHz " , RNS . LOG_DEBUG )
self . updateBitrate ( )
elif ( command == KISS . CMD_BANDWIDTH ) :
if ( byte == KISS . FESC ) :
escape = True
else :
if ( escape ) :
if ( byte == KISS . TFEND ) :
byte = KISS . FEND
if ( byte == KISS . TFESC ) :
byte = KISS . FESC
escape = False
command_buffer = command_buffer + bytes ( [ byte ] )
if ( len ( command_buffer ) == 4 ) :
self . r_bandwidth = command_buffer [ 0 ] << 24 | command_buffer [ 1 ] << 16 | command_buffer [ 2 ] << 8 | command_buffer [ 3 ]
RNS . log ( str ( self ) + " Radio reporting bandwidth is " + str ( self . r_bandwidth / 1000.0 ) + " KHz " , RNS . LOG_DEBUG )
self . updateBitrate ( )
elif ( command == KISS . CMD_TXPOWER ) :
self . r_txpower = byte
RNS . log ( str ( self ) + " Radio reporting TX power is " + str ( self . r_txpower ) + " dBm " , RNS . LOG_DEBUG )
elif ( command == KISS . CMD_SF ) :
self . r_sf = byte
RNS . log ( str ( self ) + " Radio reporting spreading factor is " + str ( self . r_sf ) , RNS . LOG_DEBUG )
self . updateBitrate ( )
elif ( command == KISS . CMD_CR ) :
self . r_cr = byte
RNS . log ( str ( self ) + " Radio reporting coding rate is " + str ( self . r_cr ) , RNS . LOG_DEBUG )
self . updateBitrate ( )
elif ( command == KISS . CMD_RADIO_STATE ) :
self . r_state = byte
if self . r_state :
RNS . log ( str ( self ) + " Radio reporting state is online " , RNS . LOG_DEBUG )
else :
RNS . log ( str ( self ) + " Radio reporting state is offline " , RNS . LOG_DEBUG )
elif ( command == KISS . CMD_RADIO_LOCK ) :
self . r_lock = byte
elif ( command == KISS . CMD_FW_VERSION ) :
if ( byte == KISS . FESC ) :
escape = True
else :
if ( escape ) :
if ( byte == KISS . TFEND ) :
byte = KISS . FEND
if ( byte == KISS . TFESC ) :
byte = KISS . FESC
escape = False
command_buffer = command_buffer + bytes ( [ byte ] )
if ( len ( command_buffer ) == 2 ) :
self . maj_version = int ( command_buffer [ 0 ] )
self . min_version = int ( command_buffer [ 1 ] )
self . validate_firmware ( )
elif ( command == KISS . CMD_STAT_RX ) :
if ( byte == KISS . FESC ) :
escape = True
else :
if ( escape ) :
if ( byte == KISS . TFEND ) :
byte = KISS . FEND
if ( byte == KISS . TFESC ) :
byte = KISS . FESC
escape = False
command_buffer = command_buffer + bytes ( [ byte ] )
if ( len ( command_buffer ) == 4 ) :
self . r_stat_rx = ord ( command_buffer [ 0 ] ) << 24 | ord ( command_buffer [ 1 ] ) << 16 | ord ( command_buffer [ 2 ] ) << 8 | ord ( command_buffer [ 3 ] )
elif ( command == KISS . CMD_STAT_TX ) :
if ( byte == KISS . FESC ) :
escape = True
else :
if ( escape ) :
if ( byte == KISS . TFEND ) :
byte = KISS . FEND
if ( byte == KISS . TFESC ) :
byte = KISS . FESC
escape = False
command_buffer = command_buffer + bytes ( [ byte ] )
if ( len ( command_buffer ) == 4 ) :
self . r_stat_tx = ord ( command_buffer [ 0 ] ) << 24 | ord ( command_buffer [ 1 ] ) << 16 | ord ( command_buffer [ 2 ] ) << 8 | ord ( command_buffer [ 3 ] )
elif ( command == KISS . CMD_STAT_RSSI ) :
self . r_stat_rssi = byte - RNodeInterface . RSSI_OFFSET
elif ( command == KISS . CMD_STAT_SNR ) :
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_PLATFORM ) :
self . platform = byte
elif ( command == KISS . CMD_MCU ) :
self . mcu = byte
elif ( command == KISS . CMD_ERROR ) :
if ( byte == KISS . ERROR_INITRADIO ) :
RNS . log ( str ( self ) + " hardware initialisation error (code " + RNS . hexrep ( byte ) + " ) " , RNS . LOG_ERROR )
raise IOError ( " Radio initialisation failure " )
elif ( byte == KISS . ERROR_INITRADIO ) :
RNS . log ( str ( self ) + " hardware TX error (code " + RNS . hexrep ( byte ) + " ) " , RNS . LOG_ERROR )
raise IOError ( " Hardware transmit failure " )
else :
RNS . log ( str ( self ) + " hardware error (code " + RNS . hexrep ( byte ) + " ) " , RNS . LOG_ERROR )
raise IOError ( " Unknown hardware failure " )
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 ) :
self . process_queue ( )
elif ( command == KISS . CMD_DETECT ) :
if byte == KISS . DETECT_RESP :
self . detected = True
else :
self . detected = False
2022-10-12 15:11:02 +02:00
if got == 0 :
time_since_last = int ( time . time ( ) * 1000 ) - last_read_ms
if len ( data_buffer ) > 0 and time_since_last > self . timeout :
RNS . log ( str ( self ) + " serial read timeout " , RNS . LOG_DEBUG )
data_buffer = b " "
in_frame = False
command = KISS . CMD_UNKNOWN
escape = False
if self . id_interval != None and self . id_callsign != None :
if self . first_tx != None :
if time . time ( ) > self . first_tx + self . id_interval :
RNS . log ( " Interface " + str ( self ) + " is transmitting beacon data: " + str ( self . id_callsign . decode ( " utf-8 " ) ) , RNS . LOG_DEBUG )
self . processOutgoing ( self . id_callsign )
# sleep(0.08)
except Exception as e :
self . online = False
RNS . log ( " A serial port error occurred, the contained exception was: " + str ( e ) , RNS . LOG_ERROR )
RNS . log ( " The interface " + str ( self ) + " experienced an unrecoverable error and is now offline. " , RNS . LOG_ERROR )
if RNS . Reticulum . panic_on_interface_error :
RNS . panic ( )
RNS . log ( " Reticulum will attempt to reconnect the interface periodically. " , RNS . LOG_ERROR )
self . online = False
self . serial . close ( )
self . reconnect_port ( )
def reconnect_port ( self ) :
while not self . online :
try :
time . sleep ( self . reconnect_w )
RNS . log ( " Attempting to reconnect serial port " + str ( self . port ) + " for " + str ( self ) + " ... " , RNS . LOG_VERBOSE )
self . open_port ( )
if hasattr ( self , " serial " ) and self . serial != None and self . serial . is_open :
self . configure_device ( )
except Exception as e :
RNS . log ( " Error while reconnecting port, the contained exception was: " + str ( e ) , RNS . LOG_ERROR )
RNS . log ( " Reconnected serial port for " + str ( self ) )
def __str__ ( self ) :
return " RNodeInterface[ " + str ( self . name ) + " ] "