diff --git a/RNS/Utilities/rnodeconf.py b/RNS/Utilities/rnodeconf.py new file mode 100644 index 0000000..6ecf3a9 --- /dev/null +++ b/RNS/Utilities/rnodeconf.py @@ -0,0 +1,2375 @@ +#!python3 + +# MIT License +# +# Copyright (c) 2018-2022 Mark Qvist - unsigned.io/rnode +# +# 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. + +from time import sleep +import argparse +import threading +import os +import os.path +import struct +import datetime +import time +import math +import hashlib +from urllib.request import urlretrieve +from importlib import util +import RNS + +RNS.logtimefmt = "%H:%M:%S" +RNS.compact_log_fmt = True + +program_version = "2.0.0" +eth_addr = "0x81F7B979fEa6134bA9FD5c701b3501A2e61E897a" +btc_addr = "3CPmacGm34qYvR6XWLVEJmi2aNe3PZqUuq" +xmr_addr = "87HcDx6jRSkMQ9nPRd5K9hGGpZLn2s7vWETjMaVM5KfV4TD36NcYa8J8WSxhTSvBzzFpqDwp2fg5GX2moZ7VAP9QMZCZGET" + +rnode = None +rnode_serial = None +rnode_port = None +rnode_baudrate = 115200 +known_keys = [["unsigned.io", "30819f300d06092a864886f70d010101050003818d0030818902818100bf831ebd99f43b477caf1a094bec829389da40653e8f1f83fc14bf1b98a3e1cc70e759c213a43f71e5a47eb56a9ca487f241335b3e6ff7cdde0ee0a1c75c698574aeba0485726b6a9dfc046b4188e3520271ee8555a8f405cf21f81f2575771d0b0887adea5dd53c1f594f72c66b5f14904ffc2e72206a6698a490d51ba1105b0203010001"], ["unsigned.io", "30819f300d06092a864886f70d010101050003818d0030818902818100e5d46084e445595376bf7efd9c6ccf19d39abbc59afdb763207e4ff68b8d00ebffb63847aa2fe6dd10783d3ea63b55ac66f71ad885c20e223709f0d51ed5c6c0d0b093be9e1d165bb8a483a548b67a3f7a1e4580f50e75b306593fa6067ae259d3e297717bd7ff8c8f5b07f2bed89929a9a0321026cf3699524db98e2d18fb2d020300ff39"]] +firmware_update_url = "https://github.com/markqvist/RNode_Firmware/releases/download/" +fw_filename = None +mapped_model = None + +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_LEAVE = 0x0A + 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_BT_CTRL = 0x46 + CMD_BOARD = 0x47 + CMD_PLATFORM = 0x48 + CMD_MCU = 0x49 + CMD_FW_VERSION = 0x50 + CMD_ROM_READ = 0x51 + CMD_ROM_WRITE = 0x52 + CMD_ROM_WIPE = 0x59 + CMD_CONF_SAVE = 0x53 + CMD_CONF_DELETE = 0x54 + CMD_RESET = 0x55 + CMD_DEV_HASH = 0x56 + CMD_DEV_SIG = 0x57 + CMD_FW_HASH = 0x58 + + 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 + + @staticmethod + def escape(data): + data = data.replace(bytes([0xdb]), bytes([0xdb, 0xdd])) + data = data.replace(bytes([0xc0]), bytes([0xdb, 0xdc])) + return data + +class ROM(): + PLATFORM_AVR = 0x90 + PLATFORM_ESP32 = 0x80 + + MCU_1284P = 0x91 + MCU_2560 = 0x92 + MCU_ESP32 = 0x81 + + PRODUCT_RNODE = 0x03 + MODEL_A4 = 0xA4 + MODEL_A9 = 0xA9 + MODEL_A3 = 0xA3 + MODEL_A8 = 0xA8 + MODEL_A2 = 0xA2 + MODEL_A7 = 0xA7 + + PRODUCT_T32_20 = 0xB0 + MODEL_B3 = 0xB3 + MODEL_B8 = 0xB8 + + PRODUCT_T32_21 = 0xB1 + MODEL_B4 = 0xB4 + MODEL_B9 = 0xB9 + + PRODUCT_H32_V2 = 0xC0 + MODEL_C4 = 0xC4 + MODEL_C9 = 0xC9 + + PRODUCT_TBEAM = 0xE0 + MODEL_E4 = 0xE4 + MODEL_E9 = 0xE9 + + PRODUCT_HMBRW = 0xF0 + MODEL_FF = 0xFF + MODEL_FE = 0xFE + + ADDR_PRODUCT = 0x00 + ADDR_MODEL = 0x01 + ADDR_HW_REV = 0x02 + ADDR_SERIAL = 0x03 + ADDR_MADE = 0x07 + ADDR_CHKSUM = 0x0B + ADDR_SIGNATURE = 0x1B + ADDR_INFO_LOCK = 0x9B + ADDR_CONF_SF = 0x9C + ADDR_CONF_CR = 0x9D + ADDR_CONF_TXP = 0x9E + ADDR_CONF_BW = 0x9F + ADDR_CONF_FREQ = 0xA3 + ADDR_CONF_OK = 0xA7 + + INFO_LOCK_BYTE = 0x73 + CONF_OK_BYTE = 0x73 + + BOARD_RNODE = 0x31 + BOARD_HMBRW = 0x32 + BOARD_TBEAM = 0x33 + BOARD_HUZZAH32 = 0x34 + BOARD_GENERIC_ESP32 = 0x35 + BOARD_LORA32_V2_0 = 0x36 + BOARD_LORA32_V2_1 = 0x37 + +mapped_product = ROM.PRODUCT_RNODE +products = { + ROM.PRODUCT_RNODE: "RNode", + ROM.PRODUCT_HMBRW: "Hombrew RNode", + ROM.PRODUCT_TBEAM: "LilyGO T-Beam", + ROM.PRODUCT_T32_20: "LilyGO LoRa32 v2.0", + ROM.PRODUCT_T32_21: "LilyGO LoRa32 v2.1", + ROM.PRODUCT_H32_V2: "Heltec LoRa32 v2", +} + +platforms = { + ROM.PLATFORM_AVR: "AVR", + ROM.PLATFORM_ESP32:"ESP32", +} + +mcus = { + ROM.MCU_1284P: "ATmega1284P", + ROM.MCU_2560:"ATmega2560", + ROM.MCU_ESP32:"Espressif Systems ESP32", +} + +models = { + 0xA4: [410000000, 525000000, 14, "410 - 525 MHz", "rnode_firmware.hex"], + 0xA9: [820000000, 1020000000, 17, "820 - 1020 MHz", "rnode_firmware.hex"], + 0xA2: [410000000, 525000000, 17, "410 - 525 MHz", "rnode_firmware_ng21.zip"], + 0xA7: [820000000, 1020000000, 17, "820 - 1020 MHz", "rnode_firmware_ng21.zip"], + 0xA3: [410000000, 525000000, 17, "410 - 525 MHz", "rnode_firmware_ng20.zip"], + 0xA8: [820000000, 1020000000, 17, "820 - 1020 MHz", "rnode_firmware_ng20.zip"], + 0xB3: [420000000, 520000000, 17, "420 - 520 MHz", "rnode_firmware_lora32v20.zip"], + 0xB8: [850000000, 950000000, 17, "850 - 950 MHz", "rnode_firmware_lora32v20.zip"], + 0xB4: [420000000, 520000000, 17, "420 - 520 MHz", "rnode_firmware_lora32v21.zip"], + 0xB9: [850000000, 950000000, 17, "850 - 950 MHz", "rnode_firmware_lora32v21.zip"], + 0xC4: [420000000, 520000000, 17, "420 - 520 MHz", "rnode_firmware_heltec32v2.zip"], + 0xC9: [850000000, 950000000, 17, "850 - 950 MHz", "rnode_firmware_heltec32v2.zip"], + 0xE4: [420000000, 520000000, 17, "420 - 520 MHz", "rnode_firmware_tbeam.zip"], + 0xE9: [850000000, 950000000, 17, "850 - 950 MHz", "rnode_firmware_tbeam.zip"], + 0xFE: [100000000, 1100000000, 17, "(Band capabilities unknown)", None], + 0xFF: [100000000, 1100000000, 14, "(Band capabilities unknown)", None], +} + +CNF_DIR = None +UPD_DIR = None +FWD_DIR = None + +try: + CNF_DIR = os.path.expanduser("~/.local/rnodeconf") + UPD_DIR = CNF_DIR+"/update" + FWD_DIR = CNF_DIR+"/firmware" + + if not os.path.isdir(CNF_DIR): + os.makedirs(CNF_DIR) + if not os.path.isdir(UPD_DIR): + os.makedirs(UPD_DIR) + if not os.path.isdir(FWD_DIR): + os.makedirs(FWD_DIR) + +except Exception as e: + print("No access to directory "+str(CNF_DIR)+". This utility needs file system access to store firmware and data files. Cannot continue.") + print("The contained exception was:") + print(str(e)) + exit(99) + +squashvw = False + +class RNode(): + def __init__(self, serial_instance): + self.serial = serial_instance + self.timeout = 100 + + self.r_frequency = None + self.r_bandwidth = None + self.r_txpower = None + self.r_sf = None + self.r_state = None + self.r_lock = None + + self.sf = None + self.cr = None + self.txpower = None + self.frequency = None + self.bandwidth = None + + self.detected = None + + self.platform = None + self.mcu = None + self.eeprom = None + self.major_version = None + self.minor_version = None + self.version = None + + self.provisioned = None + self.product = None + self.board = None + self.model = None + self.hw_rev = None + self.made = None + self.serialno = None + self.checksum = None + self.device_hash = None + self.signature = None + self.signature_valid = False + self.locally_signed = False + self.vendor = None + + self.min_freq = None + self.max_freq = None + self.max_output = None + + self.configured = None + self.conf_sf = None + self.conf_cr = None + self.conf_txpower = None + self.conf_frequency = None + self.conf_bandwidth = None + + def disconnect(self): + self.leave() + self.serial.close() + + 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) + + while self.serial.is_open: + try: + data_waiting = self.serial.in_waiting + except Exception as e: + data_waiting = False + + if data_waiting: + byte = ord(self.serial.read(1)) + last_read_ms = int(time.time()*1000) + + if (in_frame and byte == KISS.FEND and command == KISS.CMD_ROM_READ): + self.eeprom = data_buffer + in_frame = False + 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) < 512): + if (len(data_buffer) == 0 and command == KISS.CMD_UNKNOWN): + command = byte + elif (command == KISS.CMD_ROM_READ): + 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_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("Radio reporting frequency is "+str(self.r_frequency/1000000.0)+" MHz") + 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("Radio reporting bandwidth is "+str(self.r_bandwidth/1000.0)+" KHz") + self.updateBitrate() + + elif (command == KISS.CMD_DEV_HASH): + 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) == 32): + self.device_hash = command_buffer + + 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.major_version = command_buffer[0] + self.minor_version = command_buffer[1] + self.updateVersion() + + elif (command == KISS.CMD_BOARD): + self.board = byte + + elif (command == KISS.CMD_PLATFORM): + self.platform = byte + + elif (command == KISS.CMD_MCU): + self.mcu = byte + + elif (command == KISS.CMD_TXPOWER): + self.r_txpower = byte + RNS.log("Radio reporting TX power is "+str(self.r_txpower)+" dBm") + elif (command == KISS.CMD_SF): + self.r_sf = byte + RNS.log("Radio reporting spreading factor is "+str(self.r_sf)) + self.updateBitrate() + elif (command == KISS.CMD_CR): + self.r_cr = byte + RNS.log("Radio reporting coding rate is "+str(self.r_cr)) + self.updateBitrate() + elif (command == KISS.CMD_RADIO_STATE): + self.r_state = byte + elif (command == KISS.CMD_RADIO_LOCK): + self.r_lock = byte + 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_ERROR): + if (byte == KISS.ERROR_INITRADIO): + RNS.log(str(self)+" hardware initialisation error (code "+RNS.hexrep(byte)+")") + elif (byte == KISS.ERROR_TXFAILED): + RNS.log(str(self)+" hardware TX error (code "+RNS.hexrep(byte)+")") + else: + RNS.log(str(self)+" hardware error (code "+RNS.hexrep(byte)+")") + elif (command == KISS.CMD_DETECT): + if byte == KISS.DETECT_RESP: + self.detected = True + else: + self.detected = False + + else: + 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") + data_buffer = b"" + in_frame = False + command = KISS.CMD_UNKNOWN + escape = False + sleep(0.08) + + except Exception as e: + raise e + exit() + + 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) + except Exception as e: + self.bitrate = 0 + + def updateVersion(self): + minstr = str(self.minor_version) + if len(minstr) == 1: + minstr = "0"+minstr + self.version = str(self.major_version)+"."+minstr + + 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.CMD_BOARD, 0x00, KISS.FEND, KISS.CMD_DEV_HASH, 0x01, 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 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_bluetooth(self): + kiss_command = bytes([KISS.FEND, KISS.CMD_BT_CTRL, 0x01, KISS.FEND]) + written = self.serial.write(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.serial.write(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.serial.write(kiss_command) + if written != len(kiss_command): + raise IOError("An IO error occurred while sending bluetooth pair command to device") + + def store_signature(self, signature_bytes): + data = KISS.escape(signature_bytes) + kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_DEV_SIG])+data+bytes([KISS.FEND]) + + written = self.serial.write(kiss_command) + if written != len(kiss_command): + raise IOError("An IO error occurred while sending signature to device") + + def set_firmware_hash(self, hash_bytes): + data = KISS.escape(hash_bytes) + kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_FW_HASH])+data+bytes([KISS.FEND]) + + written = self.serial.write(kiss_command) + if written != len(kiss_command): + raise IOError("An IO error occurred while sending firmware hash to device") + + def initRadio(self): + self.setFrequency() + self.setBandwidth() + self.setTXPower() + self.setSpreadingFactor() + self.setCodingRate() + self.setRadioState(KISS.RADIO_STATE_ON) + + 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): + 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 setNormalMode(self): + kiss_command = bytes([KISS.FEND, KISS.CMD_CONF_DELETE, 0x00, KISS.FEND]) + written = self.serial.write(kiss_command) + if written != len(kiss_command): + raise IOError("An IO error occurred while configuring device mode") + + def setTNCMode(self): + kiss_command = bytes([KISS.FEND, KISS.CMD_CONF_SAVE, 0x00, KISS.FEND]) + written = self.serial.write(kiss_command) + if written != len(kiss_command): + raise IOError("An IO error occurred while configuring device mode") + + if self.platform == ROM.PLATFORM_ESP32: + self.hard_reset() + + def wipe_eeprom(self): + kiss_command = bytes([KISS.FEND, KISS.CMD_ROM_WIPE, 0xf8, KISS.FEND]) + written = self.serial.write(kiss_command) + if written != len(kiss_command): + raise IOError("An IO error occurred while wiping EEPROM") + sleep(13); + + 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 write_eeprom(self, addr, byte): + write_payload = b"" + bytes([addr, byte]) + write_payload = KISS.escape(write_payload) + kiss_command = bytes([KISS.FEND, KISS.CMD_ROM_WRITE]) + write_payload + bytes([KISS.FEND]) + written = self.serial.write(kiss_command) + if written != len(kiss_command): + raise IOError("An IO error occurred while writing EEPROM") + + + def download_eeprom(self): + self.eeprom = None + kiss_command = bytes([KISS.FEND, KISS.CMD_ROM_READ, 0x00, KISS.FEND]) + written = self.serial.write(kiss_command) + if written != len(kiss_command): + raise IOError("An IO error occurred while configuring radio state") + + sleep(0.6) + if self.eeprom == None: + RNS.log("Could not download EEPROM from device. Is a valid firmware installed?") + exit() + else: + self.parse_eeprom() + + def parse_eeprom(self): + global squashvw; + try: + if self.eeprom[ROM.ADDR_INFO_LOCK] == ROM.INFO_LOCK_BYTE: + from cryptography.hazmat.primitives import hashes + from cryptography.hazmat.backends import default_backend + + self.provisioned = True + + self.product = self.eeprom[ROM.ADDR_PRODUCT] + self.model = self.eeprom[ROM.ADDR_MODEL] + self.hw_rev = self.eeprom[ROM.ADDR_HW_REV] + self.serialno = bytes([self.eeprom[ROM.ADDR_SERIAL], self.eeprom[ROM.ADDR_SERIAL+1], self.eeprom[ROM.ADDR_SERIAL+2], self.eeprom[ROM.ADDR_SERIAL+3]]) + self.made = bytes([self.eeprom[ROM.ADDR_MADE], self.eeprom[ROM.ADDR_MADE+1], self.eeprom[ROM.ADDR_MADE+2], self.eeprom[ROM.ADDR_MADE+3]]) + self.checksum = b"" + + + self.min_freq = models[self.model][0] + self.max_freq = models[self.model][1] + self.max_output = models[self.model][2] + + try: + self.min_freq = models[self.model][0] + self.max_freq = models[self.model][1] + self.max_output = models[self.model][2] + except Exception as e: + RNS.log("Exception") + RNS.log(str(e)) + self.min_freq = 0 + self.max_freq = 0 + self.max_output = 0 + + for i in range(0,16): + self.checksum = self.checksum+bytes([self.eeprom[ROM.ADDR_CHKSUM+i]]) + + self.signature = b"" + for i in range(0,128): + self.signature = self.signature+bytes([self.eeprom[ROM.ADDR_SIGNATURE+i]]) + + checksummed_info = b"" + bytes([self.product]) + bytes([self.model]) + bytes([self.hw_rev]) + self.serialno + self.made + digest = hashes.Hash(hashes.MD5(), backend=default_backend()) + digest.update(checksummed_info) + checksum = digest.finalize() + + if self.checksum != checksum: + self.provisioned = False + RNS.log("EEPROM checksum mismatch") + exit() + else: + RNS.log("EEPROM checksum correct") + + from cryptography.hazmat.primitives import serialization + from cryptography.hazmat.primitives.serialization import load_der_public_key + from cryptography.hazmat.primitives.serialization import load_der_private_key + from cryptography.hazmat.primitives.asymmetric import padding + + # Try loading local signing key for + # validation of self-signed devices + if os.path.isdir(FWD_DIR) and os.path.isfile(FWD_DIR+"/signing.key"): + private_bytes = None + try: + file = open(FWD_DIR+"/signing.key", "rb") + private_bytes = file.read() + file.close() + except Exception as e: + RNS.log("Could not load local signing key") + + try: + private_key = serialization.load_der_private_key( + private_bytes, + password=None, + backend=default_backend() + ) + public_key = private_key.public_key() + public_bytes = public_key.public_bytes( + encoding=serialization.Encoding.DER, + format=serialization.PublicFormat.SubjectPublicKeyInfo + ) + public_bytes_hex = RNS.hexrep(public_bytes, delimit=False) + + vendor_keys = [] + for known in known_keys: + vendor_keys.append(known[1]) + + if not public_bytes_hex in vendor_keys: + local_key_entry = ["LOCAL", public_bytes_hex] + known_keys.append(local_key_entry) + + except Exception as e: + RNS.log("Could not deserialize local signing key") + RNS.log(str(e)) + + for known in known_keys: + vendor = known[0] + public_hexrep = known[1] + public_bytes = bytes.fromhex(public_hexrep) + public_key = load_der_public_key(public_bytes, backend=default_backend()) + try: + public_key.verify( + self.signature, + self.checksum, + padding.PSS( + mgf=padding.MGF1(hashes.SHA256()), + salt_length=padding.PSS.MAX_LENGTH + ), + hashes.SHA256()) + if vendor == "LOCAL": + self.locally_signed = True + + self.signature_valid = True + self.vendor = vendor + except Exception as e: + pass + + if self.signature_valid: + RNS.log("Device signature validated") + else: + RNS.log("Device signature validation failed") + if not squashvw: + print(" ") + print(" WARNING! This device is NOT verifiable and should NOT be trusted.") + print(" Someone could have added privacy-breaking or malicious code to it.") + print(" ") + print(" Proceed at your own risk and responsibility! If you created this") + print(" device yourself, please read the documentation on how to sign your") + print(" device to avoid this warning.") + print(" ") + print(" Always use a firmware downloaded as binaries or compiled from source") + print(" from one of the following locations:") + print(" ") + print(" https://unsigned.io/rnode") + print(" https://github.com/markqvist/rnode_firmware") + print(" ") + print(" You can reflash and bootstrap this device to a verifiable state") + print(" by using this utility. It is recommended to do so NOW!") + print(" ") + print(" To initialise this device to a verifiable state, please run:") + print(" ") + print(" rnodeconf "+str(self.serial.name)+" --autoinstall") + print("") + + + + if self.eeprom[ROM.ADDR_CONF_OK] == ROM.CONF_OK_BYTE: + self.configured = True + self.conf_sf = self.eeprom[ROM.ADDR_CONF_SF] + self.conf_cr = self.eeprom[ROM.ADDR_CONF_CR] + self.conf_txpower = self.eeprom[ROM.ADDR_CONF_TXP] + self.conf_frequency = self.eeprom[ROM.ADDR_CONF_FREQ] << 24 | self.eeprom[ROM.ADDR_CONF_FREQ+1] << 16 | self.eeprom[ROM.ADDR_CONF_FREQ+2] << 8 | self.eeprom[ROM.ADDR_CONF_FREQ+3] + self.conf_bandwidth = self.eeprom[ROM.ADDR_CONF_BW] << 24 | self.eeprom[ROM.ADDR_CONF_BW+1] << 16 | self.eeprom[ROM.ADDR_CONF_BW+2] << 8 | self.eeprom[ROM.ADDR_CONF_BW+3] + else: + self.configured = False + else: + self.provisioned = False + except Exception as e: + self.provisioned = False + RNS.log("Invalid EEPROM data, could not parse device EEPROM.") + + + def device_probe(self): + sleep(2.5) + self.detect() + sleep(0.75) + if self.detected == True: + RNS.log("Device connected") + RNS.log("Current firmware version: "+self.version) + return True + else: + raise IOError("Got invalid response while detecting device") + +selected_version = None +selected_hash = None +firmware_version_url = "https://unsigned.io/firmware/latest/?variant=" +def ensure_firmware_file(fw_filename): + global selected_version, selected_hash, upd_nocheck + try: + if selected_version == None: + if not upd_nocheck: + try: + urlretrieve(firmware_version_url+fw_filename, UPD_DIR+"/"+fw_filename+".version.latest") + except Exception as e: + RNS.log("Failed to retrive latest version information for your board.") + RNS.log("Check your internet connection and try again.") + RNS.log("If you don't have Internet access currently, use the --fw-version option to manually specify a version.") + exit() + + import shutil + file = open(UPD_DIR+"/"+fw_filename+".version.latest", "rb") + release_info = file.read().decode("utf-8").strip() + selected_version = release_info.split()[0] + selected_hash = release_info.split()[1] + if not os.path.isdir(UPD_DIR+"/"+selected_version): + os.makedirs(UPD_DIR+"/"+selected_version) + shutil.copy(UPD_DIR+"/"+fw_filename+".version.latest", UPD_DIR+"/"+selected_version+"/"+fw_filename+".version") + RNS.log("The latest firmware for this board is version "+selected_version) + + else: + RNS.log("Online firmware version check was disabled, but no firmware version specified for install.") + RNS.log("use the --fw-version option to manually specify a version.") + exit(98) + + update_target_url = firmware_update_url+selected_version+"/"+fw_filename + + try: + if not os.path.isdir(UPD_DIR+"/"+selected_version): + os.makedirs(UPD_DIR+"/"+selected_version) + + if not os.path.isfile(UPD_DIR+"/"+selected_version+"/"+fw_filename): + RNS.log("Downloading missing firmware file: "+fw_filename+" for version "+selected_version) + urlretrieve(update_target_url, UPD_DIR+"/"+selected_version+"/"+fw_filename) + RNS.log("Firmware file downloaded") + else: + RNS.log("Using existing firmware file: "+fw_filename+" for version "+selected_version) + + try: + if selected_hash == None: + try: + file = open(UPD_DIR+"/"+selected_version+"/"+fw_filename+".version", "rb") + release_info = file.read().decode("utf-8").strip() + selected_hash = release_info.split()[1] + except Exception as e: + RNS.log("Could not read locally cached release information.") + RNS.log("You can clear the cache with the --clear-cache option and try again.") + + if selected_hash == None: + RNS.log("No release hash found for "+fw_filename+". The firmware integrity could not be verified.") + exit(97) + + RNS.log("Veryfying firmware integrity...") + fw_file = open(UPD_DIR+"/"+selected_version+"/"+fw_filename, "rb") + expected_hash = bytes.fromhex(selected_hash) + file_hash = hashlib.sha256(fw_file.read()).hexdigest() + if file_hash == selected_hash: + pass + else: + RNS.log("") + RNS.log("Firmware corrupt.") + exit(96) + + except Exception as e: + RNS.log("An error occurred while checking firmware file integrity. The contained exception was:") + RNS.log(str(e)) + exit(95) + + except Exception as e: + RNS.log("Could not download required firmware file: ") + RNS.log(str(update_target_url)) + RNS.log("The contained exception was:") + RNS.log(str(e)) + exit() + + except Exception as e: + RNS.log("An error occurred while reading version information for "+str(fw_filename)+". The contained exception was:") + RNS.log(str(e)) + exit() + +def rnode_open_serial(port): + import serial + return serial.Serial( + port = port, + baudrate = rnode_baudrate, + bytesize = 8, + parity = serial.PARITY_NONE, + stopbits = 1, + xonxoff = False, + rtscts = False, + timeout = 0, + inter_byte_timeout = None, + write_timeout = None, + dsrdtr = False + ) + +device_signer = None +force_update = False +upd_nocheck = False +def main(): + global mapped_product, mapped_model, fw_filename, selected_version, force_update, upd_nocheck, device_signer + + try: + if not util.find_spec("serial"): + raise ImportError("Serial module could not be found") + except ImportError: + print("") + print("RNode Config Utility needs pyserial to work.") + print("You can install it with: pip3 install pyserial") + print("") + exit() + + try: + if not util.find_spec("cryptography"): + raise ImportError("Cryptography module could not be found") + except ImportError: + print("") + print("RNode Config Utility needs the cryptography module to work.") + print("You can install it with: pip3 install cryptography") + print("") + exit() + + import serial + from serial.tools import list_ports + + try: + parser = argparse.ArgumentParser(description="RNode Configuration and firmware utility. This program allows you to change various settings and startup modes of RNode. It can also install, flash and update the firmware on supported devices.") + parser.add_argument("-i", "--info", action="store_true", help="Show device info") + parser.add_argument("-a", "--autoinstall", action="store_true", help="Automatic installation on various supported devices") + parser.add_argument("-u", "--update", action="store_true", help="Update firmware to the latest version") + parser.add_argument("-U", "--force-update", action="store_true", help="Update to specified firmware even if version matches or is older than installed version") + parser.add_argument("--fw-version", action="store", metavar="version", default=None, help="Use a specific firmware version for update or autoinstall") + parser.add_argument("--nocheck", action="store_true", help="Don't check for firmware updates online") + parser.add_argument("-C", "--clear-cache", action="store_true", help="Clear locally cached firmware files") + + parser.add_argument("-N", "--normal", action="store_true", help="Switch device to normal mode") + parser.add_argument("-T", "--tnc", action="store_true", help="Switch device to TNC mode") + + parser.add_argument("-b", "--bluetooth-on", action="store_true", help="Turn device bluetooth on") + parser.add_argument("-B", "--bluetooth-off", action="store_true", help="Turn device bluetooth off") + parser.add_argument("-p", "--bluetooth-pair", action="store_true", help="Put device into bluetooth pairing mode") + + parser.add_argument("--freq", action="store", metavar="Hz", type=int, default=None, help="Frequency in Hz for TNC mode") + parser.add_argument("--bw", action="store", metavar="Hz", type=int, default=None, help="Bandwidth in Hz for TNC mode") + parser.add_argument("--txp", action="store", metavar="dBm", type=int, default=None, help="TX power in dBm for TNC mode") + parser.add_argument("--sf", action="store", metavar="factor", type=int, default=None, help="Spreading factor for TNC mode (7 - 12)") + parser.add_argument("--cr", action="store", metavar="rate", type=int, default=None, help="Coding rate for TNC mode (5 - 8)") + + parser.add_argument("--eeprom-backup", action="store_true", help="Backup EEPROM to file") + parser.add_argument("--eeprom-dump", action="store_true", help="Dump EEPROM to console") + parser.add_argument("--eeprom-wipe", action="store_true", help="Unlock and wipe EEPROM") + + parser.add_argument("--version", action="store_true", help="Print program version and exit") + + parser.add_argument("-f", "--flash", action="store_true", help=argparse.SUPPRESS) # Flash firmware and bootstrap EEPROM + parser.add_argument("-r", "--rom", action="store_true", help=argparse.SUPPRESS) # Bootstrap EEPROM without flashing firmware + parser.add_argument("-k", "--key", action="store_true", help=argparse.SUPPRESS) # Generate a new signing key and exit + parser.add_argument("-P", "--public", action="store_true", help=argparse.SUPPRESS) # Display public part of signing key + parser.add_argument("-S", "--sign", action="store_true", help=argparse.SUPPRESS) # Display public part of signing key + parser.add_argument("-H", "--firmware-hash", action="store", help=argparse.SUPPRESS) # Display public part of signing key + parser.add_argument("--platform", action="store", metavar="platform", type=str, default=None, help=argparse.SUPPRESS) # Platform specification for device bootstrap + parser.add_argument("--product", action="store", metavar="product", type=str, default=None, help=argparse.SUPPRESS) # Product specification for device bootstrap + parser.add_argument("--model", action="store", metavar="model", type=str, default=None, help=argparse.SUPPRESS) # Model code for device bootstrap + parser.add_argument("--hwrev", action="store", metavar="revision", type=int, default=None, help=argparse.SUPPRESS) # Hardware revision for device bootstrap + + parser.add_argument("port", nargs="?", default=None, help="serial port where RNode is attached", type=str) + args = parser.parse_args() + + def print_donation_block(): + print(" Ethereum : "+eth_addr) + print(" Bitcoin : "+btc_addr) + print(" Monero : "+xmr_addr) + print(" Ko-Fi : https://ko-fi.com/markqvist") + print("") + print(" Info : https://unsigned.io/") + print(" Code : https://github.com/markqvist") + + if args.version: + print("rnodeconf "+program_version) + exit(0) + + if args.clear_cache: + RNS.log("Clearing local firmware cache...") + import shutil + shutil.rmtree(UPD_DIR) + RNS.log("Done") + exit(0) + + if args.fw_version != None: + selected_version = args.fw_version + + if args.force_update: + force_update = True + + if args.nocheck: + upd_nocheck = True + + if args.public or args.key or args.flash or args.rom or args.autoinstall: + from cryptography.hazmat.primitives import hashes + from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives import serialization + from cryptography.hazmat.primitives.serialization import load_der_public_key + from cryptography.hazmat.primitives.serialization import load_der_private_key + from cryptography.hazmat.primitives.asymmetric import rsa + from cryptography.hazmat.primitives.asymmetric import padding + + if args.autoinstall: + print("\nHello!\n\nThis guide will help you install the RNode firmware on supported") + print("and homebrew devices. Please connect the device you wish to set\nup now. Hit enter when it is connected.") + input() + + global squashvw + squashvw = True + + selected_port = None + if not args.port: + ports = list_ports.comports() + portlist = [] + for port in ports: + portlist.insert(0, port) + + pi = 1 + print("Detected serial ports:") + for port in portlist: + print(" ["+str(pi)+"] "+str(port.device)+" ("+str(port.product)+", "+str(port.serial_number)+")") + pi += 1 + + print("\nWhat serial port is your device connected to? ", end="") + try: + c_port = int(input()) + if c_port < 1 or c_port > len(ports): + raise ValueError() + + selected_port = portlist[c_port-1] + except Exception as e: + print("That port does not exist, exiting now.") + exit() + + if selected_port == None: + print("Could not select port, exiting now.") + exit() + + port_path = selected_port.device + port_product = selected_port.product + port_serialno = selected_port.serial_number + + print("\nOk, using device on "+str(port_path)+" ("+str(port_product)+", "+str(port_serialno)+")") + + else: + ports = list_ports.comports() + + for port in ports: + if port.device == args.port: + selected_port = port + + if selected_port == None: + print("Could not find specified port "+str(args.port)+", exiting now") + exit() + + port_path = selected_port.device + port_product = selected_port.product + port_serialno = selected_port.serial_number + + print("\nUsing device on "+str(port_path)) + + print("\nProbing device...") + + try: + rnode_serial = rnode_open_serial(port_path) + except Exception as e: + RNS.log("Could not open the specified serial port. The contained exception was:") + RNS.log(str(e)) + exit() + + rnode = RNode(rnode_serial) + thread = threading.Thread(target=rnode.readLoop, daemon=True).start() + try: + rnode.device_probe() + except Exception as e: + RNS.log("No answer from device") + + if rnode.detected: + RNS.log("Trying to read EEPROM...") + rnode.download_eeprom() + + if rnode.provisioned and rnode.signature_valid: + print("\nThis device is already installed and provisioned. No further action will") + print("be taken. If you wish to completely reinstall this device, you must first") + print("wipe the current EEPROM. See the help for more info.\n\nExiting now.") + exit() + + if rnode.detected: + print("\nThe device seems to have an RNode firmware installed, but it was not") + print("provisioned correctly, or it is corrupt. We are going to reinstall the") + print("correct firmware and provision it.") + else: + print("\nIt looks like this is a fresh device with no RNode firmware.") + + print("What kind of device is this?\n") + print("[1] RNode from Unsigned.io") + print("[2] Homebrew RNode") + print("[3] LilyGO T-Beam") + print("[4] LilyGO LoRa32 v2.0") + print("[5] LilyGO LoRa32 v2.1") + print("[6] Heltec LoRa32 v2") + print("\n? ", end="") + + selected_product = None + try: + c_dev = int(input()) + if c_dev < 1 or c_dev > 6: + raise ValueError() + elif c_dev == 1: + selected_product = ROM.PRODUCT_RNODE + elif c_dev == 2: + selected_product = ROM.PRODUCT_HMBRW + print("") + print("---------------------------------------------------------------------------") + print("Important! Using RNode firmware on homebrew devices should currently be") + print("considered experimental. It is not intended for production or critical use.") + print("The currently supplied firmware is provided AS-IS as a courtesey to those") + print("who would like to experiment with it. Hit enter to continue.") + print("---------------------------------------------------------------------------") + input() + elif c_dev == 3: + selected_product = ROM.PRODUCT_TBEAM + print("") + print("---------------------------------------------------------------------------") + print("Important! Using RNode firmware on T-Beam devices should currently be") + print("considered experimental. It is not intended for production or critical use.") + print("The currently supplied firmware is provided AS-IS as a courtesey to those") + print("who would like to experiment with it. Hit enter to continue.") + print("---------------------------------------------------------------------------") + input() + elif c_dev == 4: + selected_product = ROM.PRODUCT_T32_20 + print("") + print("---------------------------------------------------------------------------") + print("Important! Using RNode firmware on LoRa32 devices should currently be") + print("considered experimental. It is not intended for production or critical use.") + print("The currently supplied firmware is provided AS-IS as a courtesey to those") + print("who would like to experiment with it. Hit enter to continue.") + print("---------------------------------------------------------------------------") + input() + elif c_dev == 5: + selected_product = ROM.PRODUCT_T32_21 + print("") + print("---------------------------------------------------------------------------") + print("Important! Using RNode firmware on LoRa32 devices should currently be") + print("considered experimental. It is not intended for production or critical use.") + print("The currently supplied firmware is provided AS-IS as a courtesey to those") + print("who would like to experiment with it. Hit enter to continue.") + print("---------------------------------------------------------------------------") + input() + elif c_dev == 6: + selected_product = ROM.PRODUCT_H32_V2 + print("") + print("---------------------------------------------------------------------------") + print("Important! Using RNode firmware on Heltec devices should currently be") + print("considered experimental. It is not intended for production or critical use.") + print("The currently supplied firmware is provided AS-IS as a courtesey to those") + print("who would like to experiment with it. Hit enter to continue.") + print("---------------------------------------------------------------------------") + input() + except Exception as e: + print("That device type does not exist, exiting now.") + exit() + + selected_platform = None + selected_model = None + selected_mcu = None + + if selected_product == ROM.PRODUCT_HMBRW: + print("\nWhat kind of microcontroller is your board based on?\n") + print("[1] AVR ATmega1284P") + print("[2] AVR ATmega2560") + print("[3] Espressif Systems ESP32") + print("\n? ", end="") + try: + c_mcu = int(input()) + if c_mcu < 1 or c_mcu > 3: + raise ValueError() + elif c_mcu == 1: + selected_mcu = ROM.MCU_1284P + selected_platform = ROM.PLATFORM_AVR + elif c_mcu == 2: + selected_mcu = ROM.MCU_2560 + selected_platform = ROM.PLATFORM_AVR + elif c_mcu == 3: + selected_mcu = ROM.MCU_ESP32 + selected_platform = ROM.PLATFORM_ESP32 + selected_model = ROM.MODEL_FF + + except Exception as e: + print("That MCU type does not exist, exiting now.") + exit() + + print("\nWhat transceiver module does your board use?\n") + print("[1] SX1276/SX1278 with antenna port on PA_BOOST pin") + print("[2] SX1276/SX1278 with antenna port on RFO pin") + print("\n? ", end="") + try: + c_trxm = int(input()) + if c_trxm < 1 or c_trxm > 3: + raise ValueError() + elif c_trxm == 1: + selected_model = ROM.MODEL_FE + elif c_trxm == 2: + selected_model = ROM.MODEL_FF + + except Exception as e: + print("That transceiver type does not exist, exiting now.") + exit() + + + elif selected_product == ROM.PRODUCT_RNODE: + selected_mcu = ROM.MCU_1284P + print("\nWhat model is this RNode?\n") + print("[1] Original v1.x RNode, 410 - 525 MHz") + print("[2] Original v1.x RNode, 820 - 1020 MHz") + print("[3] Prototype v2 RNode, 410 - 525 MHz") + print("[4] Prototype v2 RNode, 820 - 1020 MHz") + print("[5] RNode v2.x, 410 - 525 MHz") + print("[6] RNode v2.x, 820 - 1020 MHz") + print("\n? ", end="") + try: + c_model = int(input()) + if c_model < 1 or c_model > 6: + raise ValueError() + elif c_model == 1: + selected_model = ROM.MODEL_A4 + selected_platform = ROM.PLATFORM_AVR + elif c_model == 2: + selected_model = ROM.MODEL_A9 + selected_platform = ROM.PLATFORM_AVR + elif c_model == 3: + selected_model = ROM.MODEL_A3 + selected_mcu = ROM.MCU_ESP32 + selected_platform = ROM.PLATFORM_ESP32 + elif c_model == 4: + selected_model = ROM.MODEL_A8 + selected_mcu = ROM.MCU_ESP32 + selected_platform = ROM.PLATFORM_ESP32 + elif c_model == 5: + selected_model = ROM.MODEL_A2 + selected_mcu = ROM.MCU_ESP32 + selected_platform = ROM.PLATFORM_ESP32 + elif c_model == 6: + selected_model = ROM.MODEL_A7 + selected_mcu = ROM.MCU_ESP32 + selected_platform = ROM.PLATFORM_ESP32 + except Exception as e: + print("That model does not exist, exiting now.") + exit() + + elif selected_product == ROM.PRODUCT_TBEAM: + selected_mcu = ROM.MCU_ESP32 + print("\nWhat band is this T-Beam for?\n") + print("[1] 433 MHz") + print("[2] 868 MHz") + print("[3] 915 MHz") + print("[4] 923 MHz") + print("\n? ", end="") + try: + c_model = int(input()) + if c_model < 1 or c_model > 4: + raise ValueError() + elif c_model == 1: + selected_model = ROM.MODEL_E4 + selected_platform = ROM.PLATFORM_ESP32 + elif c_model > 1: + selected_model = ROM.MODEL_E9 + selected_platform = ROM.PLATFORM_ESP32 + except Exception as e: + print("That band does not exist, exiting now.") + exit() + + elif selected_product == ROM.PRODUCT_T32_20: + selected_mcu = ROM.MCU_ESP32 + print("\nWhat band is this LoRa32 for?\n") + print("[1] 433 MHz") + print("[2] 868 MHz") + print("[3] 915 MHz") + print("[4] 923 MHz") + print("\n? ", end="") + try: + c_model = int(input()) + if c_model < 1 or c_model > 4: + raise ValueError() + elif c_model == 1: + selected_model = ROM.MODEL_B3 + selected_platform = ROM.PLATFORM_ESP32 + elif c_model > 1: + selected_model = ROM.MODEL_B8 + selected_platform = ROM.PLATFORM_ESP32 + except Exception as e: + print("That band does not exist, exiting now.") + exit() + + elif selected_product == ROM.PRODUCT_T32_21: + selected_mcu = ROM.MCU_ESP32 + print("\nWhat band is this LoRa32 for?\n") + print("[1] 433 MHz") + print("[2] 868 MHz") + print("[3] 915 MHz") + print("[4] 923 MHz") + print("\n? ", end="") + try: + c_model = int(input()) + if c_model < 1 or c_model > 4: + raise ValueError() + elif c_model == 1: + selected_model = ROM.MODEL_B4 + selected_platform = ROM.PLATFORM_ESP32 + elif c_model > 1: + selected_model = ROM.MODEL_B9 + selected_platform = ROM.PLATFORM_ESP32 + except Exception as e: + print("That band does not exist, exiting now.") + exit() + + elif selected_product == ROM.PRODUCT_H32_V2: + selected_mcu = ROM.MCU_ESP32 + print("\nWhat band is this Heltec LoRa32 for?\n") + print("[1] 433 MHz") + print("[2] 868 MHz") + print("[3] 915 MHz") + print("[4] 923 MHz") + print("\n? ", end="") + try: + c_model = int(input()) + if c_model < 1 or c_model > 4: + raise ValueError() + elif c_model == 1: + selected_model = ROM.MODEL_C4 + selected_platform = ROM.PLATFORM_ESP32 + elif c_model > 1: + selected_model = ROM.MODEL_C9 + selected_platform = ROM.PLATFORM_ESP32 + except Exception as e: + print("That band does not exist, exiting now.") + exit() + + if selected_model != ROM.MODEL_FF and selected_model != ROM.MODEL_FE: + fw_filename = models[selected_model][4] + + else: + if selected_platform == ROM.PLATFORM_AVR: + if selected_mcu == ROM.MCU_1284P: + fw_filename = "rnode_firmware.hex" + elif selected_mcu == ROM.MCU_2560: + fw_filename = "rnode_firmware_m2560.hex" + + elif selected_platform == ROM.PLATFORM_ESP32: + fw_filename = None + print("\nWhat kind of ESP32 board is this?\n") + print("[1] Adafruit Feather ESP32 (HUZZAH32)") + print("[2] Generic ESP32 board") + print("\n? ", end="") + try: + c_eboard = int(input()) + if c_eboard < 1 or c_eboard > 2: + raise ValueError() + elif c_eboard == 1: + fw_filename = "rnode_firmware_featheresp32.zip" + elif c_eboard == 2: + fw_filename = "rnode_firmware_esp32_generic.zip" + except Exception as e: + print("That ESP32 board does not exist, exiting now.") + exit() + + if fw_filename == None: + print("") + print("Sorry, no firmware for your board currently exists.") + print("Help making it a reality by contributing code or by") + print("donating to the project.") + print("") + print_donation_block() + print("") + exit() + + print("\nOk, that should be all the information we need. Please confirm the following") + print("summary before proceeding. In the next step, the device will be flashed and") + print("provisioned, so make that you are satisfied with your choices.\n") + + print("Serial port : "+str(selected_port.device)) + print("Device type : "+str(products[selected_product])+" "+str(models[selected_model][3])) + print("Platform : "+str(platforms[selected_platform])) + print("Device MCU : "+str(mcus[selected_mcu])) + print("Firmware file : "+str(fw_filename)) + + print("\nIs the above correct? [y/N] ", end="") + try: + c_ok = input().lower() + if c_ok != "y": + raise ValueError() + except Exception as e: + print("OK, aborting now.") + exit() + + args.key = True + args.port = selected_port.device + args.platform = selected_platform + args.hwrev = 1 + mapped_model = selected_model + mapped_product = selected_product + args.update = False + args.flash = True + + try: + RNS.log("Checking firmware file availability...") + ensure_firmware_file(fw_filename) + except Exception as e: + RNS.log("Could not obain firmware package for your board") + RNS.log("The contained exception was: "+str(e)) + exit() + + rnode.disconnect() + + if args.public: + private_bytes = None + try: + file = open(FWD_DIR+"/signing.key", "rb") + private_bytes = file.read() + file.close() + except Exception as e: + RNS.log("Could not load EEPROM signing key") + + try: + private_key = serialization.load_der_private_key( + private_bytes, + password=None, + backend=default_backend() + ) + public_key = private_key.public_key() + public_bytes = public_key.public_bytes( + encoding=serialization.Encoding.DER, + format=serialization.PublicFormat.SubjectPublicKeyInfo + ) + RNS.log("EEPROM Signing Public key:") + RNS.log(RNS.hexrep(public_bytes, delimit=False)) + + except Exception as e: + RNS.log("Could not deserialize signing key") + RNS.log(str(e)) + + try: + device_signer = RNS.Identity.from_file(FWD_DIR+"/device.key") + RNS.log("") + RNS.log("Device Signing Public key:") + RNS.log(RNS.hexrep(device_signer.get_public_key()[32:], delimit=True)) + + except Exception as e: + RNS.log("Could not load device signing key") + + + exit() + + if args.key: + if not os.path.isfile(FWD_DIR+"/device.key"): + try: + RNS.log("Generating a new device signing key...") + device_signer = RNS.Identity() + device_signer.to_file(FWD_DIR+"/device.key") + RNS.log("Device signing key written to "+str(FWD_DIR+"/device.key")) + except Exception as e: + RNS.log("Could not create new device signing key at "+str(FWD_DIR+"/device.key")+". The contained exception was:") + RNS.log(str(e)) + RNS.log("Please ensure filesystem access and try again.") + exit(81) + else: + try: + device_signer = RNS.Identity.from_file(FWD_DIR+"/device.key") + except Exception as e: + RNS.log("Could not load device signing key from "+str(FWD_DIR+"/device.key")+". The contained exception was:") + RNS.log(str(e)) + RNS.log("Please restore or clear the key and try again.") + exit(82) + + if not os.path.isfile(FWD_DIR+"/signing.key"): + RNS.log("Generating a new EEPROM signing key...") + private_key = rsa.generate_private_key( + public_exponent=65537, + key_size=1024, + backend=default_backend() + ) + private_bytes = private_key.private_bytes( + encoding=serialization.Encoding.DER, + format=serialization.PrivateFormat.PKCS8, + encryption_algorithm=serialization.NoEncryption() + ) + public_key = private_key.public_key() + public_bytes = public_key.public_bytes( + encoding=serialization.Encoding.DER, + format=serialization.PublicFormat.SubjectPublicKeyInfo + ) + os.makedirs(FWD_DIR, exist_ok=True) + if os.path.isdir(FWD_DIR): + if os.path.isfile(FWD_DIR+"/signing.key"): + if not args.autoinstall: + RNS.log("EEPROM Signing key already exists, not overwriting!") + RNS.log("Manually delete this key to create a new one.") + else: + file = open(FWD_DIR+"/signing.key", "wb") + file.write(private_bytes) + file.close() + + if not squashvw: + RNS.log("Wrote signing key") + RNS.log("Public key:") + RNS.log(RNS.hexrep(public_bytes, delimit=False)) + else: + RNS.log("The firmware directory does not exist, can't write key!") + + if not args.autoinstall: + exit() + + def get_partition_hash(partition_file): + try: + firmware_data = open(partition_file, "rb").read() + calc_hash = hashlib.sha256(firmware_data[0:-32]).digest() + part_hash = firmware_data[-32:] + + if calc_hash == part_hash: + return part_hash + else: + return None + except Exception as e: + RNS.log("Could not calculate firmware partition hash. The contained exception was:") + RNS.log(str(e)) + + def get_flasher_call(platform, fw_filename): + global selected_version + from shutil import which + if platform == "unzip": + flasher = "unzip" + if which(flasher) is not None: + return [flasher, "-o", UPD_DIR+"/"+selected_version+"/"+fw_filename, "-d", UPD_DIR+"/"+selected_version] + else: + RNS.log("") + RNS.log("You do not currently have the \""+flasher+"\" program installed on your system.") + RNS.log("Unfortunately, that means we can't proceed, since it is needed to flash your") + RNS.log("board. You can install it via your package manager, for example:") + RNS.log("") + RNS.log(" sudo apt install "+flasher) + RNS.log("") + RNS.log("Please install \""+flasher+"\" and try again.") + exit() + elif platform == ROM.PLATFORM_AVR: + flasher = "avrdude" + if which(flasher) is not None: + # avrdude -C/home/markqvist/.arduino15/packages/arduino/tools/avrdude/6.3.0-arduino17/etc/avrdude.conf -q -q -V -patmega2560 -cwiring -P/dev/ttyACM0 -b115200 -D -Uflash:w:/tmp/arduino-sketch-0E260F46C421A84A7CBAD48E859C8E64/RNode_Firmware.ino.hex:i + # avrdude -q -q -V -patmega2560 -cwiring -P/dev/ttyACM0 -b115200 -D -Uflash:w:/tmp/arduino-sketch-0E260F46C421A84A7CBAD48E859C8E64/RNode_Firmware.ino.hex:i + if fw_filename == "rnode_firmware.hex": + return [flasher, "-P", args.port, "-p", "m1284p", "-c", "arduino", "-b", "115200", "-U", "flash:w:"+UPD_DIR+"/"+selected_version+"/"+fw_filename+":i"] + elif fw_filename == "rnode_firmware_m2560.hex": + return [flasher, "-P", args.port, "-p", "atmega2560", "-c", "wiring", "-D", "-b", "115200", "-U", "flash:w:"+UPD_DIR+"/"+selected_version+"/"+fw_filename] + else: + RNS.log("") + RNS.log("You do not currently have the \""+flasher+"\" program installed on your system.") + RNS.log("Unfortunately, that means we can't proceed, since it is needed to flash your") + RNS.log("board. You can install it via your package manager, for example:") + RNS.log("") + RNS.log(" sudo apt install avrdude") + RNS.log("") + RNS.log("Please install \""+flasher+"\" and try again.") + exit() + elif platform == ROM.PLATFORM_ESP32: + flasher = UPD_DIR+"/"+selected_version+"/esptool.py" + if which(flasher) is not None: + if fw_filename == "rnode_firmware_tbeam.zip": + return [ + flasher, + "--chip", "esp32", + "--port", args.port, + "--baud", "921600", + "--before", "default_reset", + "--after", "hard_reset", + "write_flash", "-z", + "--flash_mode", "dio", + "--flash_freq", "80m", + "--flash_size", "4MB", + "0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_tbeam.boot_app0", + "0x1000", UPD_DIR+"/"+selected_version+"/rnode_firmware_tbeam.bootloader", + "0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_tbeam.bin", + "0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_tbeam.partitions", + ] + elif fw_filename == "rnode_firmware_lora32v20.zip": + return [ + flasher, + "--chip", "esp32", + "--port", args.port, + "--baud", "921600", + "--before", "default_reset", + "--after", "hard_reset", + "write_flash", "-z", + "--flash_mode", "dio", + "--flash_freq", "80m", + "--flash_size", "4MB", + "0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v20.boot_app0", + "0x1000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v20.bootloader", + "0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v20.bin", + "0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v20.partitions", + ] + elif fw_filename == "rnode_firmware_lora32v21.zip": + return [ + flasher, + "--chip", "esp32", + "--port", args.port, + "--baud", "921600", + "--before", "default_reset", + "--after", "hard_reset", + "write_flash", "-z", + "--flash_mode", "dio", + "--flash_freq", "80m", + "--flash_size", "4MB", + "0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v21.boot_app0", + "0x1000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v21.bootloader", + "0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v21.bin", + "0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v21.partitions", + ] + elif fw_filename == "rnode_firmware_heltec32v2.zip": + return [ + flasher, + "--chip", "esp32", + "--port", args.port, + "--baud", "921600", + "--before", "default_reset", + "--after", "hard_reset", + "write_flash", "-z", + "--flash_mode", "dio", + "--flash_freq", "80m", + "--flash_size", "8MB", + "0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_heltec32v2.boot_app0", + "0x1000", UPD_DIR+"/"+selected_version+"/rnode_firmware_heltec32v2.bootloader", + "0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_heltec32v2.bin", + "0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_heltec32v2.partitions", + ] + elif fw_filename == "rnode_firmware_featheresp32.zip": + return [ + flasher, + "--chip", "esp32", + "--port", args.port, + "--baud", "921600", + "--before", "default_reset", + "--after", "hard_reset", + "write_flash", "-z", + "--flash_mode", "dio", + "--flash_freq", "80m", + "--flash_size", "4MB", + "0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_featheresp32.boot_app0", + "0x1000", UPD_DIR+"/"+selected_version+"/rnode_firmware_featheresp32.bootloader", + "0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_featheresp32.bin", + "0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_featheresp32.partitions", + ] + elif fw_filename == "rnode_firmware_esp32_generic.zip": + return [ + flasher, + "--chip", "esp32", + "--port", args.port, + "--baud", "921600", + "--before", "default_reset", + "--after", "hard_reset", + "write_flash", "-z", + "--flash_mode", "dio", + "--flash_freq", "80m", + "--flash_size", "4MB", + "0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_esp32_generic.boot_app0", + "0x1000", UPD_DIR+"/"+selected_version+"/rnode_firmware_esp32_generic.bootloader", + "0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_esp32_generic.bin", + "0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_esp32_generic.partitions", + ] + elif fw_filename == "rnode_firmware_ng20.zip": + return [ + flasher, + "--chip", "esp32", + "--port", args.port, + "--baud", "921600", + "--before", "default_reset", + "--after", "hard_reset", + "write_flash", "-z", + "--flash_mode", "dio", + "--flash_freq", "80m", + "--flash_size", "4MB", + "0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_ng20.boot_app0", + "0x1000", UPD_DIR+"/"+selected_version+"/rnode_firmware_ng20.bootloader", + "0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_ng20.bin", + "0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_ng20.partitions", + ] + elif fw_filename == "rnode_firmware_ng21.zip": + return [ + flasher, + "--chip", "esp32", + "--port", args.port, + "--baud", "921600", + "--before", "default_reset", + "--after", "hard_reset", + "write_flash", "-z", + "--flash_mode", "dio", + "--flash_freq", "80m", + "--flash_size", "4MB", + "0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_ng21.boot_app0", + "0x1000", UPD_DIR+"/"+selected_version+"/rnode_firmware_ng21.bootloader", + "0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_ng21.bin", + "0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_ng21.partitions", + ] + else: + RNS.log("No flasher available for this board, cannot install firmware.") + else: + RNS.log("") + RNS.log("You do not currently have the \""+flasher+"\" program installed on your system.") + RNS.log("Unfortunately, that means we can't proceed, since it is needed to flash your") + RNS.log("board. You can install it via your package manager, for example:") + RNS.log("") + RNS.log(" sudo apt install esptool") + RNS.log("") + RNS.log("Please install \""+flasher+"\" and try again.") + exit() + + if args.port: + wants_fw_provision = False + if args.flash: + from subprocess import call + + if fw_filename == None: + fw_filename = "rnode_firmware.hex" + + if args.platform == None: + args.platform = ROM.PLATFORM_AVR + + if selected_version == None: + RNS.log("Missing parameters, cannot continue") + exit(68) + + fw_src = UPD_DIR+"/"+selected_version+"/" + if os.path.isfile(fw_src+fw_filename): + try: + if fw_filename.endswith(".zip"): + RNS.log("Extracting firmware...") + unzip_status = call(get_flasher_call("unzip", fw_filename)) + if unzip_status == 0: + RNS.log("Firmware extracted") + else: + RNS.log("Could not extract firmware from downloaded zip file") + exit() + + RNS.log("Flashing RNode firmware to device on "+args.port) + from subprocess import call + rc = get_flasher_call(args.platform, fw_filename) + flash_status = call(rc) + if flash_status == 0: + RNS.log("Done flashing") + args.rom = True + if args.platform == ROM.PLATFORM_ESP32: + wants_fw_provision = True + RNS.log("Waiting for ESP32 reset...") + time.sleep(7) + else: + exit() + + except Exception as e: + RNS.log("Error while flashing") + RNS.log(str(e)) + exit(1) + else: + RNS.log("Firmware file not found") + exit() + + RNS.log("Opening serial port "+args.port+"...") + try: + rnode_port = args.port + rnode_serial = rnode_open_serial(rnode_port) + except Exception as e: + RNS.log("Could not open the specified serial port. The contained exception was:") + RNS.log(str(e)) + exit() + + rnode = RNode(rnode_serial) + thread = threading.Thread(target=rnode.readLoop, daemon=True).start() + + try: + rnode.device_probe() + except Exception as e: + RNS.log("Serial port opened, but RNode did not respond. Is a valid firmware installed?") + print(e) + exit() + + if rnode.detected: + if rnode.platform == None or rnode.mcu == None: + rnode.platform = ROM.PLATFORM_AVR + rnode.mcu = ROM.MCU_1284P + + + if args.eeprom_wipe: + RNS.log("WARNING: EEPROM is being wiped! Power down device NOW if you do not want this!") + rnode.wipe_eeprom() + exit() + + RNS.log("Reading EEPROM...") + rnode.download_eeprom() + + if rnode.provisioned: + if rnode.model != ROM.MODEL_FF: + fw_filename = models[rnode.model][4] + else: + if rnode.platform == ROM.PLATFORM_AVR: + if rnode.mcu == ROM.MCU_1284P: + fw_filename = "rnode_firmware.hex" + elif rnode.mcu == ROM.MCU_2560: + fw_filename = "rnode_firmware_m2560.hex" + elif rnode.platform == ROM.PLATFORM_ESP32: + if rnode.board == ROM.BOARD_HUZZAH32: + fw_filename = "rnode_firmware_featheresp32.zip" + elif rnode.board == ROM.BOARD_GENERIC_ESP32: + fw_filename = "rnode_firmware_esp32_generic.zip" + else: + fw_filename = None + if args.update: + RNS.log("ERROR: No firmware found for this board. Cannot update.") + exit() + + if args.update: + if not rnode.provisioned: + RNS.log("Device not provisioned. Cannot update device firmware.") + exit(1) + + from subprocess import call + + try: + RNS.log("Checking firmware file availability...") + if selected_version == None: + ensure_firmware_file(fw_filename) + + if not force_update: + if rnode.version == selected_version: + if args.fw_version != None: + RNS.log("Specified firmware version ("+selected_version+") is already installed on this device") + RNS.log("Override with -U option to install anyway") + exit(0) + else: + RNS.log("Latest firmware version ("+selected_version+") is already installed on this device") + RNS.log("Override with -U option to install anyway") + exit(0) + + if rnode.version > selected_version: + if args.fw_version != None: + RNS.log("Specified firmware version ("+selected_version+") is older than firmware already installed on this device") + RNS.log("Override with -U option to install anyway") + exit(0) + else: + RNS.log("Latest firmware version ("+selected_version+") is older than firmware already installed on this device") + RNS.log("Override with -U option to install anyway") + exit(0) + + if selected_version != None: + ensure_firmware_file(fw_filename) + + if fw_filename.endswith(".zip"): + RNS.log("Extracting firmware...") + unzip_status = call(get_flasher_call("unzip", fw_filename)) + if unzip_status == 0: + RNS.log("Firmware extracted") + else: + RNS.log("Could not extract firmware from downloaded zip file") + exit() + + except Exception as e: + RNS.log("Could not obtain firmware package for your board") + RNS.log("The contained exception was: "+str(e)) + exit() + + if os.path.isfile(UPD_DIR+"/"+selected_version+"/"+fw_filename): + try: + args.info = False + RNS.log("Updating RNode firmware for device on "+args.port) + partition_filename = fw_filename.replace(".zip", ".bin") + partition_hash = get_partition_hash(UPD_DIR+"/"+selected_version+"/"+partition_filename) + if partition_hash != None: + rnode.set_firmware_hash(partition_hash) + + rnode.disconnect() + flash_status = call(get_flasher_call(rnode.platform, fw_filename)) + if flash_status == 0: + RNS.log("Flashing new firmware completed") + RNS.log("Opening serial port "+args.port+"...") + try: + rnode_port = args.port + rnode_serial = rnode_open_serial(rnode_port) + except Exception as e: + RNS.log("Could not open the specified serial port. The contained exception was:") + RNS.log(str(e)) + exit() + + rnode = RNode(rnode_serial) + thread = threading.Thread(target=rnode.readLoop, daemon=True).start() + + try: + rnode.device_probe() + except Exception as e: + RNS.log("Serial port opened, but RNode did not respond. Is a valid firmware installed?") + print(e) + exit() + + if rnode.detected: + if rnode.platform == None or rnode.mcu == None: + rnode.platform = ROM.PLATFORM_AVR + rnode.mcu = ROM.MCU_1284P + + RNS.log("Reading EEPROM...") + rnode.download_eeprom() + + if rnode.provisioned: + if rnode.model != ROM.MODEL_FF: + fw_filename = models[rnode.model][4] + else: + fw_filename = None + args.info = True + if partition_hash != None: + rnode.set_firmware_hash(partition_hash) + + if args.info: + RNS.log("") + RNS.log("Firmware update completed successfully") + else: + RNS.log("An error occurred while flashing the new firmware, exiting now.") + exit() + + except Exception as e: + RNS.log("Error while updating firmware") + RNS.log(str(e)) + else: + RNS.log("Firmware update file not found") + exit() + + if args.eeprom_dump: + RNS.log("EEPROM contents:") + RNS.log(RNS.hexrep(rnode.eeprom)) + exit() + + if args.eeprom_backup: + try: + timestamp = time.time() + filename = str(time.strftime("%Y-%m-%d_%H-%M-%S")) + path = "./eeprom/"+filename+".eeprom" + file = open(path, "wb") + file.write(rnode.eeprom) + file.close() + RNS.log("EEPROM backup written to: "+path) + except Exception as e: + RNS.log("EEPROM was successfully downloaded from device,") + RNS.log("but file could not be written to disk.") + exit() + + if args.bluetooth_on: + RNS.log("Enabling Bluetooth...") + rnode.enable_bluetooth() + + if args.bluetooth_off: + RNS.log("Disabling Bluetooth...") + rnode.disable_bluetooth() + + if args.bluetooth_pair: + RNS.log("Putting device into Bluetooth pairing mode...") + rnode.bluetooth_pair() + + if args.info: + if rnode.provisioned: + timestamp = struct.unpack(">I", rnode.made)[0] + timestring = datetime.datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M:%S") + sigstring = "Unverified" + if rnode.signature_valid: + if rnode.locally_signed: + sigstring = "Validated - Local signature" + else: + sigstring = "Genuine board, vendor is "+rnode.vendor + + if rnode.board != None: + board_string = ":"+bytes([rnode.board]).hex() + else: + board_string = "" + + RNS.log("") + RNS.log("Device info:") + RNS.log("\tProduct : "+products[rnode.product]+" "+models[rnode.model][3]+" ("+bytes([rnode.product]).hex()+":"+bytes([rnode.model]).hex()+board_string+")") + RNS.log("\tDevice signature : "+sigstring) + RNS.log("\tFirmware version : "+rnode.version) + RNS.log("\tHardware revision : "+str(int(rnode.hw_rev))) + RNS.log("\tSerial number : "+RNS.hexrep(rnode.serialno)) + RNS.log("\tFrequency range : "+str(rnode.min_freq/1e6)+" MHz - "+str(rnode.max_freq/1e6)+" MHz") + RNS.log("\tMax TX power : "+str(rnode.max_output)+" dBm") + RNS.log("\tManufactured : "+timestring) + + if rnode.configured: + rnode.bandwidth = rnode.conf_bandwidth + rnode.r_bandwidth = rnode.conf_bandwidth + rnode.sf = rnode.conf_sf + rnode.r_sf = rnode.conf_sf + rnode.cr = rnode.conf_cr + rnode.r_cr = rnode.conf_cr + rnode.updateBitrate() + txp_mw = round(pow(10, (rnode.conf_txpower/10)), 3) + RNS.log(""); + RNS.log("\tDevice mode : TNC") + RNS.log("\t Frequency : "+str((rnode.conf_frequency/1000000.0))+" MHz") + RNS.log("\t Bandwidth : "+str(rnode.conf_bandwidth/1000.0)+" KHz") + RNS.log("\t TX power : "+str(rnode.conf_txpower)+" dBm ("+str(txp_mw)+" mW)") + RNS.log("\t Spreading factor : "+str(rnode.conf_sf)) + RNS.log("\t Coding rate : "+str(rnode.conf_cr)) + RNS.log("\t On-air bitrate : "+str(rnode.bitrate_kbps)+" kbps") + else: + RNS.log("\tDevice mode : Normal (host-controlled)") + + print("") + rnode.disconnect() + exit() + + else: + RNS.log("EEPROM is invalid, no further information available") + exit() + + if args.rom: + if rnode.provisioned and not args.autoinstall: + RNS.log("EEPROM bootstrap was requested, but a valid EEPROM was already present.") + RNS.log("No changes are being made.") + exit() + + else: + if rnode.signature_valid: + RNS.log("EEPROM bootstrap was requested, but a valid EEPROM was already present.") + RNS.log("No changes are being made.") + exit() + else: + if args.autoinstall: + RNS.log("Clearing old EEPROM, this will take about 15 seconds...") + rnode.wipe_eeprom() + + if rnode.platform == ROM.PLATFORM_ESP32: + RNS.log("Waiting for ESP32 reset...") + time.sleep(6) + else: + time.sleep(3) + + counter = None + counter_path = FWD_DIR+"/serial.counter" + try: + if os.path.isfile(counter_path): + file = open(counter_path, "r") + counter_str = file.read() + counter = int(counter_str) + file.close() + else: + counter = 0 + except Exception as e: + RNS.log("Could not create device serial number, exiting") + RNS.log(str(e)) + exit() + + serialno = counter+1 + model = None + hwrev = None + if args.product != None: + if args.product == "03": + mapped_product = ROM.PRODUCT_RNODE + if args.product == "f0": + mapped_product = ROM.PRODUCT_HMBRW + if args.product == "e0": + mapped_product = ROM.PRODUCT_TBEAM + + if mapped_model != None: + model = mapped_model + else: + if args.model == "a4": + model = ROM.MODEL_A4 + elif args.model == "a9": + model = ROM.MODEL_A9 + elif args.model == "e4": + model = ROM.MODEL_E4 + elif args.model == "e9": + model = ROM.MODEL_E9 + elif args.model == "ff": + model = ROM.MODEL_FF + + + if args.hwrev != None and (args.hwrev > 0 and args.hwrev < 256): + hwrev = chr(args.hwrev) + + if serialno > 0 and model != None and hwrev != None: + try: + from cryptography.hazmat.primitives import hashes + from cryptography.hazmat.backends import default_backend + + timestamp = int(time.time()) + time_bytes = struct.pack(">I", timestamp) + serial_bytes = struct.pack(">I", serialno) + file = open(counter_path, "w") + file.write(str(serialno)) + file.close() + + info_chunk = b"" + bytes([mapped_product, model, ord(hwrev)]) + info_chunk += serial_bytes + info_chunk += time_bytes + digest = hashes.Hash(hashes.MD5(), backend=default_backend()) + digest.update(info_chunk) + checksum = digest.finalize() + + RNS.log("Loading signing key...") + signature = None + key_path = FWD_DIR+"/signing.key" + if os.path.isfile(key_path): + try: + file = open(key_path, "rb") + private_bytes = file.read() + file.close() + private_key = serialization.load_der_private_key( + private_bytes, + password=None, + backend=default_backend() + ) + public_key = private_key.public_key() + public_bytes = public_key.public_bytes( + encoding=serialization.Encoding.DER, + format=serialization.PublicFormat.SubjectPublicKeyInfo + ) + signature = private_key.sign( + checksum, + padding.PSS( + mgf=padding.MGF1(hashes.SHA256()), + salt_length=padding.PSS.MAX_LENGTH + ), + hashes.SHA256() + ) + except Exception as e: + RNS.log("Error while signing EEPROM") + RNS.log(str(e)) + else: + RNS.log("No signing key found") + exit() + + + RNS.log("Bootstrapping device EEPROM...") + rnode.hard_reset() + + rnode.write_eeprom(ROM.ADDR_PRODUCT, mapped_product) + time.sleep(0.006) + rnode.write_eeprom(ROM.ADDR_MODEL, model) + time.sleep(0.006) + rnode.write_eeprom(ROM.ADDR_HW_REV, ord(hwrev)) + time.sleep(0.006) + rnode.write_eeprom(ROM.ADDR_SERIAL, serial_bytes[0]) + time.sleep(0.006) + rnode.write_eeprom(ROM.ADDR_SERIAL+1, serial_bytes[1]) + time.sleep(0.006) + rnode.write_eeprom(ROM.ADDR_SERIAL+2, serial_bytes[2]) + time.sleep(0.006) + rnode.write_eeprom(ROM.ADDR_SERIAL+3, serial_bytes[3]) + time.sleep(0.006) + rnode.write_eeprom(ROM.ADDR_MADE, time_bytes[0]) + time.sleep(0.006) + rnode.write_eeprom(ROM.ADDR_MADE+1, time_bytes[1]) + time.sleep(0.006) + rnode.write_eeprom(ROM.ADDR_MADE+2, time_bytes[2]) + time.sleep(0.006) + rnode.write_eeprom(ROM.ADDR_MADE+3, time_bytes[3]) + time.sleep(0.006) + + for i in range(0,16): + rnode.write_eeprom(ROM.ADDR_CHKSUM+i, checksum[i]) + time.sleep(0.006) + + for i in range(0,128): + rnode.write_eeprom(ROM.ADDR_SIGNATURE+i, signature[i]) + time.sleep(0.006) + + rnode.write_eeprom(ROM.ADDR_INFO_LOCK, ROM.INFO_LOCK_BYTE) + + RNS.log("EEPROM written! Validating...") + + if wants_fw_provision: + RNS.log("Getting partition data...") + partition_filename = fw_filename.replace(".zip", ".bin") + partition_hash = get_partition_hash(UPD_DIR+"/"+selected_version+"/"+partition_filename) + if partition_hash != None: + RNS.log("Setting firmware partition hash target") + rnode.set_firmware_hash(partition_hash) + + if rnode.platform == ROM.PLATFORM_ESP32: + RNS.log("Waiting for ESP32 reset...") + time.sleep(5) + + rnode.download_eeprom() + if rnode.provisioned: + RNS.log("EEPROM Bootstrapping successful!") + rnode.hard_reset() + if args.autoinstall: + print("") + print("RNode Firmware autoinstallation complete!") + print("") + print("To use your device with Reticulum, read the documetation at:") + print("") + print("https://markqvist.github.io/Reticulum/manual/gettingstartedfast.html") + print("") + print("Thank you for using this utility! Please help the project by") + print("contributing code and reporting bugs, or by donating!") + print("") + print("Your contributions and donations directly further the realisation") + print("of truly open, free and resilient communications systems.") + print("") + print_donation_block() + print("") + try: + os.makedirs(FWD_DIR+"/device_db/", exist_ok=True) + file = open(FWD_DIR+"/device_db/"+serial_bytes.hex(), "wb") + written = file.write(rnode.eeprom) + file.close() + except Exception as e: + RNS.log("WARNING: Could not backup device EEPROM to disk") + exit() + else: + RNS.log("EEPROM was written, but validation failed. Check your settings.") + exit() + except Exception as e: + RNS.log("An error occurred while writing EEPROM. The contained exception was:") + RNS.log(str(e)) + raise e + + else: + RNS.log("Invalid data specified, cancelling EEPROM write") + exit() + + if args.sign: + if rnode.provisioned: + try: + device_signer = RNS.Identity.from_file(FWD_DIR+"/device.key") + except Exception as e: + RNS.log("Could not load device signing key") + + if rnode.device_hash == None: + RNS.log("No device hash present, skipping device signing") + else: + if device_signer == None: + RNS.log("No device signer loaded, cannot sign device") + exit(78) + else: + new_device_signature = device_signer.sign(rnode.device_hash) + rnode.store_signature(new_device_signature) + RNS.log("Device signed") + else: + RNS.log("This device has not been provisioned yet, cannot create device signature") + exit(79) + + if args.firmware_hash != None: + if rnode.provisioned: + try: + hash_data = bytes.fromhex(args.firmware_hash) + if len(hash_data) != 32: + raise ValueError("Incorrect hash length") + + rnode.set_firmware_hash(hash_data) + RNS.log("Firmware hash set") + except Exception as e: + RNS.log("The provided value was not a valid SHA256 hash") + exit(78) + + else: + RNS.log("This device has not been provisioned yet, cannot set firmware hash") + exit(77) + + if rnode.provisioned: + if args.normal: + rnode.setNormalMode() + RNS.log("Device set to normal (host-controlled) operating mode") + exit() + if args.tnc: + if not (args.freq and args.bw and args.txp and args.sf and args.cr): + RNS.log("Please input startup configuration:") + + print("") + if args.freq: + rnode.frequency = args.freq + else: + print("Frequency in Hz:\t", end="") + rnode.frequency = int(input()) + + + if args.bw: + rnode.bandwidth = args.bw + else: + print("Bandwidth in Hz:\t", end="") + rnode.bandwidth = int(input()) + + if args.txp != None and (args.txp >= 0 and args.txp <= 17): + rnode.txpower = args.txp + else: + print("TX Power in dBm:\t", end="") + rnode.txpower = int(input()) + + if args.sf: + rnode.sf = args.sf + else: + print("Spreading factor:\t", end="") + rnode.sf = int(input()) + + if args.cr: + rnode.cr = args.cr + else: + print("Coding rate:\t\t", end="") + rnode.cr = int(input()) + + print("") + + rnode.initRadio() + sleep(0.5) + rnode.setTNCMode() + RNS.log("Device set to TNC operating mode") + sleep(1.0) + + exit() + else: + RNS.log("This device contains a valid firmware, but EEPROM is invalid.") + RNS.log("Probably the device has not been initialised, or the EEPROM has been erased.") + RNS.log("Please correctly initialise the device and try again!") + + else: + print("") + parser.print_help() + print("") + exit() + + + except KeyboardInterrupt: + print("") + exit() + +if __name__ == "__main__": + main() diff --git a/docs/Reticulum Manual.pdf b/docs/Reticulum Manual.pdf index dfd8c30..0ae4818 100644 Binary files a/docs/Reticulum Manual.pdf and b/docs/Reticulum Manual.pdf differ diff --git a/setup.py b/setup.py index 9ccf8f7..052ab9d 100644 --- a/setup.py +++ b/setup.py @@ -45,6 +45,7 @@ setuptools.setup( 'rnpath=RNS.Utilities.rnpath:main', 'rncp=RNS.Utilities.rncp:main', 'rnx=RNS.Utilities.rnx:main', + 'rnodeconf=RNS.Utilities.rnodeconf:main', ] }, install_requires=requirements,