Extend RAK4631 support

This commit is contained in:
jacob.eva 2024-05-13 21:49:57 +01:00
parent 09d9285104
commit c230eceaa6
No known key found for this signature in database
GPG Key ID: 0B92E083BBCCAA1E
1 changed files with 219 additions and 65 deletions

View File

@ -128,6 +128,10 @@ class ROM():
MCU_ESP32 = 0x81
MCU_NRF52 = 0x71
PRODUCT_RAK4631 = 0x10
MODEL_11 = 0x11
MODEL_12 = 0x12
PRODUCT_RNODE = 0x03
MODEL_A1 = 0xA1
MODEL_A6 = 0xA6
@ -196,7 +200,7 @@ class ROM():
BOARD_GENERIC_ESP32 = 0x35
BOARD_LORA32_V2_0 = 0x36
BOARD_LORA32_V2_1 = 0x37
BOARD_RAK4630 = 0x51
BOARD_RAK4631 = 0x51
mapped_product = ROM.PRODUCT_RNODE
products = {
@ -208,22 +212,25 @@ products = {
ROM.PRODUCT_T32_21: "LilyGO LoRa32 v2.1",
ROM.PRODUCT_H32_V2: "Heltec LoRa32 v2",
ROM.PRODUCT_H32_V3: "Heltec LoRa32 v3",
ROM.PRODUCT_RAK4631: "RAK4631",
}
platforms = {
ROM.PLATFORM_AVR: "AVR",
ROM.PLATFORM_ESP32:"ESP32",
ROM.PLATFORM_NRF52:"NRF52",
ROM.PLATFORM_NRF52: "NRF52",
}
mcus = {
ROM.MCU_1284P: "ATmega1284P",
ROM.MCU_2560:"ATmega2560",
ROM.MCU_ESP32:"Espressif Systems ESP32",
ROM.MCU_NRF52:"Nordic nRF52840",
ROM.MCU_NRF52: "Nordic Semiconductor nRF52840",
}
models = {
0x11: [430000000, 510000000, 22, "430 - 510 MHz", "rnode_firmware_rak4631.zip", "SX1262"],
0x12: [779000000, 928000000, 22, "779 - 928 MHz", "rnode_firmware_rak4631.zip", "SX1262"],
0xA4: [410000000, 525000000, 14, "410 - 525 MHz", "rnode_firmware.hex", "SX1278"],
0xA9: [820000000, 1020000000, 17, "820 - 1020 MHz", "rnode_firmware.hex", "SX1276"],
0xA1: [410000000, 525000000, 22, "410 - 525 MHz", "rnode_firmware_t3s3.zip", "SX1268"],
@ -306,6 +313,7 @@ class RNode():
self.bandwidth = None
self.detected = None
self.usb_serial_id = None
self.platform = None
self.mcu = None
@ -734,6 +742,10 @@ class RNode():
if written != len(kiss_command):
raise IOError("An IO error occurred while wiping EEPROM")
sleep(13);
# Due to the current janky emulated EEPROM implementation for the
# RAK4631, extra time must be given to allow for writing.
if self.board == ROM.BOARD_RAK4631:
sleep(10)
def hard_reset(self):
kiss_command = bytes([KISS.FEND, KISS.CMD_RESET, 0xf8, KISS.FEND])
@ -1384,6 +1396,7 @@ def main():
exit()
rnode = RNode(rnode_serial)
rnode.usb_serial_id = port_serialno
thread = threading.Thread(target=rnode.readLoop, daemon=True).start()
try:
rnode.device_probe()
@ -1396,54 +1409,58 @@ def main():
else:
RNS.log("Could not detect a connected RNode")
if rnode.provisioned:
if not rnode.signature_valid:
print("\nThe device signature in this RNode is unknown and cannot be verified. It is still")
print("possible to extract the firmware from it, but you should make absolutely sure that")
print("it comes from a trusted source. It is possible that someone could have modified the")
print("firmware. If that is the case, these modifications will propagate to any new RNodes")
print("descendent from this one!")
print("\nHit enter if you are sure you want to continue.")
input()
if rnode.platform == ROM.PLATFORM_ESP32:
if rnode.provisioned:
if not rnode.signature_valid:
print("\nThe device signature in this RNode is unknown and cannot be verified. It is still")
print("possible to extract the firmware from it, but you should make absolutely sure that")
print("it comes from a trusted source. It is possible that someone could have modified the")
print("firmware. If that is the case, these modifications will propagate to any new RNodes")
print("descendent from this one!")
print("\nHit enter if you are sure you want to continue.")
input()
if rnode.firmware_hash != None:
extracted_hash = rnode.firmware_hash
extracted_version = rnode.version
rnode.disconnect()
v_str = str(extracted_version)+" "+RNS.hexrep(extracted_hash, delimit=False)
print("\nFound RNode Firmvare v"+v_str)
if rnode.firmware_hash != None:
extracted_hash = rnode.firmware_hash
extracted_version = rnode.version
rnode.disconnect()
v_str = str(extracted_version)+" "+RNS.hexrep(extracted_hash, delimit=False)
print("\nFound RNode Firmvare v"+v_str)
print("\nReady to extract firmware images from the RNode")
print("Press enter to start the extraction process")
input()
extract_recovery_esptool()
print("\nReady to extract firmware images from the RNode")
print("Press enter to start the extraction process")
input()
extract_recovery_esptool()
hash_f = open(EXT_DIR+"/extracted_rnode_firmware.version", "wb")
hash_f.write(v_str.encode("utf-8"))
hash_f.close()
hash_f = open(EXT_DIR+"/extracted_rnode_firmware.version", "wb")
hash_f.write(v_str.encode("utf-8"))
hash_f.close()
extraction_parts = [
("bootloader", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port "+port_path+" --baud "+args.baud_flash+" --before default_reset --after hard_reset read_flash 0x1000 0x4650 \""+EXT_DIR+"/extracted_rnode_firmware.bootloader\""),
("partition table", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port "+port_path+" --baud "+args.baud_flash+" --before default_reset --after hard_reset read_flash 0x8000 0xC00 \""+EXT_DIR+"/extracted_rnode_firmware.partitions\""),
("app boot", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port "+port_path+" --baud "+args.baud_flash+" --before default_reset --after hard_reset read_flash 0xe000 0x2000 \""+EXT_DIR+"/extracted_rnode_firmware.boot_app0\""),
("application image", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port "+port_path+" --baud "+args.baud_flash+" --before default_reset --after hard_reset read_flash 0x10000 0x200000 \""+EXT_DIR+"/extracted_rnode_firmware.bin\""),
("console image", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port "+port_path+" --baud "+args.baud_flash+" --before default_reset --after hard_reset read_flash 0x210000 0x1F0000 \""+EXT_DIR+"/extracted_console_image.bin\""),
]
import subprocess, shlex
for part, command in extraction_parts:
print("Extracting "+part+"...")
if subprocess.call(shlex.split(command), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) != 0:
print("The extraction failed, the following command did not complete successfully:\n"+command)
exit(182)
extraction_parts = [
("bootloader", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port "+port_path+" --baud "+args.baud_flash+" --before default_reset --after hard_reset read_flash 0x1000 0x4650 \""+EXT_DIR+"/extracted_rnode_firmware.bootloader\""),
("partition table", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port "+port_path+" --baud "+args.baud_flash+" --before default_reset --after hard_reset read_flash 0x8000 0xC00 \""+EXT_DIR+"/extracted_rnode_firmware.partitions\""),
("app boot", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port "+port_path+" --baud "+args.baud_flash+" --before default_reset --after hard_reset read_flash 0xe000 0x2000 \""+EXT_DIR+"/extracted_rnode_firmware.boot_app0\""),
("application image", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port "+port_path+" --baud "+args.baud_flash+" --before default_reset --after hard_reset read_flash 0x10000 0x200000 \""+EXT_DIR+"/extracted_rnode_firmware.bin\""),
("console image", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port "+port_path+" --baud "+args.baud_flash+" --before default_reset --after hard_reset read_flash 0x210000 0x1F0000 \""+EXT_DIR+"/extracted_console_image.bin\""),
]
import subprocess, shlex
for part, command in extraction_parts:
print("Extracting "+part+"...")
if subprocess.call(shlex.split(command), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) != 0:
print("The extraction failed, the following command did not complete successfully:\n"+command)
exit(182)
print("\nFirmware successfully extracted!")
print("\nYou can now use this firmware to update or autoinstall other RNodes")
exit()
else:
print("Could not read firmware information from device")
print("\nFirmware successfully extracted!")
print("\nYou can now use this firmware to update or autoinstall other RNodes")
exit()
else:
print("Could not read firmware information from device")
print("\nRNode firmware extraction failed")
exit(180)
print("\nRNode firmware extraction failed")
exit(180)
else:
print("\nFirmware extraction is currently only supported on ESP32-based RNodes.")
exit(170)
if args.autoinstall:
clear()
@ -1517,6 +1534,7 @@ def main():
exit()
rnode = RNode(rnode_serial)
rnode.usb_serial_id = port_serialno
thread = threading.Thread(target=rnode.readLoop, daemon=True).start()
try:
rnode.device_probe()
@ -1561,6 +1579,7 @@ def main():
print("[7] Heltec LoRa32 v2")
print("[8] Heltec LoRa32 v3")
#print("[9] LilyGO LoRa T3S3")
print("[10] RAK4631")
print(" .")
print(" / \\ Select one of these options if you want to easily turn")
print(" | a supported development board into an RNode.")
@ -1572,7 +1591,7 @@ def main():
try:
c_dev = int(input())
c_mod = False
if c_dev < 1 or c_dev > 9:
if c_dev < 1 or c_dev > 10:
raise ValueError()
elif c_dev == 1:
selected_product = ROM.PRODUCT_RNODE
@ -1701,6 +1720,15 @@ def main():
print("")
print("Please note that Bluetooth is currently not implemented on this board.")
print("")
elif c_dev == 10:
selected_product = ROM.PRODUCT_RAK4631
clear()
print("")
print("---------------------------------------------------------------------------")
print(" RAK4631 RNode Installer")
print("")
print("Important! Using RNode firmware on RAKwireless 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("---------------------------------------------------------------------------")
@ -1953,11 +1981,6 @@ def main():
elif selected_product == ROM.PRODUCT_H32_V3:
selected_mcu = ROM.MCU_ESP32
print("\nWhat band is this Heltec LoRa32 V3 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:
@ -1971,6 +1994,27 @@ def main():
except Exception as e:
print("That band does not exist, exiting now.")
exit()
elif selected_product == ROM.PRODUCT_RAK4631:
selected_mcu = ROM.MCU_NRF52
print("\nWhat band is this RAK4631 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_11
selected_platform = ROM.PLATFORM_NRF52
elif c_model > 1:
selected_model = ROM.MODEL_12
selected_platform = ROM.PLATFORM_NRF52
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]
@ -2157,16 +2201,26 @@ def main():
if not args.autoinstall:
exit()
def get_partition_hash(partition_file):
def get_partition_hash(platform, 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 platform == ROM.PLATFORM_ESP32 or platform == ROM.PLATFORM_AVR:
firmware_data = open(partition_file, "rb").read()
# Calculate the digest manually and see if it matches the
# SHA256 digest included in the ESP32 image.
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
if calc_hash == part_hash:
return part_hash
else:
return None
elif platform == ROM.PLATFORM_NRF52:
# Calculate digest manually, as it is not included in the image.
firmware_data = open(partition_file, "rb")
hash = hashlib.file_digest(firmware_data, 'sha256').digest()
firmware_data.close()
return hash
except Exception as e:
RNS.log("Could not calculate firmware partition hash. The contained exception was:")
RNS.log(str(e))
@ -2667,6 +2721,20 @@ def main():
RNS.log("")
RNS.log("Please install \""+flasher+"\" and try again.")
exit()
elif platform == ROM.PLATFORM_NRF52:
flasher = "adafruit-nrfutil"
if which(flasher) is not None:
return [flasher, "dfu", "serial", "--package", UPD_DIR+"/"+selected_version+"/"+fw_filename, "-p", args.port, "-b", "115200", "-t", "1200"]
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(" pip3 install --user adafruit-nrfutil")
RNS.log("")
RNS.log("Please install \""+flasher+"\" and try again.")
exit()
if args.port:
wants_fw_provision = False
@ -2729,6 +2797,11 @@ def main():
wants_fw_provision = True
RNS.log("Waiting for ESP32 reset...")
time.sleep(7)
if args.platform == ROM.PLATFORM_NRF52:
wants_fw_provision = True
RNS.log("Waiting for NRF52 reset...")
# Don't need to wait as long this time.
time.sleep(5)
else:
RNS.log("Error from flasher ("+str(flash_status)+") while writing.")
RNS.log("Some boards have trouble flashing at high speeds, and you can")
@ -2754,6 +2827,11 @@ def main():
exit()
rnode = RNode(rnode_serial)
ports = list_ports.comports()
for port in ports:
if port.device == args.port:
rnode.usb_serial_id = port.serial_number
break
thread = threading.Thread(target=rnode.readLoop, daemon=True).start()
try:
@ -2772,7 +2850,6 @@ def main():
if args.eeprom_wipe:
RNS.log("WARNING: EEPROM is being wiped! Power down device NOW if you do not want this!")
rnode.wipe_eeprom()
rnode.hard_reset()
exit()
RNS.log("Reading EEPROM...")
@ -2795,8 +2872,12 @@ def main():
fw_filename = "rnode_firmware_featheresp32.zip"
elif rnode.board == ROM.BOARD_GENERIC_ESP32:
fw_filename = "rnode_firmware_esp32_generic.zip"
elif rnode.platform == ROM.PLATFORM_NRF52:
if rnode.board == ROM.BOARD_RAK4631:
fw_filename = "rnode_firmware_rak4631.zip"
else:
fw_filename = None
if args.update:
RNS.log("ERROR: No firmware found for this board. Cannot update.")
exit()
@ -2877,12 +2958,16 @@ def main():
partition_full_path = EXT_DIR+"/extracted_rnode_firmware.bin"
else:
partition_full_path = UPD_DIR+"/"+selected_version+"/"+partition_filename
partition_hash = get_partition_hash(partition_full_path)
partition_hash = get_partition_hash(rnode.platform, partition_full_path)
if partition_hash != None:
rnode.set_firmware_hash(partition_hash)
rnode.indicate_firmware_update()
sleep(1)
if rnode.platform == ROM.PLATFORM_NRF52:
# Allow extra time for writing to EEPROM on NRF52. Current implementation is slow.
sleep(14)
rnode.disconnect()
flash_status = call(get_flasher_call(rnode.platform, fw_filename))
if flash_status == 0:
@ -3074,6 +3159,27 @@ def main():
if rnode.platform == ROM.PLATFORM_ESP32:
RNS.log("Waiting for ESP32 reset...")
time.sleep(6)
elif rnode.platform == ROM.PLATFORM_NRF52:
rnode_serial.close()
RNS.log("Waiting for NRF52 reset...")
time.sleep(14)
selected_port = None
ports = list_ports.comports()
for port in ports:
if port.serial_number == rnode.usb_serial_id:
selected_port = port
break
if selected_port is None:
RNS.log("Could not detect new port for NRF52...")
else:
try:
rnode_serial = rnode_open_serial(selected_port.device)
rnode.serial = rnode_serial
thread = threading.Thread(target=rnode.readLoop, daemon=True).start()
except Exception as e:
RNS.log("Could not open the specified serial port. The contained exception was:")
RNS.log(str(e))
exit()
else:
time.sleep(3)
@ -3098,6 +3204,8 @@ def main():
if args.product != None:
if args.product == "03":
mapped_product = ROM.PRODUCT_RNODE
elif args.product == "10":
mapped_product = ROM.PRODUCT_RAK4631
elif args.product == "f0":
mapped_product = ROM.PRODUCT_HMBRW
elif args.product == "e0":
@ -3114,7 +3222,11 @@ def main():
else:
model = mapped_model
else:
if args.model == "a4":
if args.model == "11":
model = ROM.MODEL_11
elif args.model == "12":
model = ROM.MODEL_12
elif args.model == "a4":
model = ROM.MODEL_A4
elif args.model == "a9":
model = ROM.MODEL_A9
@ -3227,7 +3339,9 @@ def main():
time.sleep(0.006)
rnode.write_eeprom(ROM.ADDR_INFO_LOCK, ROM.INFO_LOCK_BYTE)
if rnode.platform == ROM.PLATFORM_NRF52:
# Allow extra time for writing to EEPROM on NRF52. Current implementation is slow.
sleep(3)
RNS.log("EEPROM written! Validating...")
if wants_fw_provision:
@ -3241,18 +3355,58 @@ def main():
vf.close()
else:
partition_filename = fw_filename.replace(".zip", ".bin")
partition_hash = get_partition_hash(UPD_DIR+"/"+selected_version+"/"+partition_filename)
partition_hash = get_partition_hash(rnode.platform, UPD_DIR+"/"+selected_version+"/"+partition_filename)
if partition_hash != None:
time.sleep(0.75)
RNS.log("Setting firmware checksum...")
rnode.set_firmware_hash(partition_hash)
rnode.hard_reset()
if rnode.platform == ROM.PLATFORM_ESP32:
rnode.hard_reset()
RNS.log("Waiting for ESP32 reset...")
time.sleep(6.5)
elif rnode.platform == ROM.PLATFORM_NRF52:
rnode.hard_reset()
# The hard reset on this platform is different
# to that of the ESP32 platform, it causes
# disruption to the serial connection.
# Therefore, we have to reestablish the serial
# connection after the reset.
rnode_serial.close()
RNS.log("Waiting for NRF52 reset...")
# Give plenty of time for to allow for
# potential e-ink display refresh too.
time.sleep(14)
# After the hard reset, the port number will
# change. We need to find the new port number,
# which can be done non-interactively by
# comparing the USB serial numbers of the
# original port and the one we are currently
# iterating.
selected_port = None
ports = list_ports.comports()
for port in ports:
if port.serial_number == rnode.usb_serial_id:
selected_port = port
break
if selected_port is None:
RNS.log("Could not detect new port for NRF52...")
else:
try:
rnode_serial = rnode_open_serial(selected_port.device)
rnode.serial = rnode_serial
thread = threading.Thread(target=rnode.readLoop, daemon=True).start()
except Exception as e:
RNS.log("Could not open the specified serial port. The contained exception was:")
RNS.log(str(e))
exit()
else:
rnode.hard_reset()
rnode.download_eeprom()
if rnode.provisioned:
RNS.log("EEPROM Bootstrapping successful!")