rnodeconf.py
1 #!/usr/bin/env python3 2 3 # Reticulum License 4 # 5 # Copyright (c) 2016-2025 Mark Qvist 6 # 7 # Permission is hereby granted, free of charge, to any person obtaining a copy 8 # of this software and associated documentation files (the "Software"), to deal 9 # in the Software without restriction, including without limitation the rights 10 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 # copies of the Software, and to permit persons to whom the Software is 12 # furnished to do so, subject to the following conditions: 13 # 14 # - The Software shall not be used in any kind of system which includes amongst 15 # its functions the ability to purposefully do harm to human beings. 16 # 17 # - The Software shall not be used, directly or indirectly, in the creation of 18 # an artificial intelligence, machine learning or language model training 19 # dataset, including but not limited to any use that contributes to the 20 # training or development of such a model or algorithm. 21 # 22 # - The above copyright notice and this permission notice shall be included in 23 # all copies or substantial portions of the Software. 24 # 25 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 31 # SOFTWARE. 32 33 from time import sleep 34 import argparse 35 import threading 36 import sys 37 import os 38 import os.path 39 import struct 40 import datetime 41 import time 42 import math 43 import hashlib 44 import zipfile 45 from urllib.request import urlretrieve 46 from importlib import util 47 import RNS 48 49 RNS.logtimefmt = "%H:%M:%S" 50 RNS.compact_log_fmt = True 51 52 program_version = "2.5.0" 53 eth_addr = "0x91C421DdfB8a30a49A71d63447ddb54cEBe3465E" 54 btc_addr = "bc1pgqgu8h8xvj4jtafslq396v7ju7hkgymyrzyqft4llfslz5vp99psqfk3a6" 55 xmr_addr = "87HcDx6jRSkMQ9nPRd5K9hGGpZLn2s7vWETjMaVM5KfV4TD36NcYa8J8WSxhTSvBzzFpqDwp2fg5GX2moZ7VAP9QMZCZGET" 56 57 rnode = None 58 rnode_serial = None 59 rnode_port = None 60 rnode_baudrate = 115200 61 known_keys = [["unsigned.io", "30819f300d06092a864886f70d010101050003818d0030818902818100bf831ebd99f43b477caf1a094bec829389da40653e8f1f83fc14bf1b98a3e1cc70e759c213a43f71e5a47eb56a9ca487f241335b3e6ff7cdde0ee0a1c75c698574aeba0485726b6a9dfc046b4188e3520271ee8555a8f405cf21f81f2575771d0b0887adea5dd53c1f594f72c66b5f14904ffc2e72206a6698a490d51ba1105b0203010001"], ["unsigned.io", "30819f300d06092a864886f70d010101050003818d0030818902818100e5d46084e445595376bf7efd9c6ccf19d39abbc59afdb763207e4ff68b8d00ebffb63847aa2fe6dd10783d3ea63b55ac66f71ad885c20e223709f0d51ed5c6c0d0b093be9e1d165bb8a483a548b67a3f7a1e4580f50e75b306593fa6067ae259d3e297717bd7ff8c8f5b07f2bed89929a9a0321026cf3699524db98e2d18fb2d020300ff39"]] 62 firmware_update_url = "https://github.com/markqvist/RNode_Firmware/releases/download/" 63 fw_filename = None 64 fw_url = None 65 mapped_model = None 66 67 class KISS(): 68 FEND = 0xC0 69 FESC = 0xDB 70 TFEND = 0xDC 71 TFESC = 0xDD 72 73 CMD_UNKNOWN = 0xFE 74 CMD_DATA = 0x00 75 CMD_FREQUENCY = 0x01 76 CMD_BANDWIDTH = 0x02 77 CMD_TXPOWER = 0x03 78 CMD_SF = 0x04 79 CMD_CR = 0x05 80 CMD_RADIO_STATE = 0x06 81 CMD_RADIO_LOCK = 0x07 82 CMD_DETECT = 0x08 83 CMD_LEAVE = 0x0A 84 CMD_READY = 0x0F 85 CMD_STAT_RX = 0x21 86 CMD_STAT_TX = 0x22 87 CMD_STAT_RSSI = 0x23 88 CMD_STAT_SNR = 0x24 89 CMD_BLINK = 0x30 90 CMD_RANDOM = 0x40 91 CMD_DISP_INT = 0x45 92 CMD_NP_INT = 0x65 93 CMD_DISP_ADR = 0x63 94 CMD_DISP_BLNK = 0x64 95 CMD_DISP_ROT = 0x67 96 CMD_DISP_RCND = 0x68 97 CMD_BT_CTRL = 0x46 98 CMD_BT_PIN = 0x62 99 CMD_DIS_IA = 0x69 100 CMD_WIFI_MODE = 0x6A 101 CMD_WIFI_SSID = 0x6B 102 CMD_WIFI_PSK = 0x6C 103 CMD_WIFI_CHN = 0x6E 104 CMD_WIFI_IP = 0x84 105 CMD_WIFI_NM = 0x85 106 CMD_BOARD = 0x47 107 CMD_PLATFORM = 0x48 108 CMD_MCU = 0x49 109 CMD_FW_VERSION = 0x50 110 CMD_CFG_READ = 0x6D 111 CMD_ROM_READ = 0x51 112 CMD_ROM_WRITE = 0x52 113 CMD_ROM_WIPE = 0x59 114 CMD_CONF_SAVE = 0x53 115 CMD_CONF_DELETE = 0x54 116 CMD_RESET = 0x55 117 CMD_DEV_HASH = 0x56 118 CMD_DEV_SIG = 0x57 119 CMD_HASHES = 0x60 120 CMD_FW_HASH = 0x58 121 CMD_FW_UPD = 0x61 122 123 DETECT_REQ = 0x73 124 DETECT_RESP = 0x46 125 126 RADIO_STATE_OFF = 0x00 127 RADIO_STATE_ON = 0x01 128 RADIO_STATE_ASK = 0xFF 129 130 CMD_ERROR = 0x90 131 ERROR_INITRADIO = 0x01 132 ERROR_TXFAILED = 0x02 133 ERROR_EEPROM_LOCKED = 0x03 134 135 @staticmethod 136 def escape(data): 137 data = data.replace(bytes([0xdb]), bytes([0xdb, 0xdd])) 138 data = data.replace(bytes([0xc0]), bytes([0xdb, 0xdc])) 139 return data 140 141 class ROM(): 142 PLATFORM_AVR = 0x90 143 PLATFORM_ESP32 = 0x80 144 PLATFORM_NRF52 = 0x70 145 146 MCU_1284P = 0x91 147 MCU_2560 = 0x92 148 MCU_ESP32 = 0x81 149 MCU_NRF52 = 0x71 150 151 PRODUCT_RNODE = 0x03 152 MODEL_A1 = 0xA1 153 MODEL_A6 = 0xA6 154 MODEL_A4 = 0xA4 155 MODEL_A9 = 0xA9 156 MODEL_A3 = 0xA3 157 MODEL_A8 = 0xA8 158 MODEL_A2 = 0xA2 159 MODEL_A7 = 0xA7 160 MODEL_A5 = 0xA5 161 MODEL_AA = 0xAA 162 MODEL_AC = 0xAC 163 164 PRODUCT_T32_10 = 0xB2 165 MODEL_BA = 0xBA 166 MODEL_BB = 0xBB 167 168 PRODUCT_T32_20 = 0xB0 169 MODEL_B3 = 0xB3 170 MODEL_B8 = 0xB8 171 172 PRODUCT_T32_21 = 0xB1 173 MODEL_B4 = 0xB4 174 MODEL_B9 = 0xB9 175 MODEL_B4_TCXO = 0x04 # The TCXO model codes are only used here to select the correct firmware, 176 MODEL_B9_TCXO = 0x09 # actual model codes in firmware is still 0xB4 and 0xB9. 177 178 PRODUCT_H32_V2 = 0xC0 179 MODEL_C4 = 0xC4 180 MODEL_C9 = 0xC9 181 182 PRODUCT_H32_V3 = 0xC1 183 MODEL_C5 = 0xC5 184 MODEL_CA = 0xCA 185 186 PRODUCT_H32_V4 = 0xC3 187 MODEL_C8 = 0xC8 # 868/915/923 MHz with PA 188 189 PRODUCT_TBEAM = 0xE0 190 MODEL_E4 = 0xE4 191 MODEL_E9 = 0xE9 192 MODEL_E3 = 0xE3 193 MODEL_E8 = 0xE8 194 195 PRODUCT_TBEAM_S_V1 = 0xEA 196 MODEL_DB = 0xDB 197 MODEL_DC = 0xDC 198 199 PRODUCT_TDECK = 0xD0 200 MODEL_D4 = 0xD4 201 MODEL_D9 = 0xD9 202 203 PRODUCT_RAK4631 = 0x10 204 MODEL_11 = 0x11 205 MODEL_12 = 0x12 206 MODEL_13 = 0x13 207 MODEL_14 = 0x14 208 209 PRODUCT_OPENCOM_XL = 0x20 210 MODEL_21 = 0x21 211 212 PRODUCT_TECHO = 0x15 213 MODEL_16 = 0x16 214 MODEL_17 = 0x17 215 216 PRODUCT_HELTEC_T114 = 0xC2 217 BOARD_HELTEC_T114 = 0x3C 218 MODEL_C6 = 0xC6 # Heltec Mesh Node T114, 470-510 MHz (HT-n5262-LF) 219 MODEL_C7 = 0xC7 # Heltec Mesh Node T114, 863-928 MHz (HT-n5262-HF) 220 221 PRODUCT_XIAO_S3 = 0xEB 222 BOARD_XIAO_S3 = 0x3E 223 MODEL_DE = 0xDE # Xiao ESP32S3 with Wio-SX1262 module, 433 MHz 224 MODEL_DD = 0xDD # Xiao ESP32S3 with Wio-SX1262 module, 868 MHz 225 226 PRODUCT_HMBRW = 0xF0 227 MODEL_FF = 0xFF 228 MODEL_FE = 0xFE 229 230 ADDR_PRODUCT = 0x00 231 ADDR_MODEL = 0x01 232 ADDR_HW_REV = 0x02 233 ADDR_SERIAL = 0x03 234 ADDR_MADE = 0x07 235 ADDR_CHKSUM = 0x0B 236 ADDR_SIGNATURE = 0x1B 237 ADDR_INFO_LOCK = 0x9B 238 ADDR_CONF_SF = 0x9C 239 ADDR_CONF_CR = 0x9D 240 ADDR_CONF_TXP = 0x9E 241 ADDR_CONF_BW = 0x9F 242 ADDR_CONF_FREQ = 0xA3 243 ADDR_CONF_OK = 0xA7 244 245 ADDR_CONF_BT = 0xB0 246 ADDR_CONF_DSET = 0xB1 247 ADDR_CONF_DINT = 0xB2 248 ADDR_CONF_DADR = 0xB3 249 ADDR_CONF_DBLK = 0xB4 250 ADDR_CONF_DROT = 0xB8 251 ADDR_CONF_PSET = 0xB5 252 ADDR_CONF_PINT = 0xB6 253 ADDR_CONF_BSET = 0xB7 254 ADDR_CONF_DIA = 0xB9 255 ADDR_CONF_WIFI = 0xBA 256 ADDR_CONF_WCHN = 0xBB 257 ADDR_CONF_SSID = 0x00 258 ADDR_CONF_PSK = 0x21 259 ADDR_CONF_IP = 0x42 260 ADDR_CONF_NM = 0x46 261 262 INFO_LOCK_BYTE = 0x73 263 CONF_OK_BYTE = 0x73 264 265 BOARD_RNODE = 0x31 266 BOARD_HMBRW = 0x32 267 BOARD_TBEAM = 0x33 268 BOARD_TDECK = 0x3B 269 BOARD_HUZZAH32 = 0x34 270 BOARD_GENERIC_ESP32 = 0x35 271 BOARD_LORA32_V2_0 = 0x36 272 BOARD_LORA32_V2_1 = 0x37 273 BOARD_TECHO = 0x43 274 BOARD_RAK4631 = 0x51 275 276 MANUAL_FLASH_MODELS = [] 277 278 mapped_product = ROM.PRODUCT_RNODE 279 products = { 280 ROM.PRODUCT_RNODE: "RNode", 281 ROM.PRODUCT_HMBRW: "Hombrew RNode", 282 ROM.PRODUCT_TBEAM: "LilyGO T-Beam", 283 ROM.PRODUCT_TBEAM_S_V1:"LilyGO T-Beam Supreme", 284 ROM.PRODUCT_TDECK: "LilyGO T-Deck", 285 ROM.PRODUCT_T32_10: "LilyGO LoRa32 v1.0", 286 ROM.PRODUCT_T32_20: "LilyGO LoRa32 v2.0", 287 ROM.PRODUCT_T32_21: "LilyGO LoRa32 v2.1", 288 ROM.PRODUCT_H32_V2: "Heltec LoRa32 v2", 289 ROM.PRODUCT_H32_V3: "Heltec LoRa32 v3", 290 ROM.PRODUCT_H32_V4: "Heltec LoRa32 v4", 291 ROM.PRODUCT_TECHO: "LilyGO T-Echo", 292 ROM.PRODUCT_RAK4631: "RAK4631", 293 ROM.PRODUCT_OPENCOM_XL: "openCom XL", 294 ROM.PRODUCT_HELTEC_T114: "Heltec Mesh Node T114", 295 ROM.PRODUCT_XIAO_S3: "Seeed XIAO ESP32S3 Wio-SX1262", 296 } 297 298 platforms = { 299 ROM.PLATFORM_AVR: "AVR", 300 ROM.PLATFORM_ESP32:"ESP32", 301 ROM.PLATFORM_NRF52: "NRF52", 302 } 303 304 mcus = { 305 ROM.MCU_1284P: "ATmega1284P", 306 ROM.MCU_2560:"ATmega2560", 307 ROM.MCU_ESP32:"Espressif Systems ESP32", 308 ROM.MCU_NRF52: "Nordic Semiconductor nRF52840", 309 } 310 311 models = { 312 0xA4: [410000000, 525000000, 14, "410 - 525 MHz", "rnode_firmware.hex", "SX1278"], 313 0xA9: [820000000, 1020000000, 17, "820 - 1020 MHz", "rnode_firmware.hex", "SX1276"], 314 0xA1: [410000000, 525000000, 22, "410 - 525 MHz", "rnode_firmware_t3s3.zip", "SX1268"], 315 0xA6: [820000000, 1020000000, 22, "820 - 960 MHz", "rnode_firmware_t3s3.zip", "SX1262"], 316 0xA5: [410000000, 525000000, 17, "410 - 525 MHz", "rnode_firmware_t3s3_sx127x.zip", "SX1278"], 317 0xAA: [820000000, 1020000000, 17, "820 - 960 MHz", "rnode_firmware_t3s3_sx127x.zip", "SX1276"], 318 0xAC: [2400000000, 2500000000, 20, "2.4 - 2.5 GHz", "rnode_firmware_t3s3_sx1280_pa.zip", "SX1280"], 319 0xA2: [410000000, 525000000, 17, "410 - 525 MHz", "rnode_firmware_ng21.zip", "SX1278"], 320 0xA7: [820000000, 1020000000, 17, "820 - 1020 MHz", "rnode_firmware_ng21.zip", "SX1276"], 321 0xA3: [410000000, 525000000, 17, "410 - 525 MHz", "rnode_firmware_ng20.zip", "SX1278"], 322 0xA8: [820000000, 1020000000, 17, "820 - 1020 MHz", "rnode_firmware_ng20.zip", "SX1276"], 323 0xB3: [420000000, 520000000, 17, "420 - 520 MHz", "rnode_firmware_lora32v20.zip", "SX1278"], 324 0xB8: [850000000, 950000000, 17, "850 - 950 MHz", "rnode_firmware_lora32v20.zip", "SX1276"], 325 0xB4: [420000000, 520000000, 17, "420 - 520 MHz", "rnode_firmware_lora32v21.zip", "SX1278"], 326 0xB9: [850000000, 950000000, 17, "850 - 950 MHz", "rnode_firmware_lora32v21.zip", "SX1276"], 327 0x04: [420000000, 520000000, 17, "420 - 520 MHz", "rnode_firmware_lora32v21_tcxo.zip", "SX1278"], 328 0x09: [850000000, 950000000, 17, "850 - 950 MHz", "rnode_firmware_lora32v21_tcxo.zip", "SX1276"], 329 0xBA: [420000000, 520000000, 17, "420 - 520 MHz", "rnode_firmware_lora32v10.zip", "SX1278"], 330 0xBB: [850000000, 950000000, 17, "850 - 950 MHz", "rnode_firmware_lora32v10.zip", "SX1276"], 331 0xC4: [420000000, 520000000, 17, "420 - 520 MHz", "rnode_firmware_heltec32v2.zip", "SX1278"], 332 0xC9: [850000000, 950000000, 17, "850 - 950 MHz", "rnode_firmware_heltec32v2.zip", "SX1276"], 333 0xC5: [420000000, 520000000, 22, "420 - 520 MHz", "rnode_firmware_heltec32v3.zip", "SX1268"], 334 0xCA: [850000000, 950000000, 22, "850 - 950 MHz", "rnode_firmware_heltec32v3.zip", "SX1262"], 335 0xC8: [860000000, 930000000, 28, "850 - 950 MHz", "rnode_firmware_heltec32v4pa.zip", "SX1262"], 336 0xC6: [420000000, 520000000, 22, "420 - 520 MHz", "rnode_firmware_heltec_t114.zip", "SX1268"], 337 0xC7: [850000000, 950000000, 22, "850 - 950 MHz", "rnode_firmware_heltec_t114.zip", "SX1262"], 338 0xE4: [420000000, 520000000, 17, "420 - 520 MHz", "rnode_firmware_tbeam.zip", "SX1278"], 339 0xE9: [850000000, 950000000, 17, "850 - 950 MHz", "rnode_firmware_tbeam.zip", "SX1276"], 340 0xD4: [420000000, 520000000, 22, "420 - 520 MHz", "rnode_firmware_tdeck.zip", "SX1268"], 341 0xD9: [850000000, 950000000, 22, "850 - 950 MHz", "rnode_firmware_tdeck.zip", "SX1262"], 342 0xDB: [420000000, 520000000, 22, "420 - 520 MHz", "rnode_firmware_tbeam_supreme.zip", "SX1268"], 343 0xDC: [850000000, 950000000, 22, "850 - 950 MHz", "rnode_firmware_tbeam_supreme.zip", "SX1262"], 344 0xE3: [420000000, 520000000, 22, "420 - 520 MHz", "rnode_firmware_tbeam_sx1262.zip", "SX1268"], 345 0xE8: [850000000, 950000000, 22, "850 - 950 MHz", "rnode_firmware_tbeam_sx1262.zip", "SX1262"], 346 0x11: [430000000, 510000000, 22, "430 - 510 MHz", "rnode_firmware_rak4631.zip", "SX1262"], 347 0x12: [779000000, 928000000, 22, "779 - 928 MHz", "rnode_firmware_rak4631.zip", "SX1262"], 348 0x13: [430000000, 510000000, 22, "430 - 510 MHz", "rnode_firmware_rak4631_sx1280.zip", "SX1262 + SX1280"], 349 0x14: [779000000, 928000000, 22, "779 - 928 MHz", "rnode_firmware_rak4631_sx1280.zip", "SX1262 + SX1280"], 350 0x16: [779000000, 928000000, 22, "430 - 510 Mhz", "rnode_firmware_techo.zip", "SX1262"], 351 0x17: [779000000, 928000000, 22, "779 - 928 Mhz", "rnode_firmware_techo.zip", "SX1262"], 352 0x21: [820000000, 960000000, 22, "820 - 960 MHz", "rnode_firmware_opencom_xl.zip", "SX1262 + SX1280"], 353 0xDE: [420000000, 520000000, 22, "420 - 520 MHz", "rnode_firmware_xiao_esp32s3.zip", "SX1262"], 354 0xDD: [850000000, 950000000, 22, "850 - 950 MHz", "rnode_firmware_xiao_esp32s3.zip", "SX1262"], 355 0xFE: [100000000, 1100000000, 17, "(Band capabilities unknown)", None, "Unknown"], 356 0xFF: [100000000, 1100000000, 14, "(Band capabilities unknown)", None, "Unknown"], 357 } 358 359 CNF_DIR = None 360 UPD_DIR = None 361 FWD_DIR = None 362 EXT_DIR = None 363 364 try: 365 CNF_DIR = os.path.expanduser("~/.config/rnodeconf") 366 UPD_DIR = CNF_DIR+"/update" 367 FWD_DIR = CNF_DIR+"/firmware" 368 EXT_DIR = CNF_DIR+"/extracted" 369 RT_PATH = CNF_DIR+"/recovery_esptool.py" 370 TK_DIR = CNF_DIR+"/trusted_keys" 371 ROM_DIR = CNF_DIR+"/eeprom" 372 373 if not os.path.isdir(CNF_DIR): 374 os.makedirs(CNF_DIR) 375 if not os.path.isdir(UPD_DIR): 376 os.makedirs(UPD_DIR) 377 if not os.path.isdir(FWD_DIR): 378 os.makedirs(FWD_DIR) 379 if not os.path.isdir(EXT_DIR): 380 os.makedirs(EXT_DIR) 381 if not os.path.isdir(TK_DIR): 382 os.makedirs(TK_DIR) 383 if not os.path.isdir(ROM_DIR): 384 os.makedirs(ROM_DIR) 385 386 except Exception as e: 387 print("No access to directory "+str(CNF_DIR)+". This utility needs file system access to store firmware and data files. Cannot continue.") 388 print("The contained exception was:") 389 print(str(e)) 390 graceful_exit(99) 391 392 squashvw = False 393 394 class RNode(): 395 def __init__(self, serial_instance): 396 self.serial = serial_instance 397 self.timeout = 100 398 399 self.r_frequency = None 400 self.r_bandwidth = None 401 self.r_txpower = None 402 self.r_sf = None 403 self.r_state = None 404 self.r_lock = None 405 406 self.sf = None 407 self.cr = None 408 self.txpower = None 409 self.frequency = None 410 self.bandwidth = None 411 412 self.detected = None 413 self.usb_serial_id = None 414 415 self.platform = None 416 self.mcu = None 417 self.eeprom = None 418 self.cfg_sector = None 419 self.major_version = None 420 self.minor_version = None 421 self.version = None 422 423 self.provisioned = None 424 self.product = None 425 self.board = None 426 self.model = None 427 self.hw_rev = None 428 self.made = None 429 self.serialno = None 430 self.checksum = None 431 self.device_hash = None 432 self.firmware_hash = None 433 self.firmware_hash_target = None 434 self.signature = None 435 self.signature_valid = False 436 self.locally_signed = False 437 self.vendor = None 438 439 self.min_freq = None 440 self.max_freq = None 441 self.max_output = None 442 443 self.configured = None 444 self.conf_sf = None 445 self.conf_cr = None 446 self.conf_txpower = None 447 self.conf_frequency = None 448 self.conf_bandwidth = None 449 450 def disconnect(self): 451 self.leave() 452 self.serial.close() 453 454 def readLoop(self): 455 try: 456 in_frame = False 457 escape = False 458 command = KISS.CMD_UNKNOWN 459 data_buffer = b"" 460 command_buffer = b"" 461 last_read_ms = int(time.time()*1000) 462 463 while self.serial.is_open: 464 try: 465 data_waiting = self.serial.in_waiting 466 except Exception as e: 467 data_waiting = False 468 469 if data_waiting: 470 byte = ord(self.serial.read(1)) 471 last_read_ms = int(time.time()*1000) 472 473 if (in_frame and byte == KISS.FEND and command == KISS.CMD_ROM_READ): 474 self.eeprom = data_buffer 475 in_frame = False 476 data_buffer = b"" 477 command_buffer = b"" 478 elif (in_frame and byte == KISS.FEND and command == KISS.CMD_CFG_READ): 479 self.cfg_sector = data_buffer 480 in_frame = False 481 data_buffer = b"" 482 command_buffer = b"" 483 elif (byte == KISS.FEND): 484 in_frame = True 485 command = KISS.CMD_UNKNOWN 486 data_buffer = b"" 487 command_buffer = b"" 488 elif (in_frame and len(data_buffer) < 1024): 489 if (len(data_buffer) == 0 and command == KISS.CMD_UNKNOWN): 490 command = byte 491 elif (command == KISS.CMD_ROM_READ): 492 if (byte == KISS.FESC): 493 escape = True 494 else: 495 if (escape): 496 if (byte == KISS.TFEND): 497 byte = KISS.FEND 498 if (byte == KISS.TFESC): 499 byte = KISS.FESC 500 escape = False 501 data_buffer = data_buffer+bytes([byte]) 502 elif (command == KISS.CMD_CFG_READ): 503 if (byte == KISS.FESC): 504 escape = True 505 else: 506 if (escape): 507 if (byte == KISS.TFEND): 508 byte = KISS.FEND 509 if (byte == KISS.TFESC): 510 byte = KISS.FESC 511 escape = False 512 data_buffer = data_buffer+bytes([byte]) 513 elif (command == KISS.CMD_DATA): 514 if (byte == KISS.FESC): 515 escape = True 516 else: 517 if (escape): 518 if (byte == KISS.TFEND): 519 byte = KISS.FEND 520 if (byte == KISS.TFESC): 521 byte = KISS.FESC 522 escape = False 523 data_buffer = data_buffer+bytes([byte]) 524 elif (command == KISS.CMD_FREQUENCY): 525 if (byte == KISS.FESC): 526 escape = True 527 else: 528 if (escape): 529 if (byte == KISS.TFEND): 530 byte = KISS.FEND 531 if (byte == KISS.TFESC): 532 byte = KISS.FESC 533 escape = False 534 command_buffer = command_buffer+bytes([byte]) 535 if (len(command_buffer) == 4): 536 self.r_frequency = command_buffer[0] << 24 | command_buffer[1] << 16 | command_buffer[2] << 8 | command_buffer[3] 537 RNS.log("Radio reporting frequency is "+str(self.r_frequency/1000000.0)+" MHz") 538 self.updateBitrate() 539 540 elif (command == KISS.CMD_BANDWIDTH): 541 if (byte == KISS.FESC): 542 escape = True 543 else: 544 if (escape): 545 if (byte == KISS.TFEND): 546 byte = KISS.FEND 547 if (byte == KISS.TFESC): 548 byte = KISS.FESC 549 escape = False 550 command_buffer = command_buffer+bytes([byte]) 551 if (len(command_buffer) == 4): 552 self.r_bandwidth = command_buffer[0] << 24 | command_buffer[1] << 16 | command_buffer[2] << 8 | command_buffer[3] 553 RNS.log("Radio reporting bandwidth is "+str(self.r_bandwidth/1000.0)+" KHz") 554 self.updateBitrate() 555 556 elif (command == KISS.CMD_BT_PIN): 557 if (byte == KISS.FESC): 558 escape = True 559 else: 560 if (escape): 561 if (byte == KISS.TFEND): 562 byte = KISS.FEND 563 if (byte == KISS.TFESC): 564 byte = KISS.FESC 565 escape = False 566 command_buffer = command_buffer+bytes([byte]) 567 if (len(command_buffer) == 4): 568 self.r_bt_pin = command_buffer[0] << 24 | command_buffer[1] << 16 | command_buffer[2] << 8 | command_buffer[3] 569 RNS.log("Bluetooth pairing PIN is: {:06d}".format(self.r_bt_pin)) 570 571 elif (command == KISS.CMD_DEV_HASH): 572 if (byte == KISS.FESC): 573 escape = True 574 else: 575 if (escape): 576 if (byte == KISS.TFEND): 577 byte = KISS.FEND 578 if (byte == KISS.TFESC): 579 byte = KISS.FESC 580 escape = False 581 command_buffer = command_buffer+bytes([byte]) 582 if (len(command_buffer) == 32): 583 self.device_hash = command_buffer 584 585 elif (command == KISS.CMD_HASHES): 586 if (byte == KISS.FESC): 587 escape = True 588 else: 589 if (escape): 590 if (byte == KISS.TFEND): 591 byte = KISS.FEND 592 if (byte == KISS.TFESC): 593 byte = KISS.FESC 594 escape = False 595 command_buffer = command_buffer+bytes([byte]) 596 if (len(command_buffer) == 33): 597 if command_buffer[0] == 0x01: 598 self.firmware_hash_target = command_buffer[1:] 599 if command_buffer[0] == 0x02: 600 self.firmware_hash = command_buffer[1:] 601 602 elif (command == KISS.CMD_FW_VERSION): 603 if (byte == KISS.FESC): 604 escape = True 605 else: 606 if (escape): 607 if (byte == KISS.TFEND): 608 byte = KISS.FEND 609 if (byte == KISS.TFESC): 610 byte = KISS.FESC 611 escape = False 612 command_buffer = command_buffer+bytes([byte]) 613 if (len(command_buffer) == 2): 614 self.major_version = command_buffer[0] 615 self.minor_version = command_buffer[1] 616 self.updateVersion() 617 618 elif (command == KISS.CMD_BOARD): 619 self.board = byte 620 621 elif (command == KISS.CMD_PLATFORM): 622 self.platform = byte 623 624 elif (command == KISS.CMD_MCU): 625 self.mcu = byte 626 627 elif (command == KISS.CMD_TXPOWER): 628 self.r_txpower = byte 629 RNS.log("Radio reporting TX power is "+str(self.r_txpower)+" dBm") 630 elif (command == KISS.CMD_SF): 631 self.r_sf = byte 632 RNS.log("Radio reporting spreading factor is "+str(self.r_sf)) 633 self.updateBitrate() 634 elif (command == KISS.CMD_CR): 635 self.r_cr = byte 636 RNS.log("Radio reporting coding rate is "+str(self.r_cr)) 637 self.updateBitrate() 638 elif (command == KISS.CMD_RADIO_STATE): 639 self.r_state = byte 640 elif (command == KISS.CMD_RADIO_LOCK): 641 self.r_lock = byte 642 elif (command == KISS.CMD_STAT_RX): 643 if (byte == KISS.FESC): 644 escape = True 645 else: 646 if (escape): 647 if (byte == KISS.TFEND): 648 byte = KISS.FEND 649 if (byte == KISS.TFESC): 650 byte = KISS.FESC 651 escape = False 652 command_buffer = command_buffer+bytes([byte]) 653 if (len(command_buffer) == 4): 654 self.r_stat_rx = ord(command_buffer[0]) << 24 | ord(command_buffer[1]) << 16 | ord(command_buffer[2]) << 8 | ord(command_buffer[3]) 655 656 elif (command == KISS.CMD_STAT_TX): 657 if (byte == KISS.FESC): 658 escape = True 659 else: 660 if (escape): 661 if (byte == KISS.TFEND): 662 byte = KISS.FEND 663 if (byte == KISS.TFESC): 664 byte = KISS.FESC 665 escape = False 666 command_buffer = command_buffer+bytes([byte]) 667 if (len(command_buffer) == 4): 668 self.r_stat_tx = ord(command_buffer[0]) << 24 | ord(command_buffer[1]) << 16 | ord(command_buffer[2]) << 8 | ord(command_buffer[3]) 669 elif (command == KISS.CMD_STAT_RSSI): 670 self.r_stat_rssi = byte-157 # RSSI Offset 671 elif (command == KISS.CMD_STAT_SNR): 672 self.r_stat_snr = int.from_bytes(bytes([byte]), byteorder="big", signed=True) * 0.25 673 elif (command == KISS.CMD_RANDOM): 674 self.r_random = byte 675 elif (command == KISS.CMD_ERROR): 676 if (byte == KISS.ERROR_INITRADIO): 677 RNS.log(str(self)+" hardware initialisation error (code "+RNS.hexrep(byte)+")") 678 elif (byte == KISS.ERROR_TXFAILED): 679 RNS.log(str(self)+" hardware TX error (code "+RNS.hexrep(byte)+")") 680 else: 681 RNS.log(str(self)+" hardware error (code "+RNS.hexrep(byte)+")") 682 elif (command == KISS.CMD_DETECT): 683 if byte == KISS.DETECT_RESP: 684 self.detected = True 685 else: 686 self.detected = False 687 688 else: 689 time_since_last = int(time.time()*1000) - last_read_ms 690 if len(data_buffer) > 0 and time_since_last > self.timeout: 691 RNS.log(str(self)+" serial read timeout") 692 data_buffer = b"" 693 in_frame = False 694 command = KISS.CMD_UNKNOWN 695 escape = False 696 sleep(0.08) 697 698 except Exception as e: 699 raise e 700 graceful_exit() 701 702 def updateBitrate(self): 703 try: 704 self.bitrate = self.r_sf * ( (4.0/self.r_cr) / (math.pow(2,self.r_sf)/(self.r_bandwidth/1000)) ) * 1000 705 self.bitrate_kbps = round(self.bitrate/1000.0, 2) 706 except Exception as e: 707 self.bitrate = 0 708 709 def updateVersion(self): 710 minstr = str(self.minor_version) 711 if len(minstr) == 1: 712 minstr = "0"+minstr 713 self.version = str(self.major_version)+"."+minstr 714 715 def detect(self): 716 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, KISS.CMD_HASHES, 0x01, KISS.FEND, KISS.CMD_HASHES, 0x02, KISS.FEND]) 717 written = self.serial.write(kiss_command) 718 if written != len(kiss_command): 719 raise IOError("An IO error occurred while detecting hardware for "+self(str)) 720 721 def leave(self): 722 kiss_command = bytes([KISS.FEND, KISS.CMD_LEAVE, 0xFF, KISS.FEND]) 723 written = self.serial.write(kiss_command) 724 if written != len(kiss_command): 725 raise IOError("An IO error occurred while sending host left command to device") 726 sleep(1) 727 728 def set_display_intensity(self, intensity): 729 data = bytes([intensity & 0xFF]) 730 kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_DISP_INT])+data+bytes([KISS.FEND]) 731 written = self.serial.write(kiss_command) 732 if written != len(kiss_command): 733 raise IOError("An IO error occurred while sending display intensity command to device") 734 735 def set_display_blanking(self, blanking_timeout): 736 data = bytes([blanking_timeout & 0xFF]) 737 kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_DISP_BLNK])+data+bytes([KISS.FEND]) 738 written = self.serial.write(kiss_command) 739 if written != len(kiss_command): 740 raise IOError("An IO error occurred while sending display blanking timeout command to device") 741 742 def set_display_rotation(self, rotation): 743 data = bytes([rotation & 0xFF]) 744 kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_DISP_ROT])+data+bytes([KISS.FEND]) 745 written = self.serial.write(kiss_command) 746 if written != len(kiss_command): 747 raise IOError("An IO error occurred while sending display rotation command to device") 748 749 def recondition_display(self): 750 data = bytes([0x01]) 751 kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_DISP_RCND])+data+bytes([KISS.FEND]) 752 written = self.serial.write(kiss_command) 753 if written != len(kiss_command): 754 raise IOError("An IO error occurred while sending display recondition command to device") 755 756 def set_disable_interference_avoidance(self, ia_disabled): 757 if ia_disabled: 758 data = bytes([0x01]) 759 else: 760 data = bytes([0x00]) 761 kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_DIS_IA])+data+bytes([KISS.FEND]) 762 written = self.serial.write(kiss_command) 763 if written != len(kiss_command): 764 raise IOError("An IO error occurred while sending interference avoidance configuration command to device") 765 766 def set_neopixel_intensity(self, intensity): 767 data = bytes([intensity & 0xFF]) 768 kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_NP_INT])+data+bytes([KISS.FEND]) 769 written = self.serial.write(kiss_command) 770 if written != len(kiss_command): 771 raise IOError("An IO error occurred while sending NeoPixel intensity command to device") 772 773 def set_display_address(self, address): 774 data = bytes([address & 0xFF]) 775 kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_DISP_ADR])+data+bytes([KISS.FEND]) 776 written = self.serial.write(kiss_command) 777 if written != len(kiss_command): 778 raise IOError("An IO error occurred while sending display address command to device") 779 780 def enable_bluetooth(self): 781 kiss_command = bytes([KISS.FEND, KISS.CMD_BT_CTRL, 0x01, KISS.FEND]) 782 written = self.serial.write(kiss_command) 783 if written != len(kiss_command): 784 raise IOError("An IO error occurred while sending bluetooth enable command to device") 785 786 def disable_bluetooth(self): 787 kiss_command = bytes([KISS.FEND, KISS.CMD_BT_CTRL, 0x00, KISS.FEND]) 788 written = self.serial.write(kiss_command) 789 if written != len(kiss_command): 790 raise IOError("An IO error occurred while sending bluetooth disable command to device") 791 792 def bluetooth_pair(self): 793 kiss_command = bytes([KISS.FEND, KISS.CMD_BT_CTRL, 0x02, KISS.FEND]) 794 written = self.serial.write(kiss_command) 795 if written != len(kiss_command): 796 raise IOError("An IO error occurred while sending bluetooth pair command to device") 797 798 def store_signature(self, signature_bytes): 799 data = KISS.escape(signature_bytes) 800 kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_DEV_SIG])+data+bytes([KISS.FEND]) 801 802 written = self.serial.write(kiss_command) 803 if written != len(kiss_command): 804 raise IOError("An IO error occurred while sending signature to device") 805 806 def set_firmware_hash(self, hash_bytes): 807 data = KISS.escape(hash_bytes) 808 kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_FW_HASH])+data+bytes([KISS.FEND]) 809 810 written = self.serial.write(kiss_command) 811 if written != len(kiss_command): 812 raise IOError("An IO error occurred while sending firmware hash to device") 813 814 def indicate_firmware_update(self): 815 kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_FW_UPD])+bytes([0x01])+bytes([KISS.FEND]) 816 817 written = self.serial.write(kiss_command) 818 if written != len(kiss_command): 819 raise IOError("An IO error occurred while sending firmware update command to device") 820 821 def set_wifi_mode(self, mode): 822 kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_WIFI_MODE, mode])+bytes([KISS.FEND]) 823 written = self.serial.write(kiss_command) 824 if written != len(kiss_command): 825 raise IOError("An IO error occurred while sending wifi mode command to device") 826 827 def set_wifi_channel(self, channel): 828 try: ch = int(channel) 829 except: raise ValueError("Invalid WiFi channel") 830 if ch < 1 or ch > 14: raise ValueError("Invalid WiFi channel") 831 ch_data = bytes([ch]) 832 data = KISS.escape(ch_data) 833 kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_WIFI_CHN])+data+bytes([KISS.FEND]) 834 835 written = self.serial.write(kiss_command) 836 if written != len(kiss_command): 837 raise IOError("An IO error occurred while sending wifi channel to device") 838 839 def set_wifi_ip(self, ip): 840 if ip == None: ip_data = bytes([0x00, 0x00, 0x00, 0x00]) 841 else: 842 ip_data = b"" 843 if not type(ip) == str: raise TypeError("Invalid IP address") 844 octets = ip.split(".") 845 if not len(octets) == 4: raise ValueError("Invalid IP address length") 846 try: 847 for i in range(0, 4): 848 octet = int(octets[i]) 849 if octet < 0 or octet > 255: raise ValueError("Invalid IP octet value") 850 else: ip_data += bytes([octet]) 851 except Exception as e: 852 raise ValueError(f"Could not decode IP address octet: {e}") 853 854 data = KISS.escape(ip_data) 855 kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_WIFI_IP])+data+bytes([KISS.FEND]) 856 857 written = self.serial.write(kiss_command) 858 if written != len(kiss_command): raise IOError("An IO error occurred while sending wifi IP address to device") 859 860 def set_wifi_nm(self, nm): 861 if nm == None: nm_data = bytes([0x00, 0x00, 0x00, 0x00]) 862 else: 863 nm_data = b"" 864 if not type(nm) == str: raise TypeError("Invalid IP address") 865 octets = nm.split(".") 866 if not len(octets) == 4: raise ValueError("Invalid IP address length") 867 try: 868 for i in range(0, 4): 869 octet = int(octets[i]) 870 if octet < 0 or octet > 255: raise ValueError("Invalid IP octet value") 871 else: nm_data += bytes([octet]) 872 except Exception as e: 873 raise ValueError(f"Could not decode IP address octet: {e}") 874 875 data = KISS.escape(nm_data) 876 kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_WIFI_NM])+data+bytes([KISS.FEND]) 877 878 written = self.serial.write(kiss_command) 879 if written != len(kiss_command): raise IOError("An IO error occurred while sending wifi netmask to device") 880 881 def set_wifi_ssid(self, ssid): 882 if ssid == None: data = bytes([0x00]) 883 else: 884 ssid_data = ssid.encode("utf-8")+bytes([0x00]) 885 if len(ssid_data) < 0 or len(ssid_data) > 33: raise ValueError("Invalid SSID length") 886 data = KISS.escape(ssid_data) 887 888 kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_WIFI_SSID])+data+bytes([KISS.FEND]) 889 890 written = self.serial.write(kiss_command) 891 if written != len(kiss_command): 892 raise IOError("An IO error occurred while sending wifi SSID to device") 893 894 def set_wifi_psk(self, psk): 895 if psk == None: data = bytes([0x00]) 896 else: 897 psk_data = psk.encode("utf-8")+bytes([0x00]) 898 if len(psk_data) < 8 or len(psk_data) > 33: raise ValueError("Invalid psk length") 899 data = KISS.escape(psk_data) 900 901 kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_WIFI_PSK])+data+bytes([KISS.FEND]) 902 903 written = self.serial.write(kiss_command) 904 if written != len(kiss_command): 905 raise IOError("An IO error occurred while sending wifi SSID to device") 906 907 def initRadio(self): 908 self.setFrequency() 909 self.setBandwidth() 910 self.setTXPower() 911 self.setSpreadingFactor() 912 self.setCodingRate() 913 self.setRadioState(KISS.RADIO_STATE_ON) 914 915 def setFrequency(self): 916 c1 = self.frequency >> 24 917 c2 = self.frequency >> 16 & 0xFF 918 c3 = self.frequency >> 8 & 0xFF 919 c4 = self.frequency & 0xFF 920 data = KISS.escape(bytes([c1])+bytes([c2])+bytes([c3])+bytes([c4])) 921 922 kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_FREQUENCY])+data+bytes([KISS.FEND]) 923 written = self.serial.write(kiss_command) 924 if written != len(kiss_command): 925 raise IOError("An IO error occurred while configuring frequency for "+self(str)) 926 927 def setBandwidth(self): 928 c1 = self.bandwidth >> 24 929 c2 = self.bandwidth >> 16 & 0xFF 930 c3 = self.bandwidth >> 8 & 0xFF 931 c4 = self.bandwidth & 0xFF 932 data = KISS.escape(bytes([c1])+bytes([c2])+bytes([c3])+bytes([c4])) 933 934 kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_BANDWIDTH])+data+bytes([KISS.FEND]) 935 written = self.serial.write(kiss_command) 936 if written != len(kiss_command): 937 raise IOError("An IO error occurred while configuring bandwidth for "+self(str)) 938 939 def setTXPower(self): 940 txp = bytes([self.txpower]) 941 kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_TXPOWER])+txp+bytes([KISS.FEND]) 942 written = self.serial.write(kiss_command) 943 if written != len(kiss_command): 944 raise IOError("An IO error occurred while configuring TX power for "+self(str)) 945 946 def setSpreadingFactor(self): 947 sf = bytes([self.sf]) 948 kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_SF])+sf+bytes([KISS.FEND]) 949 written = self.serial.write(kiss_command) 950 if written != len(kiss_command): 951 raise IOError("An IO error occurred while configuring spreading factor for "+self(str)) 952 953 def setCodingRate(self): 954 cr = bytes([self.cr]) 955 kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_CR])+cr+bytes([KISS.FEND]) 956 written = self.serial.write(kiss_command) 957 if written != len(kiss_command): 958 raise IOError("An IO error occurred while configuring coding rate for "+self(str)) 959 960 def setRadioState(self, state): 961 kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_RADIO_STATE])+bytes([state])+bytes([KISS.FEND]) 962 written = self.serial.write(kiss_command) 963 if written != len(kiss_command): 964 raise IOError("An IO error occurred while configuring radio state for "+self(str)) 965 966 def setNormalMode(self): 967 kiss_command = bytes([KISS.FEND, KISS.CMD_CONF_DELETE, 0x00, KISS.FEND]) 968 written = self.serial.write(kiss_command) 969 if written != len(kiss_command): 970 raise IOError("An IO error occurred while configuring device mode") 971 972 def setTNCMode(self): 973 kiss_command = bytes([KISS.FEND, KISS.CMD_CONF_SAVE, 0x00, KISS.FEND]) 974 written = self.serial.write(kiss_command) 975 if written != len(kiss_command): 976 raise IOError("An IO error occurred while configuring device mode") 977 978 if self.platform == ROM.PLATFORM_ESP32: 979 self.hard_reset() 980 981 def wipe_eeprom(self): 982 kiss_command = bytes([KISS.FEND, KISS.CMD_ROM_WIPE, 0xf8, KISS.FEND]) 983 written = self.serial.write(kiss_command) 984 if written != len(kiss_command): 985 raise IOError("An IO error occurred while wiping EEPROM") 986 sleep(13); 987 # Due to the current janky emulated EEPROM implementation for the 988 # RAK4631, extra time must be given to allow for writing. 989 if self.board == ROM.BOARD_RAK4631: 990 sleep(10) 991 992 def hard_reset(self): 993 kiss_command = bytes([KISS.FEND, KISS.CMD_RESET, 0xf8, KISS.FEND]) 994 written = self.serial.write(kiss_command) 995 if written != len(kiss_command): 996 raise IOError("An IO error occurred while restarting device") 997 sleep(2); 998 999 def write_eeprom(self, addr, byte): 1000 write_payload = b"" + bytes([addr, byte]) 1001 write_payload = KISS.escape(write_payload) 1002 kiss_command = bytes([KISS.FEND, KISS.CMD_ROM_WRITE]) + write_payload + bytes([KISS.FEND]) 1003 written = self.serial.write(kiss_command) 1004 if written != len(kiss_command): 1005 raise IOError("An IO error occurred while writing EEPROM") 1006 1007 1008 def download_eeprom(self): 1009 self.eeprom = None 1010 kiss_command = bytes([KISS.FEND, KISS.CMD_ROM_READ, 0x00, KISS.FEND]) 1011 written = self.serial.write(kiss_command) 1012 if written != len(kiss_command): 1013 raise IOError("An IO error occurred while downloading EEPROM") 1014 1015 sleep(0.6) 1016 if self.eeprom == None: 1017 RNS.log("Could not download EEPROM from device. Is a valid firmware installed?") 1018 graceful_exit() 1019 else: 1020 self.parse_eeprom() 1021 1022 def download_cfg_sector(self): 1023 self.cfg_sector = None 1024 kiss_command = bytes([KISS.FEND, KISS.CMD_CFG_READ, 0x00, KISS.FEND]) 1025 written = self.serial.write(kiss_command) 1026 if written != len(kiss_command): 1027 raise IOError("An IO error occurred while downloading config sector") 1028 1029 sleep(0.6) 1030 1031 def parse_eeprom(self): 1032 global squashvw; 1033 try: 1034 if self.eeprom[ROM.ADDR_INFO_LOCK] == ROM.INFO_LOCK_BYTE: 1035 from cryptography.hazmat.primitives import hashes 1036 from cryptography.hazmat.backends import default_backend 1037 1038 self.provisioned = True 1039 1040 self.product = self.eeprom[ROM.ADDR_PRODUCT] 1041 self.model = self.eeprom[ROM.ADDR_MODEL] 1042 self.hw_rev = self.eeprom[ROM.ADDR_HW_REV] 1043 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]]) 1044 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]]) 1045 self.checksum = b"" 1046 1047 try: 1048 self.min_freq = models[self.model][0] 1049 self.max_freq = models[self.model][1] 1050 self.max_output = models[self.model][2] 1051 except Exception as e: 1052 RNS.log("Error: Model band and output power capabilities are unknown!") 1053 RNS.log("The contained exception was: "+str(e)) 1054 self.min_freq = 0 1055 self.max_freq = 0 1056 self.max_output = 0 1057 1058 for i in range(0,16): 1059 self.checksum = self.checksum+bytes([self.eeprom[ROM.ADDR_CHKSUM+i]]) 1060 1061 self.signature = b"" 1062 for i in range(0,128): 1063 self.signature = self.signature+bytes([self.eeprom[ROM.ADDR_SIGNATURE+i]]) 1064 1065 checksummed_info = b"" + bytes([self.product]) + bytes([self.model]) + bytes([self.hw_rev]) + self.serialno + self.made 1066 digest = hashes.Hash(hashes.MD5(), backend=default_backend()) 1067 digest.update(checksummed_info) 1068 checksum = digest.finalize() 1069 1070 if self.checksum != checksum: 1071 self.provisioned = False 1072 RNS.log("EEPROM checksum mismatch") 1073 graceful_exit() 1074 else: 1075 RNS.log("EEPROM checksum correct") 1076 1077 from cryptography.hazmat.primitives import serialization 1078 from cryptography.hazmat.primitives.serialization import load_der_public_key 1079 from cryptography.hazmat.primitives.serialization import load_der_private_key 1080 from cryptography.hazmat.primitives.asymmetric import padding 1081 1082 # Try loading local signing key for 1083 # validation of self-signed devices 1084 if os.path.isdir(FWD_DIR) and os.path.isfile(FWD_DIR+"/signing.key"): 1085 private_bytes = None 1086 try: 1087 file = open(FWD_DIR+"/signing.key", "rb") 1088 private_bytes = file.read() 1089 file.close() 1090 except Exception as e: 1091 RNS.log("Could not load local signing key") 1092 1093 try: 1094 private_key = serialization.load_der_private_key( 1095 private_bytes, 1096 password=None, 1097 backend=default_backend() 1098 ) 1099 public_key = private_key.public_key() 1100 public_bytes = public_key.public_bytes( 1101 encoding=serialization.Encoding.DER, 1102 format=serialization.PublicFormat.SubjectPublicKeyInfo 1103 ) 1104 public_bytes_hex = RNS.hexrep(public_bytes, delimit=False) 1105 1106 vendor_keys = [] 1107 for known in known_keys: 1108 vendor_keys.append(known[1]) 1109 1110 if not public_bytes_hex in vendor_keys: 1111 local_key_entry = ["LOCAL", public_bytes_hex] 1112 known_keys.append(local_key_entry) 1113 1114 except Exception as e: 1115 RNS.log("Could not deserialize local signing key") 1116 RNS.log(str(e)) 1117 1118 # Try loading trusted signing key for 1119 # validation of devices 1120 if os.path.isdir(TK_DIR): 1121 for f in os.listdir(TK_DIR): 1122 if os.path.isfile(TK_DIR+"/"+f) and f.endswith(".pubkey"): 1123 try: 1124 file = open(TK_DIR+"/"+f, "rb") 1125 public_bytes = file.read() 1126 file.close() 1127 1128 try: 1129 public_bytes_hex = RNS.hexrep(public_bytes, delimit=False) 1130 1131 vendor_keys = [] 1132 for known in known_keys: 1133 vendor_keys.append(known[1]) 1134 1135 if not public_bytes_hex in vendor_keys: 1136 local_key_entry = ["LOCAL", public_bytes_hex] 1137 known_keys.append(local_key_entry) 1138 1139 except Exception as e: 1140 RNS.log("Could not deserialize trusted signing key "+str(f)) 1141 RNS.log(str(e)) 1142 1143 except Exception as e: 1144 RNS.log("Could not load trusted signing key"+str(f)) 1145 1146 1147 for known in known_keys: 1148 vendor = known[0] 1149 public_hexrep = known[1] 1150 public_bytes = bytes.fromhex(public_hexrep) 1151 public_key = load_der_public_key(public_bytes, backend=default_backend()) 1152 try: 1153 public_key.verify( 1154 self.signature, 1155 self.checksum, 1156 padding.PSS( 1157 mgf=padding.MGF1(hashes.SHA256()), 1158 salt_length=padding.PSS.MAX_LENGTH 1159 ), 1160 hashes.SHA256()) 1161 if vendor == "LOCAL": 1162 self.locally_signed = True 1163 1164 self.signature_valid = True 1165 self.vendor = vendor 1166 except Exception as e: 1167 pass 1168 1169 if self.signature_valid: 1170 RNS.log("Device signature validated") 1171 else: 1172 RNS.log("Device signature validation failed") 1173 if not squashvw: 1174 print(" ") 1175 print(" WARNING! This device is NOT verifiable and should NOT be trusted.") 1176 print(" Someone could have added privacy-breaking or malicious code to it.") 1177 print(" ") 1178 print(" Please verify the signing key is present on this machine.") 1179 print(" Autogenerated keys will not match another machine's signature.") 1180 print(" ") 1181 print(" Proceed at your own risk and responsibility! If you created this") 1182 print(" device yourself, please read the documentation on how to sign your") 1183 print(" device to avoid this warning.") 1184 print(" ") 1185 print(" Always use a firmware downloaded as binaries or compiled from source") 1186 print(" from one of the following locations:") 1187 print(" ") 1188 print(" https://github.com/markqvist/rnode_firmware") 1189 print(" https://github.com/liberatedsystems/RNode_Firmware_CE") 1190 print(" ") 1191 print(" You can reflash and bootstrap this device to a verifiable state") 1192 print(" by using this utility. It is recommended to do so NOW!") 1193 print(" ") 1194 print(" To initialise this device to a verifiable state, please run:") 1195 print(" ") 1196 print(" rnodeconf "+str(self.serial.name)+" --autoinstall") 1197 print("") 1198 1199 1200 1201 if self.eeprom[ROM.ADDR_CONF_OK] == ROM.CONF_OK_BYTE: 1202 self.configured = True 1203 self.conf_sf = self.eeprom[ROM.ADDR_CONF_SF] 1204 self.conf_cr = self.eeprom[ROM.ADDR_CONF_CR] 1205 self.conf_txpower = self.eeprom[ROM.ADDR_CONF_TXP] 1206 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] 1207 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] 1208 else: 1209 self.configured = False 1210 else: 1211 self.provisioned = False 1212 except Exception as e: 1213 self.provisioned = False 1214 RNS.log("Invalid EEPROM data, could not parse device EEPROM.") 1215 RNS.log("The contained exception was: "+str(e)) 1216 1217 1218 def device_probe(self): 1219 sleep(2.5) 1220 self.detect() 1221 sleep(0.75) 1222 if self.detected == True: 1223 RNS.log("Device connected") 1224 RNS.log("Current firmware version: "+self.version) 1225 return True 1226 else: 1227 raise IOError("Got invalid response while detecting device") 1228 1229 selected_version = None 1230 selected_hash = None 1231 firmware_version_url = "https://github.com/markqvist/rnode_firmware/releases/latest/download/release.json" 1232 fallback_firmware_version_url = "https://github.com/markqvist/rnode_firmware/releases/latest/download/release.json" 1233 def ensure_firmware_file(fw_filename): 1234 global selected_version, selected_hash, upd_nocheck 1235 if fw_filename == "extracted_rnode_firmware.zip": 1236 vfpath = EXT_DIR+"/extracted_rnode_firmware.version" 1237 if os.path.isfile(vfpath): 1238 required_files = [ 1239 "extracted_console_image.bin", 1240 "extracted_rnode_firmware.bin", 1241 "extracted_rnode_firmware.boot_app0", 1242 "extracted_rnode_firmware.bootloader", 1243 "extracted_rnode_firmware.partitions", 1244 ] 1245 parts_missing = False 1246 for rf in required_files: 1247 if not os.path.isfile(EXT_DIR+"/"+rf): 1248 parts_missing = True 1249 1250 if parts_missing: 1251 RNS.log("One or more required firmware files are missing from the extracted RNode") 1252 RNS.log("Firmware archive. Installation cannot continue. Please try extracting the") 1253 RNS.log("firmware again with the --extract-firmware option.") 1254 graceful_exit(184) 1255 1256 vf = open(vfpath, "rb") 1257 release_info = vf.read().decode("utf-8").strip() 1258 selected_version = release_info.split()[0] 1259 selected_hash = release_info.split()[1] 1260 RNS.log("Using existing firmware file: "+fw_filename+" for version "+selected_version) 1261 else: 1262 RNS.log("No extracted firmware is available, cannot continue.") 1263 RNS.log("Extract a firmware from an existing RNode first, using the --extract-firmware option.") 1264 graceful_exit(183) 1265 1266 else: 1267 try: 1268 if not upd_nocheck: 1269 try: 1270 # if custom firmware url, download latest release 1271 if selected_version == None and fw_url == None: 1272 urlretrieve(firmware_version_url, UPD_DIR+"/release_info.json") 1273 import json 1274 with open(UPD_DIR+"/release_info.json", "rb") as rif: 1275 rdat = json.loads(rif.read()) 1276 variant = rdat[fw_filename] 1277 with open(UPD_DIR+"/"+fw_filename+".version.latest", "wb") as verf: 1278 inf_str = str(variant["version"])+" "+str(variant["hash"]) 1279 verf.write(inf_str.encode("utf-8")) 1280 1281 else: 1282 if fw_url != None: 1283 if selected_version == None: 1284 version_url = fw_url+"latest/download/release.json" 1285 else: 1286 version_url = fw_url+"download/"+selected_version+"/release.json" 1287 else: 1288 version_url = firmware_update_url+selected_version+"/release.json" 1289 try: 1290 RNS.log("Retrieving specified version info from "+version_url) 1291 urlretrieve(version_url, UPD_DIR+"/version_release_info.json") 1292 import json 1293 with open(UPD_DIR+"/version_release_info.json", "rb") as rif: 1294 rdat = json.loads(rif.read()) 1295 variant = rdat[fw_filename] 1296 with open(UPD_DIR+"/"+fw_filename+".version.latest", "wb") as verf: 1297 inf_str = str(variant["version"])+" "+str(variant["hash"]) 1298 verf.write(inf_str.encode("utf-8")) 1299 except Exception as e: 1300 RNS.log("Failed to retrive version information for your board.") 1301 RNS.log("Check your internet connection and try again.") 1302 RNS.log("If you don't have Internet access currently, use the --fw-version option to manually specify a version.") 1303 RNS.log("You can also use --extract to copy the firmware from a known-good RNode of the same model.") 1304 graceful_exit() 1305 except Exception as e: 1306 # if custom firmware url, don't fallback 1307 if fw_url != None: 1308 RNS.log("Failed to retrive version information for your board from the specified url.") 1309 RNS.log("Check your internet connection and try again.") 1310 RNS.log("If you don't have Internet access currently, use the --fw-version option to manually specify a version.") 1311 RNS.log("You can also use --extract to copy the firmware from a known-good RNode of the same model.") 1312 graceful_exit() 1313 1314 RNS.log("") 1315 RNS.log("WARNING!") 1316 RNS.log("Failed to retrieve latest version information for your board from the default server.") 1317 RNS.log("Will retry using the following fallback URL: "+fallback_firmware_version_url) 1318 RNS.log("") 1319 RNS.log("Hit enter if you want to proceed") 1320 input() 1321 try: 1322 urlretrieve(fallback_firmware_version_url, UPD_DIR+"/fallback_release_info.json") 1323 import json 1324 with open(UPD_DIR+"/fallback_release_info.json", "rb") as rif: 1325 rdat = json.loads(rif.read()) 1326 variant = rdat[fw_filename] 1327 with open(UPD_DIR+"/"+fw_filename+".version.latest", "wb") as verf: 1328 inf_str = str(variant["version"])+" "+str(variant["hash"]) 1329 verf.write(inf_str.encode("utf-8")) 1330 1331 except Exception as e: 1332 RNS.log("Error while trying fallback URL: "+str(e)) 1333 raise e 1334 1335 import shutil 1336 file = open(UPD_DIR+"/"+fw_filename+".version.latest", "rb") 1337 release_info = file.read().decode("utf-8").strip() 1338 selected_version = release_info.split()[0] 1339 if selected_version == "not": 1340 RNS.log("No valid version found for this board, exiting.") 1341 graceful_exit(199) 1342 1343 selected_hash = release_info.split()[1] 1344 if not os.path.isdir(UPD_DIR+"/"+selected_version): 1345 os.makedirs(UPD_DIR+"/"+selected_version) 1346 shutil.copy(UPD_DIR+"/"+fw_filename+".version.latest", UPD_DIR+"/"+selected_version+"/"+fw_filename+".version") 1347 RNS.log("The selected firmware for this board is version "+selected_version) 1348 1349 else: 1350 if selected_version == None: 1351 RNS.log("Online firmware version check was disabled, but no firmware version specified for install.") 1352 RNS.log("use the --fw-version option to manually specify a version.") 1353 graceful_exit(98) 1354 1355 # if custom firmware url, use it 1356 if fw_url != None: 1357 update_target_url = fw_url+"download/"+selected_version+"/"+fw_filename 1358 RNS.log("Retrieving firmware from custom url "+update_target_url) 1359 else: 1360 update_target_url = firmware_update_url+selected_version+"/"+fw_filename 1361 1362 try: 1363 if not os.path.isdir(UPD_DIR+"/"+selected_version): 1364 os.makedirs(UPD_DIR+"/"+selected_version) 1365 1366 if not os.path.isfile(UPD_DIR+"/"+selected_version+"/"+fw_filename): 1367 RNS.log("Firmware "+UPD_DIR+"/"+selected_version+"/"+fw_filename+" not found.") 1368 RNS.log("Downloading missing firmware file: "+fw_filename+" for version "+selected_version) 1369 urlretrieve(update_target_url, UPD_DIR+"/"+selected_version+"/"+fw_filename) 1370 RNS.log("Firmware file downloaded") 1371 else: 1372 RNS.log("Using existing firmware file: "+fw_filename+" for version "+selected_version) 1373 1374 try: 1375 if selected_hash == None: 1376 try: 1377 file = open(UPD_DIR+"/"+selected_version+"/"+fw_filename+".version", "rb") 1378 release_info = file.read().decode("utf-8").strip() 1379 selected_hash = release_info.split()[1] 1380 except Exception as e: 1381 RNS.log("Could not read locally cached release information.") 1382 RNS.log("Ensure "+UPD_DIR+"/"+selected_version+"/"+fw_filename+".version exists and has the correct format and hash.") 1383 RNS.log("You can clear the cache with the --clear-cache option and try again.") 1384 1385 if selected_hash == None: 1386 RNS.log("No release hash found for "+fw_filename+". The firmware integrity could not be verified.") 1387 graceful_exit(97) 1388 1389 RNS.log("Verifying firmware integrity...") 1390 fw_file = open(UPD_DIR+"/"+selected_version+"/"+fw_filename, "rb") 1391 expected_hash = bytes.fromhex(selected_hash) 1392 file_hash = hashlib.sha256(fw_file.read()).hexdigest() 1393 if file_hash == selected_hash: 1394 pass 1395 else: 1396 RNS.log("") 1397 RNS.log(f"Firmware hash {file_hash} but should be {selected_hash}, possibly due to download corruption.") 1398 RNS.log("Firmware corrupt. Try clearing the local firmware cache with: rnodeconf --clear-cache") 1399 graceful_exit(96) 1400 1401 except Exception as e: 1402 RNS.log("An error occurred while checking firmware file integrity. The contained exception was:") 1403 RNS.log(str(e)) 1404 graceful_exit(95) 1405 1406 except Exception as e: 1407 RNS.log("Could not download required firmware file: ") 1408 RNS.log(str(update_target_url)) 1409 RNS.log("The contained exception was:") 1410 RNS.log(str(e)) 1411 graceful_exit() 1412 1413 except Exception as e: 1414 RNS.log("An error occurred while reading version information for "+str(fw_filename)+". The contained exception was:") 1415 RNS.log(str(e)) 1416 graceful_exit() 1417 1418 def rnode_open_serial(port): 1419 import serial 1420 return serial.Serial( 1421 port = port, 1422 baudrate = rnode_baudrate, 1423 bytesize = 8, 1424 parity = serial.PARITY_NONE, 1425 stopbits = 1, 1426 xonxoff = False, 1427 rtscts = False, 1428 timeout = 0, 1429 inter_byte_timeout = None, 1430 write_timeout = None, 1431 dsrdtr = False 1432 ) 1433 1434 1435 def graceful_exit(C=0): 1436 if RNS.vendor.platformutils.is_windows(): 1437 RNS.log("Windows detected; delaying DTR",RNS.LOG_VERBOSE) 1438 if rnode: 1439 RNS.log("Sending \"Leave\" to Rnode",RNS.LOG_VERBOSE) 1440 rnode.leave() # Leave has wait built in 1441 elif rnode_serial: 1442 RNS.log("Closing raw serial",RNS.LOG_VERBOSE) 1443 sleep(1) # Wait for MCU to complete operation before DTR goes false 1444 rnode_serial.close() 1445 RNS.log("Exiting: Code "+str(C),RNS.LOG_INFO) 1446 exit(C) 1447 1448 1449 device_signer = None 1450 force_update = False 1451 upd_nocheck = False 1452 def main(): 1453 global mapped_product, mapped_model, fw_filename, fw_url, selected_version, force_update, upd_nocheck, device_signer 1454 1455 try: 1456 if not util.find_spec("serial"): 1457 raise ImportError("Serial module could not be found") 1458 except ImportError: 1459 print("") 1460 print("RNode Config Utility needs pyserial to work.") 1461 print("You can install it with: pip3 install pyserial") 1462 print("") 1463 graceful_exit() 1464 1465 try: 1466 if not util.find_spec("cryptography"): 1467 raise ImportError("Cryptography module could not be found") 1468 except ImportError: 1469 print("") 1470 print("RNode Config Utility needs the cryptography module to work.") 1471 print("You can install it with: pip3 install cryptography") 1472 print("") 1473 graceful_exit() 1474 1475 import serial 1476 from serial.tools import list_ports 1477 1478 try: 1479 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.") 1480 parser.add_argument("-i", "--info", action="store_true", help="Show device info") 1481 parser.add_argument("-a", "--autoinstall", action="store_true", help="Automatic installation on various supported devices") 1482 parser.add_argument("-u", "--update", action="store_true", help="Update firmware to the latest version") 1483 parser.add_argument("-U", "--force-update", action="store_true", help="Update to specified firmware even if version matches or is older than installed version") 1484 parser.add_argument("--fw-version", action="store", metavar="version", default=None, help="Use a specific firmware version for update or autoinstall") 1485 parser.add_argument("--fw-url", action="store", metavar="url", default=None, help="Use an alternate firmware download URL") 1486 parser.add_argument("--nocheck", action="store_true", help="Don't check for firmware updates online") 1487 parser.add_argument("-e", "--extract", action="store_true", help="Extract firmware from connected RNode for later use") 1488 parser.add_argument("-E", "--use-extracted", action="store_true", help="Use the extracted firmware for autoinstallation or update") 1489 parser.add_argument("-C", "--clear-cache", action="store_true", help="Clear locally cached firmware files") 1490 parser.add_argument("--baud-flash", action="store", metavar="baud_flash", type=str, default="921600", help="Set specific baud rate when flashing device. Default is 921600") 1491 1492 parser.add_argument("-N", "--normal", action="store_true", help="Switch device to normal mode") 1493 parser.add_argument("-T", "--tnc", action="store_true", help="Switch device to TNC mode") 1494 1495 parser.add_argument("-b", "--bluetooth-on", action="store_true", help="Turn device bluetooth on") 1496 parser.add_argument("-B", "--bluetooth-off", action="store_true", help="Turn device bluetooth off") 1497 parser.add_argument("-p", "--bluetooth-pair", action="store_true", help="Put device into bluetooth pairing mode") 1498 1499 parser.add_argument("-w", "--wifi", action="store", metavar="mode", default=None, help="Set WiFi mode (OFF, AP or STATION)") 1500 parser.add_argument("--channel", action="store", metavar="channel", default=None, help="Set WiFi channel") 1501 parser.add_argument("--ssid", action="store", metavar="ssid", default=None, help="Set WiFi SSID (NONE to delete)") 1502 parser.add_argument("--psk", action="store", metavar="psk", default=None, help="Set WiFi PSK (NONE to delete)") 1503 parser.add_argument("--show-psk", action="store_true", default=False, help="Display stored WiFi PSK") 1504 parser.add_argument("--ip", action="store", metavar="ip", default=None, help="Set static WiFi IP address (NONE for DHCP)") 1505 parser.add_argument("--nm", action="store", metavar="nm", default=None, help="Set static WiFi network mask (NONE for DHCP)") 1506 1507 parser.add_argument("-D", "--display", action="store", metavar="i", type=int, default=None, help="Set display intensity (0-255)") 1508 parser.add_argument("-t", "--timeout", action="store", metavar="s", type=int, default=None, help="Set display timeout in seconds, 0 to disable") 1509 parser.add_argument("-R", "--rotation", action="store", metavar="rotation", type=int, default=None, help="Set display rotation, valid values are 0 through 3") 1510 parser.add_argument("--display-addr", action="store", metavar="byte", type=str, default=None, help="Set display address as hex byte (00 - FF)") 1511 parser.add_argument("--recondition-display", action="store_true", help="Start display reconditioning") 1512 1513 parser.add_argument("--np", action="store", metavar="i", type=int, default=None, help="Set NeoPixel intensity (0-255)") 1514 1515 parser.add_argument("--freq", action="store", metavar="Hz", type=int, default=None, help="Frequency in Hz for TNC mode") 1516 parser.add_argument("--bw", action="store", metavar="Hz", type=int, default=None, help="Bandwidth in Hz for TNC mode") 1517 parser.add_argument("--txp", action="store", metavar="dBm", type=int, default=None, help="TX power in dBm for TNC mode") 1518 parser.add_argument("--sf", action="store", metavar="factor", type=int, default=None, help="Spreading factor for TNC mode (7 - 12)") 1519 parser.add_argument("--cr", action="store", metavar="rate", type=int, default=None, help="Coding rate for TNC mode (5 - 8)") 1520 1521 parser.add_argument("-x", "--ia-enable", action="store_true", help="Enable interference avoidance") 1522 parser.add_argument("-X", "--ia-disable", action="store_true", help="Disable interference avoidance") 1523 1524 parser.add_argument("-c", "--config", action="store_true", help="Print device configuration") 1525 1526 parser.add_argument("--eeprom-backup", action="store_true", help="Backup EEPROM to file") 1527 parser.add_argument("--eeprom-dump", action="store_true", help="Dump EEPROM to console") 1528 parser.add_argument("--eeprom-wipe", action="store_true", help="Unlock and wipe EEPROM") 1529 1530 parser.add_argument("-P", "--public", action="store_true", help="Display public part of signing key") 1531 parser.add_argument("--trust-key", action="store", metavar="hexbytes", type=str, default=None, help="Public key to trust for device verification") 1532 1533 parser.add_argument("--version", action="store_true", help="Print program version and exit") 1534 1535 parser.add_argument("-f", "--flash", action="store_true", help="Flash firmware and bootstrap EEPROM") 1536 parser.add_argument("-r", "--rom", action="store_true", help="Bootstrap EEPROM without flashing firmware") 1537 parser.add_argument("-k", "--key", action="store_true", help="Generate a new signing key and exit") # 1538 parser.add_argument("-S", "--sign", action="store_true", help="Display public part of signing key") 1539 parser.add_argument("-H", "--firmware-hash", action="store", help="Set installed firmware hash") 1540 parser.add_argument("-K", "--get-target-firmware-hash", action="store_true", help=argparse.SUPPRESS) # Get target firmware hash from device 1541 parser.add_argument("-L", "--get-firmware-hash", action="store_true", help=argparse.SUPPRESS) # Get calculated firmware hash from device 1542 parser.add_argument("--platform", action="store", metavar="platform", type=str, default=None, help="Platform specification for device bootstrap") 1543 parser.add_argument("--product", action="store", metavar="product", type=str, default=None, help="Product specification for device bootstrap") # 1544 parser.add_argument("--model", action="store", metavar="model", type=str, default=None, help="Model code for device bootstrap") 1545 parser.add_argument("--hwrev", action="store", metavar="revision", type=int, default=None, help="Hardware revision for device bootstrap") 1546 1547 parser.add_argument("port", nargs="?", default=None, help="serial port where RNode is attached", type=str) 1548 args = parser.parse_args() 1549 1550 def print_donation_block(): 1551 print(" Ethereum : "+eth_addr) 1552 print(" Bitcoin : "+btc_addr) 1553 print(" Monero : "+xmr_addr) 1554 print(" Ko-Fi : https://ko-fi.com/markqvist") 1555 print(" LiberaPay : https://liberapay.com/reticulum") 1556 print("") 1557 print(" Info : https://reticulum.network") 1558 print(" Code : https://github.com/markqvist") 1559 1560 if args.version: 1561 print("rnodeconf "+program_version) 1562 graceful_exit(0) 1563 1564 if args.clear_cache: 1565 RNS.log("Clearing local firmware cache...") 1566 import shutil 1567 shutil.rmtree(UPD_DIR) 1568 RNS.log("Done") 1569 graceful_exit(0) 1570 1571 if args.fw_version != None: 1572 selected_version = args.fw_version 1573 try: 1574 check_float = float(selected_version) 1575 except ValueError: 1576 RNS.log("Selected version \""+selected_version+"\" does not appear to be a number.") 1577 graceful_exit() 1578 1579 if args.fw_url != None: 1580 fw_url = args.fw_url 1581 1582 if args.force_update: 1583 force_update = True 1584 1585 if args.nocheck: 1586 upd_nocheck = True 1587 1588 if args.public or args.key or args.flash or args.rom or args.autoinstall or args.trust_key: 1589 from cryptography.hazmat.primitives import hashes 1590 from cryptography.hazmat.backends import default_backend 1591 from cryptography.hazmat.primitives import serialization 1592 from cryptography.hazmat.primitives.serialization import load_der_public_key 1593 from cryptography.hazmat.primitives.serialization import load_der_private_key 1594 from cryptography.hazmat.primitives.asymmetric import rsa 1595 from cryptography.hazmat.primitives.asymmetric import padding 1596 1597 clear = lambda: os.system('clear') 1598 1599 if args.trust_key: 1600 try: 1601 public_bytes = bytes.fromhex(args.trust_key) 1602 try: 1603 public_key = load_der_public_key(public_bytes, backend=default_backend()) 1604 key_hash = hashlib.sha256(public_bytes).hexdigest() 1605 RNS.log("Trusting key: "+str(key_hash)) 1606 f = open(TK_DIR+"/"+str(key_hash)+".pubkey", "wb") 1607 f.write(public_bytes) 1608 f.close() 1609 1610 except Exception as e: 1611 RNS.log("Could not create public key from supplied data. Check that the key format is valid.") 1612 RNS.log(str(e)) 1613 1614 except Exception as e: 1615 RNS.log("Invalid key data supplied") 1616 graceful_exit(0) 1617 1618 if args.use_extracted and ((args.update and args.port != None) or args.autoinstall): 1619 print("") 1620 print("You have specified that rnodeconf should use a firmware extracted") 1621 print("from another device. Please note that this *only* works if you are") 1622 print("targeting a device of the same type that the firmware came from!") 1623 print("") 1624 print("Flashing this firmware to a device it was not created for will most") 1625 print("likely result in it being inoperable until it is updated with the") 1626 print("correct firmware. Hit enter to continue.") 1627 input() 1628 1629 if args.extract: 1630 # clear() 1631 print("") 1632 print("RNode Firmware Extraction") 1633 print("") 1634 if not args.port: 1635 ports = list_ports.comports() 1636 portlist = [] 1637 for port in ports: 1638 portlist.insert(0, port) 1639 1640 pi = 1 1641 print("Detected serial ports:") 1642 for port in portlist: 1643 print(" ["+str(pi)+"] "+str(port.device)+" ("+str(port.product)+", "+str(port.serial_number)+")") 1644 pi += 1 1645 1646 print("\nEnter the number of the serial port your device is connected to:\n? ", end="") 1647 try: 1648 c_port = int(input()) 1649 if c_port < 1 or c_port > len(ports): 1650 raise ValueError() 1651 1652 selected_port = portlist[c_port-1] 1653 except Exception as e: 1654 print("That port does not exist, exiting now.") 1655 graceful_exit() 1656 1657 if selected_port == None: 1658 print("Could not select port, exiting now.") 1659 graceful_exit() 1660 1661 port_path = selected_port.device 1662 port_product = selected_port.product 1663 port_serialno = selected_port.serial_number 1664 1665 print("\nOk, using device on "+str(port_path)+" ("+str(port_product)+", "+str(port_serialno)+")") 1666 1667 else: 1668 ports = list_ports.comports() 1669 1670 for port in ports: 1671 if port.device == args.port: 1672 selected_port = port 1673 1674 if selected_port == None: 1675 print("Could not find specified port "+str(args.port)+", exiting now") 1676 graceful_exit() 1677 1678 port_path = selected_port.device 1679 port_product = selected_port.product 1680 port_serialno = selected_port.serial_number 1681 1682 print("\nUsing device on "+str(port_path)) 1683 1684 print("\nProbing device...") 1685 1686 try: 1687 rnode_serial = rnode_open_serial(port_path) 1688 except Exception as e: 1689 RNS.log("Could not open the specified serial port. The contained exception was:") 1690 RNS.log(str(e)) 1691 graceful_exit() 1692 1693 rnode = RNode(rnode_serial) 1694 rnode.usb_serial_id = port_serialno 1695 thread = threading.Thread(target=rnode.readLoop, daemon=True).start() 1696 try: 1697 rnode.device_probe() 1698 except Exception as e: 1699 RNS.log("No answer from device") 1700 1701 if rnode.detected: 1702 RNS.log("Trying to read EEPROM...") 1703 rnode.download_eeprom() 1704 else: 1705 RNS.log("Could not detect a connected RNode") 1706 1707 if rnode.platform == ROM.PLATFORM_ESP32: 1708 if rnode.provisioned: 1709 if not rnode.signature_valid: 1710 print("\nThe device signature in this RNode is unknown and cannot be verified. It is still") 1711 print("possible to extract the firmware from it, but you should make absolutely sure that") 1712 print("it comes from a trusted source. It is possible that someone could have modified the") 1713 print("firmware. If that is the case, these modifications will propagate to any new RNodes") 1714 print("descendent from this one!") 1715 print("\nHit enter if you are sure you want to continue.") 1716 input() 1717 1718 if rnode.firmware_hash != None: 1719 extracted_hash = rnode.firmware_hash 1720 extracted_version = rnode.version 1721 rnode.disconnect() 1722 v_str = str(extracted_version)+" "+RNS.hexrep(extracted_hash, delimit=False) 1723 print("\nFound RNode Firmvare v"+v_str) 1724 1725 print("\nReady to extract firmware images from the RNode") 1726 print("Press enter to start the extraction process") 1727 input() 1728 extract_recovery_esptool() 1729 1730 hash_f = open(EXT_DIR+"/extracted_rnode_firmware.version", "wb") 1731 hash_f.write(v_str.encode("utf-8")) 1732 hash_f.close() 1733 1734 extraction_parts = [ 1735 ("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\""), 1736 ("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\""), 1737 ("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\""), 1738 ("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\""), 1739 ("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\""), 1740 ] 1741 import subprocess, shlex 1742 for part, command in extraction_parts: 1743 print("Extracting "+part+"...") 1744 if subprocess.call(shlex.split(command), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) != 0: 1745 print("The extraction failed, the following command did not complete successfully:\n"+command) 1746 exit(182) 1747 1748 print("\nFirmware successfully extracted!") 1749 print("\nYou can now use this firmware to update or autoinstall other RNodes") 1750 exit() 1751 else: 1752 print("Could not read firmware information from device") 1753 1754 print("\nRNode firmware extraction failed") 1755 graceful_exit(180) 1756 else: 1757 print("\nFirmware extraction is currently only supported on ESP32-based RNodes.") 1758 graceful_exit(170) 1759 1760 if args.autoinstall: 1761 clear() 1762 if not args.port: 1763 print("\nHello!\n\nThis guide will help you install the RNode firmware on supported") 1764 print("and homebrew devices. Please connect the device you wish to set\nup now. Hit enter when it is connected.") 1765 input() 1766 1767 global squashvw 1768 squashvw = True 1769 1770 selected_port = None 1771 if not args.port: 1772 ports = list_ports.comports() 1773 portlist = [] 1774 for port in ports: 1775 portlist.insert(0, port) 1776 1777 pi = 1 1778 print("Detected serial ports:") 1779 for port in portlist: 1780 print(" ["+str(pi)+"] "+str(port.device)+" ("+str(port.product)+", "+str(port.serial_number)+")") 1781 pi += 1 1782 1783 print("\nEnter the number of the serial port your device is connected to:\n? ", end="") 1784 try: 1785 c_port = int(input()) 1786 if c_port < 1 or c_port > len(ports): 1787 raise ValueError() 1788 1789 selected_port = portlist[c_port-1] 1790 except Exception as e: 1791 print("That port does not exist, exiting now.") 1792 graceful_exit() 1793 1794 if selected_port == None: 1795 print("Could not select port, exiting now.") 1796 graceful_exit() 1797 1798 port_path = selected_port.device 1799 port_product = selected_port.product 1800 port_serialno = selected_port.serial_number 1801 1802 clear() 1803 print("\nOk, using device on "+str(port_path)+" ("+str(port_product)+", "+str(port_serialno)+")") 1804 1805 else: 1806 ports = list_ports.comports() 1807 1808 for port in ports: 1809 if port.device == args.port: 1810 selected_port = port 1811 1812 if selected_port == None: 1813 print("Could not find specified port "+str(args.port)+", exiting now") 1814 graceful_exit() 1815 1816 port_path = selected_port.device 1817 port_product = selected_port.product 1818 port_serialno = selected_port.serial_number 1819 1820 print("\nUsing device on "+str(port_path)) 1821 1822 print("\nProbing device...") 1823 1824 try: 1825 rnode_serial = rnode_open_serial(port_path) 1826 except Exception as e: 1827 RNS.log("Could not open the specified serial port. The contained exception was:") 1828 RNS.log(str(e)) 1829 graceful_exit() 1830 1831 rnode = RNode(rnode_serial) 1832 rnode.usb_serial_id = port_serialno 1833 thread = threading.Thread(target=rnode.readLoop, daemon=True).start() 1834 try: 1835 rnode.device_probe() 1836 except Exception as e: 1837 RNS.log("No answer from device") 1838 1839 if rnode.detected: 1840 RNS.log("Trying to read EEPROM...") 1841 rnode.download_eeprom() 1842 1843 if rnode.provisioned and rnode.signature_valid: 1844 print("\nThis device is already installed and provisioned. No further action will") 1845 print("be taken. If you wish to completely reinstall this device, you must first") 1846 print("wipe the current EEPROM. See the help for more info.\n\nExiting now.") 1847 graceful_exit() 1848 1849 print("\n---------------------------------------------------------------------------") 1850 print(" Device Selection") 1851 if rnode.detected: 1852 print("\nThe device seems to have an RNode firmware installed, but it was not") 1853 print("provisioned correctly, or it is corrupt. We are going to reinstall the") 1854 print("correct firmware and provision it.") 1855 else: 1856 print("\nIt looks like this is a fresh device with no RNode firmware.") 1857 1858 print("") 1859 print("What kind of device is this?\n") 1860 print(" | Select this option if you have an RNode of a specific") 1861 print(" \\ / type, built from a recipe or bought from a vendor.") 1862 print(" '") 1863 print("[1] A specific kind of RNode") 1864 print("") 1865 print(" | Select this option if you have put together an RNode") 1866 print(" \\ / of your own design, or if you are prototyping one.") 1867 print(" '") 1868 print("[2] Homebrew RNode") 1869 print("") 1870 print(" | Select one of these options if you want to easily turn") 1871 print(" \\ / a supported development board into an RNode.") 1872 print(" '") 1873 print("[3] LilyGO LoRa32 v2.1 (aka T3 v1.6 / T3 v1.6.1)") 1874 print("[4] LilyGO LoRa32 v2.0") 1875 print("[5] LilyGO LoRa32 v1.0") 1876 print("[6] LilyGO T-Beam") 1877 print("[7] Heltec LoRa32 v2") 1878 print("[8] Heltec LoRa32 v3") 1879 print("[9] Heltec LoRa32 v4") 1880 print("[10] LilyGO LoRa T3S3") 1881 print("[11] RAK4631") 1882 print("[12] LilyGo T-Echo") 1883 print("[13] LilyGO T-Beam Supreme") 1884 print("[14] LilyGO T-Deck") 1885 print("[15] Heltec T114") 1886 print("[16] Seeed XIAO ESP32S3 Wio-SX1262") 1887 print("") 1888 print("---------------------------------------------------------------------------") 1889 print("\nEnter the number that matches your device type:\n? ", end="") 1890 1891 selected_product = None 1892 try: 1893 c_dev = int(input()) 1894 c_mod = False 1895 if c_dev < 1 or c_dev > 16: 1896 raise ValueError() 1897 elif c_dev == 1: 1898 selected_product = ROM.PRODUCT_RNODE 1899 elif c_dev == 2: 1900 selected_product = ROM.PRODUCT_HMBRW 1901 clear() 1902 print("") 1903 print("---------------------------------------------------------------------------") 1904 print(" Homebrew RNode Installer") 1905 print("") 1906 print("This option allows you to install and provision the RNode firmware on a") 1907 print("custom board design, or a custom device created by coupling a generic") 1908 print("development board with a supported transceiver module.") 1909 print("") 1910 print("Important! Using RNode firmware on homebrew devices should currently be") 1911 print("considered experimental. It is not intended for production or critical use.") 1912 print("The currently supplied firmware is provided AS-IS as a courtesy to those") 1913 print("who would like to experiment with it. Hit enter to continue.") 1914 print("---------------------------------------------------------------------------") 1915 input() 1916 elif c_dev == 6: 1917 selected_product = ROM.PRODUCT_TBEAM 1918 clear() 1919 print("") 1920 print("---------------------------------------------------------------------------") 1921 print(" T-Beam RNode Installer") 1922 print("") 1923 print("The RNode firmware can currently be installed on T-Beam devices using the") 1924 print("SX1276, SX1278, SX1262 and SX1268 transceiver chips.") 1925 print("") 1926 print("Important! Using RNode firmware on T-Beam devices should currently be") 1927 print("considered experimental. It is not intended for production or critical use.") 1928 print("The currently supplied firmware is provided AS-IS as a courtesy to those") 1929 print("who would like to experiment with it. Hit enter to continue.") 1930 print("---------------------------------------------------------------------------") 1931 input() 1932 elif c_dev == 13: 1933 selected_product = ROM.PRODUCT_TBEAM_S_V1 1934 clear() 1935 print("") 1936 print("---------------------------------------------------------------------------") 1937 print(" T-Beam Supreme RNode Installer") 1938 print("") 1939 print("The RNode firmware can currently be installed on T-Beam Supreme devices") 1940 print("using the SX1262 and SX1268 transceiver chips.") 1941 print("") 1942 print("Important! Using RNode firmware on T-Beam devices should currently be") 1943 print("considered experimental. It is not intended for production or critical use.") 1944 print("The currently supplied firmware is provided AS-IS as a courtesy to those") 1945 print("who would like to experiment with it. Hit enter to continue.") 1946 print("---------------------------------------------------------------------------") 1947 input() 1948 elif c_dev == 14: 1949 selected_product = ROM.PRODUCT_TDECK 1950 clear() 1951 print("") 1952 print("---------------------------------------------------------------------------") 1953 print(" T-Deck RNode Installer") 1954 print("") 1955 print("The RNode firmware can currently be installed on T-Deck devices using the") 1956 print("SX1262 and SX1268 transceiver chips.") 1957 print("") 1958 print("Important! Using RNode firmware on T-Beam devices should currently be") 1959 print("considered experimental. It is not intended for production or critical use.") 1960 print("The currently supplied firmware is provided AS-IS as a courtesy to those") 1961 print("who would like to experiment with it. Hit enter to continue.") 1962 print("---------------------------------------------------------------------------") 1963 input() 1964 elif c_dev == 4: 1965 selected_product = ROM.PRODUCT_T32_20 1966 clear() 1967 print("") 1968 print("---------------------------------------------------------------------------") 1969 print(" LilyGO LoRa32 v2.0 RNode Installer") 1970 print("") 1971 print("Important! Using RNode firmware on LoRa32 devices should currently be") 1972 print("considered experimental. It is not intended for production or critical use.") 1973 print("The currently supplied firmware is provided AS-IS as a courtesy to those") 1974 print("who would like to experiment with it. Hit enter to continue.") 1975 print("---------------------------------------------------------------------------") 1976 input() 1977 elif c_dev == 5: 1978 selected_product = ROM.PRODUCT_T32_10 1979 clear() 1980 print("") 1981 print("---------------------------------------------------------------------------") 1982 print(" LilyGO LoRa32 v1.0 RNode Installer") 1983 print("") 1984 print("Important! Using RNode firmware on LoRa32 devices should currently be") 1985 print("considered experimental. It is not intended for production or critical use.") 1986 print("The currently supplied firmware is provided AS-IS as a courtesy to those") 1987 print("who would like to experiment with it.") 1988 print("") 1989 print("Please Note! This device is known to have a faulty battery charging circuit,") 1990 print("which can result in overcharging and damaging batteries. If at all possible,") 1991 print("it is recommended to avoid this device.") 1992 print("") 1993 print("Hit enter if you're sure you wish to continue.") 1994 print("---------------------------------------------------------------------------") 1995 input() 1996 elif c_dev == 3: 1997 selected_product = ROM.PRODUCT_T32_21 1998 clear() 1999 print("") 2000 print("---------------------------------------------------------------------------") 2001 print(" LilyGO LoRa32 v2.1 RNode Installer") 2002 print("") 2003 print("Important! Using RNode firmware on LoRa32 devices should currently be") 2004 print("considered experimental. It is not intended for production or critical use.") 2005 print("The currently supplied firmware is provided AS-IS as a courtesy to those") 2006 print("who would like to experiment with it. Hit enter to continue.") 2007 print("---------------------------------------------------------------------------") 2008 input() 2009 elif c_dev == 7: 2010 selected_product = ROM.PRODUCT_H32_V2 2011 clear() 2012 print("") 2013 print("---------------------------------------------------------------------------") 2014 print(" Heltec LoRa32 v2.0 RNode Installer") 2015 print("") 2016 print("Important! Using RNode firmware on Heltec devices should currently be") 2017 print("considered experimental. It is not intended for production or critical use.") 2018 print("") 2019 print("The currently supplied firmware is provided AS-IS as a courtesy to those") 2020 print("who would like to experiment with it. Hit enter to continue.") 2021 print("---------------------------------------------------------------------------") 2022 input() 2023 elif c_dev == 10: 2024 selected_product = ROM.PRODUCT_RNODE 2025 c_mod = True 2026 clear() 2027 print("") 2028 print("---------------------------------------------------------------------------") 2029 print(" LilyGO LoRa32 T3S3 RNode Installer") 2030 print("") 2031 print("Important! Using RNode firmware on T3S3 devices should currently be") 2032 print("considered experimental. It is not intended for production or critical use.") 2033 print("") 2034 print("The currently supplied firmware is provided AS-IS as a courtesy to those") 2035 print("who would like to experiment with it. Hit enter to continue.") 2036 print("---------------------------------------------------------------------------") 2037 input() 2038 elif c_dev == 8: 2039 selected_product = ROM.PRODUCT_H32_V3 2040 clear() 2041 print("") 2042 print("---------------------------------------------------------------------------") 2043 print(" Heltec LoRa32 v3.0 RNode Installer") 2044 print("") 2045 print("Important! Using RNode firmware on Heltec devices should currently be") 2046 print("considered experimental. It is not intended for production or critical use.") 2047 print("") 2048 print("The currently supplied firmware is provided AS-IS as a courtesy to those") 2049 print("who would like to experiment with it. Hit enter to continue.") 2050 print("---------------------------------------------------------------------------") 2051 input() 2052 elif c_dev == 9: 2053 selected_product = ROM.PRODUCT_H32_V4 2054 clear() 2055 print("") 2056 print("---------------------------------------------------------------------------") 2057 print(" Heltec LoRa32 v4 RNode Installer") 2058 print("") 2059 print("Important! Using RNode firmware on Heltec devices should currently be") 2060 print("considered experimental. It is not intended for production or critical use.") 2061 print("") 2062 print("The currently supplied firmware is provided AS-IS as a courtesy to those") 2063 print("who would like to experiment with it. Hit enter to continue.") 2064 print("---------------------------------------------------------------------------") 2065 input() 2066 elif c_dev == 11: 2067 selected_product = ROM.PRODUCT_RAK4631 2068 clear() 2069 print("") 2070 print("---------------------------------------------------------------------------") 2071 print(" RAK4631 RNode Installer") 2072 print("") 2073 print("Important! Using RNode firmware on RAKwireless devices should currently be") 2074 print("considered experimental. It is not intended for production or critical use.") 2075 print("The currently supplied firmware is provided AS-IS as a courtesy to those") 2076 print("who would like to experiment with it. Hit enter to continue.") 2077 print("---------------------------------------------------------------------------") 2078 input() 2079 elif c_dev == 12: 2080 selected_product = ROM.PRODUCT_TECHO 2081 clear() 2082 print("") 2083 print("---------------------------------------------------------------------------") 2084 print(" LilyGo T-Echo RNode Installer") 2085 print("") 2086 print("Important! Using RNode firmware on LilyGo T-Echo devices should currently be") 2087 print("considered experimental. It is not intended for production or critical use.") 2088 print("The currently supplied firmware is provided AS-IS as a courtesy to those") 2089 print("who would like to experiment with it. Hit enter to continue.") 2090 print("---------------------------------------------------------------------------") 2091 input() 2092 elif c_dev == 15: 2093 selected_product = ROM.PRODUCT_HELTEC_T114 2094 clear() 2095 print("") 2096 print("---------------------------------------------------------------------------") 2097 print(" Heltec T114 RNode Installer") 2098 print("") 2099 print("Important! Using RNode firmware on Heltec T114 devices should currently be") 2100 print("considered experimental. It is not intended for production or critical use.") 2101 print("The currently supplied firmware is provided AS-IS as a courtesy to those") 2102 print("who would like to experiment with it. Hit enter to continue.") 2103 print("---------------------------------------------------------------------------") 2104 input() 2105 elif c_dev == 16: 2106 selected_product = ROM.PRODUCT_XIAO_S3 2107 clear() 2108 print("") 2109 print("---------------------------------------------------------------------------") 2110 print(" SeeedStudio XIAO esp32s3 wio RNode Installer") 2111 print("") 2112 print("Important! Using RNode firmware on SeeedStudio XIAO/wio devices should currently be") 2113 print("considered experimental. It is not intended for production or critical use.") 2114 print("The currently supplied firmware is provided AS-IS as a courtesy to those") 2115 print("who would like to experiment with it. Hit enter to continue.") 2116 print("---------------------------------------------------------------------------") 2117 input() 2118 2119 except Exception as e: 2120 print("That device type does not exist, exiting now.") 2121 graceful_exit() 2122 2123 selected_platform = None 2124 selected_model = None 2125 selected_mcu = None 2126 2127 if selected_product == ROM.PRODUCT_HMBRW: 2128 print("\nWhat kind of microcontroller is your board based on?\n") 2129 print("[1] AVR ATmega1284P") 2130 print("[2] AVR ATmega2560") 2131 print("[3] Espressif Systems ESP32") 2132 print("\n? ", end="") 2133 try: 2134 c_mcu = int(input()) 2135 if c_mcu < 1 or c_mcu > 3: 2136 raise ValueError() 2137 elif c_mcu == 1: 2138 selected_mcu = ROM.MCU_1284P 2139 selected_platform = ROM.PLATFORM_AVR 2140 elif c_mcu == 2: 2141 selected_mcu = ROM.MCU_2560 2142 selected_platform = ROM.PLATFORM_AVR 2143 elif c_mcu == 3: 2144 selected_mcu = ROM.MCU_ESP32 2145 selected_platform = ROM.PLATFORM_ESP32 2146 selected_model = ROM.MODEL_FF 2147 2148 except Exception as e: 2149 print("That MCU type does not exist, exiting now.") 2150 graceful_exit() 2151 2152 print("\nWhat transceiver module does your board use?\n") 2153 print("[1] SX1276/SX1278 with antenna port on PA_BOOST pin") 2154 print("[2] SX1276/SX1278 with antenna port on RFO pin") 2155 print("\n? ", end="") 2156 try: 2157 c_trxm = int(input()) 2158 if c_trxm < 1 or c_trxm > 3: 2159 raise ValueError() 2160 elif c_trxm == 1: 2161 selected_model = ROM.MODEL_FE 2162 elif c_trxm == 2: 2163 selected_model = ROM.MODEL_FF 2164 2165 except Exception as e: 2166 print("That transceiver type does not exist, exiting now.") 2167 graceful_exit() 2168 2169 2170 elif selected_product == ROM.PRODUCT_RNODE: 2171 if not c_mod: 2172 selected_mcu = ROM.MCU_1284P 2173 print("\nWhat model is this RNode?\n") 2174 print("[1] Handheld v2.1 RNode, 410 - 525 MHz") 2175 print("[2] Handheld v2.1 RNode, 820 - 1020 MHz") 2176 print("") 2177 print("[3] Original v1.x RNode, 410 - 525 MHz") 2178 print("[4] Original v1.x RNode, 820 - 1020 MHz") 2179 print("") 2180 print("[5] Prototype v2.2 RNode, 410 - 525 MHz") 2181 print("[6] Prototype v2.2 RNode, 820 - 1020 MHz") 2182 print("\n? ", end="") 2183 try: 2184 c_model = int(input()) 2185 if c_model < 1 or c_model > 6: 2186 raise ValueError() 2187 elif c_model == 1: 2188 selected_model = ROM.MODEL_A2 2189 selected_mcu = ROM.MCU_ESP32 2190 selected_platform = ROM.PLATFORM_ESP32 2191 elif c_model == 2: 2192 selected_model = ROM.MODEL_A7 2193 selected_mcu = ROM.MCU_ESP32 2194 selected_platform = ROM.PLATFORM_ESP32 2195 elif c_model == 3: 2196 selected_model = ROM.MODEL_A4 2197 selected_platform = ROM.PLATFORM_AVR 2198 elif c_model == 4: 2199 selected_model = ROM.MODEL_A9 2200 selected_platform = ROM.PLATFORM_AVR 2201 elif c_model == 5: 2202 selected_model = ROM.MODEL_A1 2203 selected_mcu = ROM.MCU_ESP32 2204 selected_platform = ROM.PLATFORM_ESP32 2205 elif c_model == 6: 2206 selected_model = ROM.MODEL_A6 2207 selected_mcu = ROM.MCU_ESP32 2208 selected_platform = ROM.PLATFORM_ESP32 2209 # elif c_model == 5: 2210 # selected_model = ROM.MODEL_A3 2211 # selected_mcu = ROM.MCU_ESP32 2212 # selected_platform = ROM.PLATFORM_ESP32 2213 # elif c_model == 6: 2214 # selected_model = ROM.MODEL_A8 2215 # selected_mcu = ROM.MCU_ESP32 2216 # selected_platform = ROM.PLATFORM_ESP32 2217 except Exception as e: 2218 print("That model does not exist, exiting now.") 2219 graceful_exit() 2220 else: 2221 print("\nWhat band is this T3S3 for?\n") 2222 print("[1] 433 MHz (with SX1278 chip)") 2223 print("[2] 868/915/923 MHz (with SX1276 chip)") 2224 print(""); 2225 print("[3] 433 MHz (with SX1268 chip)") 2226 print("[4] 868/915/923 MHz (with SX1262 chip)") 2227 print(""); 2228 print("[5] 2.4 GHz (with SX1280 chip and PA)") 2229 print("\n? ", end="") 2230 try: 2231 c_model = int(input()) 2232 if c_model < 1 or c_model > 5: 2233 raise ValueError() 2234 elif c_model == 1: 2235 selected_model = ROM.MODEL_A5 2236 selected_mcu = ROM.MCU_ESP32 2237 selected_platform = ROM.PLATFORM_ESP32 2238 elif c_model == 2: 2239 selected_model = ROM.MODEL_AA 2240 selected_mcu = ROM.MCU_ESP32 2241 selected_platform = ROM.PLATFORM_ESP32 2242 elif c_model == 3: 2243 selected_model = ROM.MODEL_A1 2244 selected_mcu = ROM.MCU_ESP32 2245 selected_platform = ROM.PLATFORM_ESP32 2246 elif c_model == 4: 2247 selected_model = ROM.MODEL_A6 2248 selected_mcu = ROM.MCU_ESP32 2249 selected_platform = ROM.PLATFORM_ESP32 2250 elif c_model == 5: 2251 selected_model = ROM.MODEL_AC 2252 selected_mcu = ROM.MCU_ESP32 2253 selected_platform = ROM.PLATFORM_ESP32 2254 except Exception as e: 2255 print("That model does not exist, exiting now.") 2256 graceful_exit() 2257 2258 elif selected_product == ROM.PRODUCT_TBEAM: 2259 selected_mcu = ROM.MCU_ESP32 2260 print("\nWhat band is this T-Beam for?\n") 2261 print("[1] 433 MHz (with SX1278 chip)") 2262 print("[2] 868/915/923 MHz (with SX1276 chip)") 2263 print(""); 2264 print("[3] 433 MHz (with SX1268 chip)") 2265 print("[4] 868/915/923 MHz (with SX1262 chip)") 2266 print("\n? ", end="") 2267 try: 2268 c_model = int(input()) 2269 if c_model < 1 or c_model > 4: 2270 raise ValueError() 2271 elif c_model == 1: 2272 selected_model = ROM.MODEL_E4 2273 selected_platform = ROM.PLATFORM_ESP32 2274 elif c_model == 2: 2275 selected_model = ROM.MODEL_E9 2276 selected_platform = ROM.PLATFORM_ESP32 2277 elif c_model == 3: 2278 selected_model = ROM.MODEL_E3 2279 selected_platform = ROM.PLATFORM_ESP32 2280 elif c_model == 4: 2281 selected_model = ROM.MODEL_E8 2282 selected_platform = ROM.PLATFORM_ESP32 2283 except Exception as e: 2284 print("That band does not exist, exiting now.") 2285 graceful_exit() 2286 2287 elif selected_product == ROM.PRODUCT_TBEAM_S_V1: 2288 selected_mcu = ROM.MCU_ESP32 2289 print("\nWhat band is this T-Beam Supreme for?\n") 2290 print("[1] 433 MHz (with SX1268 chip)") 2291 print("[2] 868/915/923 MHz (with SX1262 chip)") 2292 print("\n? ", end="") 2293 try: 2294 c_model = int(input()) 2295 if c_model < 1 or c_model > 2: 2296 raise ValueError() 2297 elif c_model == 1: 2298 selected_model = ROM.MODEL_DB 2299 selected_platform = ROM.PLATFORM_ESP32 2300 elif c_model == 2: 2301 selected_model = ROM.MODEL_DC 2302 selected_platform = ROM.PLATFORM_ESP32 2303 except Exception as e: 2304 print("That band does not exist, exiting now.") 2305 graceful_exit() 2306 2307 elif selected_product == ROM.PRODUCT_TDECK: 2308 selected_mcu = ROM.MCU_ESP32 2309 print("\nWhat band is this T-Deck for?\n") 2310 print("[1] 433 MHz (with SX1268 chip)") 2311 print("[2] 868/915/923 MHz (with SX1262 chip)") 2312 print("\n? ", end="") 2313 try: 2314 c_model = int(input()) 2315 if c_model < 1 or c_model > 2: 2316 raise ValueError() 2317 elif c_model == 1: 2318 selected_model = ROM.MODEL_D4 2319 selected_platform = ROM.PLATFORM_ESP32 2320 elif c_model == 2: 2321 selected_model = ROM.MODEL_D9 2322 selected_platform = ROM.PLATFORM_ESP32 2323 except Exception as e: 2324 print("That band does not exist, exiting now.") 2325 graceful_exit() 2326 2327 elif selected_product == ROM.PRODUCT_T32_10: 2328 selected_mcu = ROM.MCU_ESP32 2329 print("\nWhat band is this LoRa32 for?\n") 2330 print("[1] 433 MHz") 2331 print("[2] 868 MHz") 2332 print("[3] 915 MHz") 2333 print("[4] 923 MHz") 2334 print("\n? ", end="") 2335 try: 2336 c_model = int(input()) 2337 if c_model < 1 or c_model > 4: 2338 raise ValueError() 2339 elif c_model == 1: 2340 selected_model = ROM.MODEL_BA 2341 selected_platform = ROM.PLATFORM_ESP32 2342 elif c_model > 1: 2343 selected_model = ROM.MODEL_BB 2344 selected_platform = ROM.PLATFORM_ESP32 2345 except Exception as e: 2346 print("That band does not exist, exiting now.") 2347 graceful_exit() 2348 2349 elif selected_product == ROM.PRODUCT_T32_20: 2350 selected_mcu = ROM.MCU_ESP32 2351 print("\nWhat band is this LoRa32 for?\n") 2352 print("[1] 433 MHz") 2353 print("[2] 868 MHz") 2354 print("[3] 915 MHz") 2355 print("[4] 923 MHz") 2356 print("\n? ", end="") 2357 try: 2358 c_model = int(input()) 2359 if c_model < 1 or c_model > 4: 2360 raise ValueError() 2361 elif c_model == 1: 2362 selected_model = ROM.MODEL_B3 2363 selected_platform = ROM.PLATFORM_ESP32 2364 elif c_model > 1: 2365 selected_model = ROM.MODEL_B8 2366 selected_platform = ROM.PLATFORM_ESP32 2367 except Exception as e: 2368 print("That band does not exist, exiting now.") 2369 graceful_exit() 2370 2371 elif selected_product == ROM.PRODUCT_T32_21: 2372 selected_mcu = ROM.MCU_ESP32 2373 print("\nWhat band is this LoRa32 for?\n") 2374 print("[1] 433 MHz") 2375 print("[2] 868/915/923 MHz") 2376 print("[3] 433 MHz, with TCXO") 2377 print("[4] 868/915/923 MHz, with TCXO") 2378 print("\n? ", end="") 2379 try: 2380 c_model = int(input()) 2381 if c_model < 1 or c_model > 4: 2382 raise ValueError() 2383 elif c_model == 1: 2384 selected_model = ROM.MODEL_B4 2385 selected_platform = ROM.PLATFORM_ESP32 2386 elif c_model == 2: 2387 selected_model = ROM.MODEL_B9 2388 selected_platform = ROM.PLATFORM_ESP32 2389 elif c_model == 3: 2390 selected_model = ROM.MODEL_B4_TCXO 2391 selected_platform = ROM.PLATFORM_ESP32 2392 elif c_model == 4: 2393 selected_model = ROM.MODEL_B9_TCXO 2394 selected_platform = ROM.PLATFORM_ESP32 2395 except Exception as e: 2396 print("That band does not exist, exiting now.") 2397 graceful_exit() 2398 2399 elif selected_product == ROM.PRODUCT_H32_V2: 2400 selected_mcu = ROM.MCU_ESP32 2401 print("\nWhat band is this Heltec LoRa32 V2 for?\n") 2402 print("[1] 433 MHz") 2403 print("[2] 868 MHz") 2404 print("[3] 915 MHz") 2405 print("[4] 923 MHz") 2406 print("\n? ", end="") 2407 try: 2408 c_model = int(input()) 2409 if c_model < 1 or c_model > 4: 2410 raise ValueError() 2411 elif c_model == 1: 2412 selected_model = ROM.MODEL_C4 2413 selected_platform = ROM.PLATFORM_ESP32 2414 elif c_model > 1: 2415 selected_model = ROM.MODEL_C9 2416 selected_platform = ROM.PLATFORM_ESP32 2417 except Exception as e: 2418 print("That band does not exist, exiting now.") 2419 graceful_exit() 2420 2421 elif selected_product == ROM.PRODUCT_H32_V3: 2422 selected_mcu = ROM.MCU_ESP32 2423 print("\nWhat band is this Heltec LoRa32 V3 for?\n") 2424 print("[1] 433 MHz") 2425 print("[2] 868 MHz") 2426 print("[3] 915 MHz") 2427 print("[4] 923 MHz") 2428 print("\n? ", end="") 2429 try: 2430 c_model = int(input()) 2431 if c_model < 1 or c_model > 4: 2432 raise ValueError() 2433 elif c_model == 1: 2434 selected_model = ROM.MODEL_C5 2435 selected_platform = ROM.PLATFORM_ESP32 2436 elif c_model > 1: 2437 selected_model = ROM.MODEL_CA 2438 selected_platform = ROM.PLATFORM_ESP32 2439 except Exception as e: 2440 print("That band does not exist, exiting now.") 2441 exit() 2442 2443 elif selected_product == ROM.PRODUCT_H32_V4: 2444 selected_mcu = ROM.MCU_ESP32 2445 print("\nWhat band is this Heltec LoRa32 V4 for?\n") 2446 print("[1] 868 MHz (28 dBm output)") 2447 print("[2] 915 MHz (28 dBm output)") 2448 print("[3] 923 MHz (28 dBm output)") 2449 print("\n? ", end="") 2450 try: 2451 c_model = int(input()) 2452 if c_model < 1 or c_model > 3: 2453 raise ValueError() 2454 else: 2455 selected_model = ROM.MODEL_C8 2456 selected_platform = ROM.PLATFORM_ESP32 2457 except Exception as e: 2458 print("That band does not exist, exiting now.") 2459 exit() 2460 2461 elif selected_product == ROM.PRODUCT_HELTEC_T114: 2462 selected_mcu = ROM.MCU_NRF52 2463 print("\nWhat band is this Heltec T114 for?\n") 2464 print("[1] 433 MHz") 2465 print("[2] 868 MHz") 2466 print("[3] 915 MHz") 2467 print("[4] 923 MHz") 2468 print("\n? ", end="") 2469 try: 2470 c_model = int(input()) 2471 if c_model < 1 or c_model > 4: 2472 raise ValueError() 2473 elif c_model == 1: 2474 selected_model = ROM.MODEL_C6 2475 selected_platform = ROM.PLATFORM_NRF52 2476 elif c_model > 1: 2477 selected_model = ROM.MODEL_C7 2478 selected_platform = ROM.PLATFORM_NRF52 2479 except Exception as e: 2480 print("That band does not exist, exiting now.") 2481 exit() 2482 2483 elif selected_product == ROM.PRODUCT_XIAO_S3: 2484 selected_mcu = ROM.MCU_ESP32 2485 print("\nWhat band is this XIAO esp32s3 wio module for?\n") 2486 print("[1] 433 MHz") 2487 print("[2] 868 MHz") 2488 print("\n? ", end="") 2489 try: 2490 c_model = int(input()) 2491 if c_model < 1 or c_model > 2: 2492 raise ValueError() 2493 elif c_model == 1: 2494 selected_model = ROM.MODEL_DE 2495 selected_platform = ROM.PLATFORM_ESP32 2496 elif c_model == 2: 2497 selected_model = ROM.MODEL_DD 2498 selected_platform = ROM.PLATFORM_ESP32 2499 except Exception as e: 2500 print("That band does not exist, exiting now.") 2501 exit() 2502 2503 elif selected_product == ROM.PRODUCT_RAK4631: 2504 selected_mcu = ROM.MCU_NRF52 2505 print("\nWhat band is this RAK4631 for?\n") 2506 print("[1] 433 MHz") 2507 print("[2] 868 MHz") 2508 print("[3] 915 MHz") 2509 print("[4] 923 MHz") 2510 print("\n? ", end="") 2511 try: 2512 c_model = int(input()) 2513 if c_model < 1 or c_model > 4: 2514 raise ValueError() 2515 elif c_model == 1: 2516 selected_model = ROM.MODEL_11 2517 selected_platform = ROM.PLATFORM_NRF52 2518 elif c_model > 1: 2519 selected_model = ROM.MODEL_12 2520 selected_platform = ROM.PLATFORM_NRF52 2521 except Exception as e: 2522 print("That band does not exist, exiting now.") 2523 graceful_exit() 2524 elif selected_product == ROM.PRODUCT_TECHO: 2525 selected_mcu = ROM.MCU_NRF52 2526 print("\nWhat band is this T-Echo for?\n") 2527 print("[1] 433 MHz") 2528 print("[2] 868 MHz") 2529 print("[3] 915 MHz") 2530 print("[4] 923 MHz") 2531 print("\n? ", end="") 2532 try: 2533 c_model = int(input()) 2534 if c_model < 1 or c_model > 4: 2535 raise ValueError() 2536 elif c_model == 1: 2537 selected_model = ROM.MODEL_16 2538 selected_platform = ROM.PLATFORM_NRF52 2539 elif c_model > 1: 2540 selected_model = ROM.MODEL_17 2541 selected_platform = ROM.PLATFORM_NRF52 2542 except Exception as e: 2543 print("That band does not exist, exiting now.") 2544 graceful_exit() 2545 2546 if selected_model != ROM.MODEL_FF and selected_model != ROM.MODEL_FE: 2547 fw_filename = models[selected_model][4] 2548 2549 else: 2550 if selected_platform == ROM.PLATFORM_AVR: 2551 if selected_mcu == ROM.MCU_1284P: 2552 fw_filename = "rnode_firmware.hex" 2553 elif selected_mcu == ROM.MCU_2560: 2554 fw_filename = "rnode_firmware_m2560.hex" 2555 2556 elif selected_platform == ROM.PLATFORM_ESP32: 2557 fw_filename = None 2558 print("\nWhat kind of ESP32 board is this?\n") 2559 print("[1] Adafruit Feather ESP32 (HUZZAH32)") 2560 print("[2] Generic ESP32 board") 2561 print("\n? ", end="") 2562 try: 2563 c_eboard = int(input()) 2564 if c_eboard < 1 or c_eboard > 2: 2565 raise ValueError() 2566 elif c_eboard == 1: 2567 fw_filename = "rnode_firmware_featheresp32.zip" 2568 elif c_eboard == 2: 2569 fw_filename = "rnode_firmware_esp32_generic.zip" 2570 except Exception as e: 2571 print("That ESP32 board does not exist, exiting now.") 2572 graceful_exit() 2573 2574 if fw_filename == None: 2575 print("") 2576 print("Sorry, no firmware for your board currently exists.") 2577 print("Help making it a reality by contributing code or by") 2578 print("donating to the project.") 2579 print("") 2580 print_donation_block() 2581 print("") 2582 graceful_exit() 2583 2584 if args.use_extracted: 2585 fw_filename = "extracted_rnode_firmware.zip" 2586 2587 clear() 2588 print("") 2589 print("------------------------------------------------------------------------------") 2590 print(" Installer Ready") 2591 print("") 2592 print("Ok, that should be all the information we need. Please confirm the following") 2593 print("summary before proceeding. In the next step, the device will be flashed and") 2594 print("provisioned, so make sure that you are satisfied with your choices.\n") 2595 2596 print("Serial port : "+str(selected_port.device)) 2597 print("Device type : "+str(products[selected_product])+" "+str(models[selected_model][3])) 2598 print("Platform : "+str(platforms[selected_platform])) 2599 print("Device MCU : "+str(mcus[selected_mcu])) 2600 print("Firmware file : "+str(fw_filename)) 2601 2602 print("") 2603 print("------------------------------------------------------------------------------") 2604 2605 print("\nIs the above correct? [y/N] ", end="") 2606 try: 2607 c_ok = input().lower() 2608 if c_ok != "y": 2609 raise ValueError() 2610 except Exception as e: 2611 print("OK, aborting now.") 2612 graceful_exit() 2613 2614 args.key = True 2615 args.port = selected_port.device 2616 args.platform = selected_platform 2617 args.hwrev = 1 2618 mapped_model = selected_model 2619 mapped_product = selected_product 2620 args.update = False 2621 args.flash = True 2622 2623 try: 2624 RNS.log("Checking firmware file availability...") 2625 ensure_firmware_file(fw_filename) 2626 except Exception as e: 2627 RNS.log("Could not obain firmware package for your board") 2628 RNS.log("The contained exception was: "+str(e)) 2629 graceful_exit() 2630 2631 rnode.disconnect() 2632 2633 if args.public: 2634 private_bytes = None 2635 try: 2636 file = open(FWD_DIR+"/signing.key", "rb") 2637 private_bytes = file.read() 2638 file.close() 2639 except Exception as e: 2640 RNS.log("Could not load EEPROM signing key") 2641 2642 try: 2643 private_key = serialization.load_der_private_key( 2644 private_bytes, 2645 password=None, 2646 backend=default_backend() 2647 ) 2648 public_key = private_key.public_key() 2649 public_bytes = public_key.public_bytes( 2650 encoding=serialization.Encoding.DER, 2651 format=serialization.PublicFormat.SubjectPublicKeyInfo 2652 ) 2653 RNS.log("EEPROM Signing Public key:") 2654 RNS.log(RNS.hexrep(public_bytes, delimit=False)) 2655 2656 except Exception as e: 2657 RNS.log("Could not deserialize signing key") 2658 RNS.log(str(e)) 2659 2660 try: 2661 device_signer = RNS.Identity.from_file(FWD_DIR+"/device.key") 2662 RNS.log("") 2663 RNS.log("Device Signing Public key:") 2664 RNS.log(RNS.hexrep(device_signer.get_public_key()[32:], delimit=True)) 2665 2666 except Exception as e: 2667 RNS.log("Could not load device signing key") 2668 2669 2670 graceful_exit() 2671 2672 if args.key: 2673 if not os.path.isfile(FWD_DIR+"/device.key"): 2674 try: 2675 RNS.log("Generating a new device signing key...") 2676 device_signer = RNS.Identity() 2677 device_signer.to_file(FWD_DIR+"/device.key") 2678 RNS.log("Device signing key written to "+str(FWD_DIR+"/device.key")) 2679 except Exception as e: 2680 RNS.log("Could not create new device signing key at "+str(FWD_DIR+"/device.key")+". The contained exception was:") 2681 RNS.log(str(e)) 2682 RNS.log("Please ensure filesystem access and try again.") 2683 graceful_exit(81) 2684 else: 2685 try: 2686 device_signer = RNS.Identity.from_file(FWD_DIR+"/device.key") 2687 except Exception as e: 2688 RNS.log("Could not load device signing key from "+str(FWD_DIR+"/device.key")+". The contained exception was:") 2689 RNS.log(str(e)) 2690 RNS.log("Please restore or clear the key and try again.") 2691 graceful_exit(82) 2692 2693 if not os.path.isfile(FWD_DIR+"/signing.key"): 2694 RNS.log("Generating a new EEPROM signing key...") 2695 private_key = rsa.generate_private_key( 2696 public_exponent=65537, 2697 key_size=1024, 2698 backend=default_backend() 2699 ) 2700 private_bytes = private_key.private_bytes( 2701 encoding=serialization.Encoding.DER, 2702 format=serialization.PrivateFormat.PKCS8, 2703 encryption_algorithm=serialization.NoEncryption() 2704 ) 2705 public_key = private_key.public_key() 2706 public_bytes = public_key.public_bytes( 2707 encoding=serialization.Encoding.DER, 2708 format=serialization.PublicFormat.SubjectPublicKeyInfo 2709 ) 2710 os.makedirs(FWD_DIR, exist_ok=True) 2711 if os.path.isdir(FWD_DIR): 2712 if os.path.isfile(FWD_DIR+"/signing.key"): 2713 if not args.autoinstall: 2714 RNS.log("EEPROM Signing key already exists, not overwriting!") 2715 RNS.log("Manually delete this key to create a new one.") 2716 else: 2717 file = open(FWD_DIR+"/signing.key", "wb") 2718 file.write(private_bytes) 2719 file.close() 2720 2721 if not squashvw: 2722 RNS.log("Wrote signing key") 2723 RNS.log("Public key:") 2724 RNS.log(RNS.hexrep(public_bytes, delimit=False)) 2725 else: 2726 RNS.log("The firmware directory does not exist, can't write key!") 2727 2728 if not args.autoinstall: 2729 graceful_exit() 2730 2731 def get_partition_hash(platform, partition_file): 2732 try: 2733 if platform == ROM.PLATFORM_ESP32 or platform == ROM.PLATFORM_AVR: 2734 firmware_data = open(partition_file, "rb").read() 2735 # Calculate the digest manually and see if it matches the 2736 # SHA256 digest included in the ESP32 image. 2737 calc_hash = hashlib.sha256(firmware_data[0:-32]).digest() 2738 part_hash = firmware_data[-32:] 2739 2740 if calc_hash == part_hash: 2741 return part_hash 2742 else: 2743 return None 2744 2745 elif platform == ROM.PLATFORM_NRF52: 2746 # Calculate digest manually, as it is not included in the image. 2747 firmware_data = open(partition_file, "rb") 2748 hash = hashlib.file_digest(firmware_data, 'sha256').digest() 2749 firmware_data.close() 2750 return hash 2751 except Exception as e: 2752 RNS.log("Could not calculate firmware partition hash. The contained exception was:") 2753 RNS.log(str(e)) 2754 2755 def get_flasher_call(platform, fw_filename): 2756 global selected_version 2757 from shutil import which 2758 if platform == "unzip": 2759 flasher = "unzip" 2760 if which(flasher) is not None: 2761 return [flasher, "-o", UPD_DIR+"/"+selected_version+"/"+fw_filename, "-d", UPD_DIR+"/"+selected_version] 2762 else: 2763 RNS.log("") 2764 RNS.log("You do not currently have the \""+flasher+"\" program installed on your system.") 2765 RNS.log("Unfortunately, that means we can't proceed, since it is needed to flash your") 2766 RNS.log("board. You can install it via your package manager, for example:") 2767 RNS.log("") 2768 RNS.log(" sudo apt install "+flasher) 2769 RNS.log("") 2770 RNS.log("Please install \""+flasher+"\" and try again.") 2771 graceful_exit() 2772 elif platform == ROM.PLATFORM_AVR: 2773 flasher = "avrdude" 2774 if which(flasher) is not None: 2775 # 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 2776 # avrdude -q -q -V -patmega2560 -cwiring -P/dev/ttyACM0 -b115200 -D -Uflash:w:/tmp/arduino-sketch-0E260F46C421A84A7CBAD48E859C8E64/RNode_Firmware.ino.hex:i 2777 if fw_filename == "rnode_firmware.hex": 2778 return [flasher, "-P", args.port, "-p", "m1284p", "-c", "arduino", "-b", "115200", "-U", "flash:w:"+UPD_DIR+"/"+selected_version+"/"+fw_filename+":i"] 2779 elif fw_filename == "rnode_firmware_m2560.hex": 2780 return [flasher, "-P", args.port, "-p", "atmega2560", "-c", "wiring", "-D", "-b", "115200", "-U", "flash:w:"+UPD_DIR+"/"+selected_version+"/"+fw_filename] 2781 else: 2782 RNS.log("") 2783 RNS.log("You do not currently have the \""+flasher+"\" program installed on your system.") 2784 RNS.log("Unfortunately, that means we can't proceed, since it is needed to flash your") 2785 RNS.log("board. You can install it via your package manager, for example:") 2786 RNS.log("") 2787 RNS.log(" sudo apt install avrdude") 2788 RNS.log("") 2789 RNS.log("Please install \""+flasher+"\" and try again.") 2790 graceful_exit() 2791 elif platform == ROM.PLATFORM_ESP32: 2792 numeric_version = float(selected_version) 2793 flasher_dir = UPD_DIR+"/"+selected_version 2794 flasher = flasher_dir+"/esptool.py" 2795 if not os.path.isfile(flasher): 2796 if os.path.isfile(CNF_DIR+"/recovery_esptool.py"): 2797 import shutil 2798 if not os.path.isdir(flasher_dir): 2799 os.makedirs(flasher_dir) 2800 shutil.copy(CNF_DIR+"/recovery_esptool.py", flasher) 2801 RNS.log("No flasher present, using recovery flasher to write firmware to device") 2802 2803 if os.path.isfile(flasher): 2804 import stat 2805 os.chmod(flasher, stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP) 2806 2807 if which(flasher) is not None: 2808 if fw_filename == "rnode_firmware_tbeam.zip": 2809 if numeric_version >= 1.55: 2810 return [ 2811 sys.executable, flasher, 2812 "--chip", "esp32", 2813 "--port", args.port, 2814 "--baud", args.baud_flash, 2815 "--before", "default_reset", 2816 "--after", "hard_reset", 2817 "write_flash", "-z", 2818 "--flash_mode", "dio", 2819 "--flash_freq", "80m", 2820 "--flash_size", "4MB", 2821 "0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_tbeam.boot_app0", 2822 "0x1000", UPD_DIR+"/"+selected_version+"/rnode_firmware_tbeam.bootloader", 2823 "0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_tbeam.bin", 2824 "0x210000",UPD_DIR+"/"+selected_version+"/console_image.bin", 2825 "0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_tbeam.partitions", 2826 ] 2827 else: 2828 return [ 2829 sys.executable, flasher, 2830 "--chip", "esp32", 2831 "--port", args.port, 2832 "--baud", args.baud_flash, 2833 "--before", "default_reset", 2834 "--after", "hard_reset", 2835 "write_flash", "-z", 2836 "--flash_mode", "dio", 2837 "--flash_freq", "80m", 2838 "--flash_size", "4MB", 2839 "0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_tbeam.boot_app0", 2840 "0x1000", UPD_DIR+"/"+selected_version+"/rnode_firmware_tbeam.bootloader", 2841 "0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_tbeam.bin", 2842 "0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_tbeam.partitions", 2843 ] 2844 elif fw_filename == "rnode_firmware_tbeam_sx1262.zip": 2845 if numeric_version >= 1.55: 2846 return [ 2847 sys.executable, flasher, 2848 "--chip", "esp32", 2849 "--port", args.port, 2850 "--baud", args.baud_flash, 2851 "--before", "default_reset", 2852 "--after", "hard_reset", 2853 "write_flash", "-z", 2854 "--flash_mode", "dio", 2855 "--flash_freq", "80m", 2856 "--flash_size", "4MB", 2857 "0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_tbeam_sx1262.boot_app0", 2858 "0x1000", UPD_DIR+"/"+selected_version+"/rnode_firmware_tbeam_sx1262.bootloader", 2859 "0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_tbeam_sx1262.bin", 2860 "0x210000",UPD_DIR+"/"+selected_version+"/console_image.bin", 2861 "0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_tbeam_sx1262.partitions", 2862 ] 2863 else: 2864 return [ 2865 sys.executable, flasher, 2866 "--chip", "esp32", 2867 "--port", args.port, 2868 "--baud", args.baud_flash, 2869 "--before", "default_reset", 2870 "--after", "hard_reset", 2871 "write_flash", "-z", 2872 "--flash_mode", "dio", 2873 "--flash_freq", "80m", 2874 "--flash_size", "4MB", 2875 "0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_tbeam.boot_app0", 2876 "0x1000", UPD_DIR+"/"+selected_version+"/rnode_firmware_tbeam.bootloader", 2877 "0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_tbeam.bin", 2878 "0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_tbeam.partitions", 2879 ] 2880 elif fw_filename == "rnode_firmware_lora32v10.zip": 2881 if numeric_version >= 1.59: 2882 return [ 2883 sys.executable, flasher, 2884 "--chip", "esp32", 2885 "--port", args.port, 2886 "--baud", args.baud_flash, 2887 "--before", "default_reset", 2888 "--after", "hard_reset", 2889 "write_flash", "-z", 2890 "--flash_mode", "dio", 2891 "--flash_freq", "80m", 2892 "--flash_size", "4MB", 2893 "0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v10.boot_app0", 2894 "0x1000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v10.bootloader", 2895 "0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v10.bin", 2896 "0x210000",UPD_DIR+"/"+selected_version+"/console_image.bin", 2897 "0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v10.partitions", 2898 ] 2899 else: 2900 return [ 2901 sys.executable, flasher, 2902 "--chip", "esp32", 2903 "--port", args.port, 2904 "--baud", args.baud_flash, 2905 "--before", "default_reset", 2906 "--after", "hard_reset", 2907 "write_flash", "-z", 2908 "--flash_mode", "dio", 2909 "--flash_freq", "80m", 2910 "--flash_size", "4MB", 2911 "0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v20.boot_app0", 2912 "0x1000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v20.bootloader", 2913 "0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v20.bin", 2914 "0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v20.partitions", 2915 ] 2916 elif fw_filename == "rnode_firmware_lora32v20.zip": 2917 if numeric_version >= 1.55: 2918 return [ 2919 sys.executable, flasher, 2920 "--chip", "esp32", 2921 "--port", args.port, 2922 "--baud", args.baud_flash, 2923 "--before", "default_reset", 2924 "--after", "hard_reset", 2925 "write_flash", "-z", 2926 "--flash_mode", "dio", 2927 "--flash_freq", "80m", 2928 "--flash_size", "4MB", 2929 "0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v20.boot_app0", 2930 "0x1000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v20.bootloader", 2931 "0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v20.bin", 2932 "0x210000",UPD_DIR+"/"+selected_version+"/console_image.bin", 2933 "0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v20.partitions", 2934 ] 2935 else: 2936 return [ 2937 sys.executable, flasher, 2938 "--chip", "esp32", 2939 "--port", args.port, 2940 "--baud", args.baud_flash, 2941 "--before", "default_reset", 2942 "--after", "hard_reset", 2943 "write_flash", "-z", 2944 "--flash_mode", "dio", 2945 "--flash_freq", "80m", 2946 "--flash_size", "4MB", 2947 "0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v20.boot_app0", 2948 "0x1000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v20.bootloader", 2949 "0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v20.bin", 2950 "0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v20.partitions", 2951 ] 2952 elif fw_filename == "rnode_firmware_lora32v21.zip": 2953 if numeric_version >= 1.55: 2954 return [ 2955 sys.executable, flasher, 2956 "--chip", "esp32", 2957 "--port", args.port, 2958 "--baud", args.baud_flash, 2959 "--before", "default_reset", 2960 "--after", "hard_reset", 2961 "write_flash", "-z", 2962 "--flash_mode", "dio", 2963 "--flash_freq", "80m", 2964 "--flash_size", "4MB", 2965 "0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v21.boot_app0", 2966 "0x1000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v21.bootloader", 2967 "0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v21.bin", 2968 "0x210000",UPD_DIR+"/"+selected_version+"/console_image.bin", 2969 "0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v21.partitions", 2970 ] 2971 else: 2972 return [ 2973 sys.executable, flasher, 2974 "--chip", "esp32", 2975 "--port", args.port, 2976 "--baud", args.baud_flash, 2977 "--before", "default_reset", 2978 "--after", "hard_reset", 2979 "write_flash", "-z", 2980 "--flash_mode", "dio", 2981 "--flash_freq", "80m", 2982 "--flash_size", "4MB", 2983 "0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v21.boot_app0", 2984 "0x1000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v21.bootloader", 2985 "0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v21.bin", 2986 "0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v21.partitions", 2987 ] 2988 elif fw_filename == "rnode_firmware_lora32v21_tcxo.zip": 2989 return [ 2990 sys.executable, flasher, 2991 "--chip", "esp32", 2992 "--port", args.port, 2993 "--baud", args.baud_flash, 2994 "--before", "default_reset", 2995 "--after", "hard_reset", 2996 "write_flash", "-z", 2997 "--flash_mode", "dio", 2998 "--flash_freq", "80m", 2999 "--flash_size", "4MB", 3000 "0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v21_tcxo.boot_app0", 3001 "0x1000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v21_tcxo.bootloader", 3002 "0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v21_tcxo.bin", 3003 "0x210000",UPD_DIR+"/"+selected_version+"/console_image.bin", 3004 "0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v21_tcxo.partitions", 3005 ] 3006 elif fw_filename == "rnode_firmware_heltec32v2.zip": 3007 if numeric_version >= 1.55: 3008 return [ 3009 sys.executable, flasher, 3010 "--chip", "esp32", 3011 "--port", args.port, 3012 "--baud", args.baud_flash, 3013 "--before", "default_reset", 3014 "--after", "hard_reset", 3015 "write_flash", "-z", 3016 "--flash_mode", "dio", 3017 "--flash_freq", "80m", 3018 "--flash_size", "8MB", 3019 "0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_heltec32v2.boot_app0", 3020 "0x1000", UPD_DIR+"/"+selected_version+"/rnode_firmware_heltec32v2.bootloader", 3021 "0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_heltec32v2.bin", 3022 "0x210000",UPD_DIR+"/"+selected_version+"/console_image.bin", 3023 "0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_heltec32v2.partitions", 3024 ] 3025 else: 3026 return [ 3027 sys.executable, flasher, 3028 "--chip", "esp32", 3029 "--port", args.port, 3030 "--baud", args.baud_flash, 3031 "--before", "default_reset", 3032 "--after", "hard_reset", 3033 "write_flash", "-z", 3034 "--flash_mode", "dio", 3035 "--flash_freq", "80m", 3036 "--flash_size", "8MB", 3037 "0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_heltec32v2.boot_app0", 3038 "0x1000", UPD_DIR+"/"+selected_version+"/rnode_firmware_heltec32v2.bootloader", 3039 "0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_heltec32v2.bin", 3040 "0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_heltec32v2.partitions", 3041 ] 3042 elif fw_filename == "rnode_firmware_heltec32v3.zip": 3043 return [ 3044 sys.executable, flasher, 3045 "--chip", "esp32-s3", 3046 "--port", args.port, 3047 "--baud", args.baud_flash, 3048 "--before", "default_reset", 3049 "--after", "hard_reset", 3050 "write_flash", "-z", 3051 "--flash_mode", "dio", 3052 "--flash_freq", "80m", 3053 "--flash_size", "8MB", 3054 "0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_heltec32v3.boot_app0", 3055 "0x0", UPD_DIR+"/"+selected_version+"/rnode_firmware_heltec32v3.bootloader", 3056 "0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_heltec32v3.bin", 3057 "0x210000",UPD_DIR+"/"+selected_version+"/console_image.bin", 3058 "0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_heltec32v3.partitions", 3059 ] 3060 elif fw_filename == "rnode_firmware_heltec32v4pa.zip": 3061 return [ 3062 sys.executable, flasher, 3063 "--chip", "esp32-s3", 3064 "--port", args.port, 3065 "--baud", args.baud_flash, 3066 "--before", "default_reset", 3067 "--after", "hard_reset", 3068 "write_flash", "-z", 3069 "--flash_mode", "dio", 3070 "--flash_freq", "80m", 3071 "--flash_size", "16MB", 3072 "0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_heltec32v4pa.boot_app0", 3073 "0x0", UPD_DIR+"/"+selected_version+"/rnode_firmware_heltec32v4pa.bootloader", 3074 "0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_heltec32v4pa.bin", 3075 "0x210000",UPD_DIR+"/"+selected_version+"/console_image.bin", 3076 "0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_heltec32v4pa.partitions", 3077 ] 3078 elif fw_filename == "rnode_firmware_featheresp32.zip": 3079 if numeric_version >= 1.55: 3080 return [ 3081 sys.executable, flasher, 3082 "--chip", "esp32", 3083 "--port", args.port, 3084 "--baud", args.baud_flash, 3085 "--before", "default_reset", 3086 "--after", "hard_reset", 3087 "write_flash", "-z", 3088 "--flash_mode", "dio", 3089 "--flash_freq", "80m", 3090 "--flash_size", "4MB", 3091 "0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_featheresp32.boot_app0", 3092 "0x1000", UPD_DIR+"/"+selected_version+"/rnode_firmware_featheresp32.bootloader", 3093 "0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_featheresp32.bin", 3094 "0x210000",UPD_DIR+"/"+selected_version+"/console_image.bin", 3095 "0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_featheresp32.partitions", 3096 ] 3097 else: 3098 return [ 3099 sys.executable, flasher, 3100 "--chip", "esp32", 3101 "--port", args.port, 3102 "--baud", args.baud_flash, 3103 "--before", "default_reset", 3104 "--after", "hard_reset", 3105 "write_flash", "-z", 3106 "--flash_mode", "dio", 3107 "--flash_freq", "80m", 3108 "--flash_size", "4MB", 3109 "0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_featheresp32.boot_app0", 3110 "0x1000", UPD_DIR+"/"+selected_version+"/rnode_firmware_featheresp32.bootloader", 3111 "0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_featheresp32.bin", 3112 "0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_featheresp32.partitions", 3113 ] 3114 elif fw_filename == "rnode_firmware_esp32_generic.zip": 3115 if numeric_version >= 1.55: 3116 return [ 3117 sys.executable, flasher, 3118 "--chip", "esp32", 3119 "--port", args.port, 3120 "--baud", args.baud_flash, 3121 "--before", "default_reset", 3122 "--after", "hard_reset", 3123 "write_flash", "-z", 3124 "--flash_mode", "dio", 3125 "--flash_freq", "80m", 3126 "--flash_size", "4MB", 3127 "0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_esp32_generic.boot_app0", 3128 "0x1000", UPD_DIR+"/"+selected_version+"/rnode_firmware_esp32_generic.bootloader", 3129 "0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_esp32_generic.bin", 3130 "0x210000",UPD_DIR+"/"+selected_version+"/console_image.bin", 3131 "0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_esp32_generic.partitions", 3132 ] 3133 else: 3134 return [ 3135 sys.executable, flasher, 3136 "--chip", "esp32", 3137 "--port", args.port, 3138 "--baud", args.baud_flash, 3139 "--before", "default_reset", 3140 "--after", "hard_reset", 3141 "write_flash", "-z", 3142 "--flash_mode", "dio", 3143 "--flash_freq", "80m", 3144 "--flash_size", "4MB", 3145 "0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_esp32_generic.boot_app0", 3146 "0x1000", UPD_DIR+"/"+selected_version+"/rnode_firmware_esp32_generic.bootloader", 3147 "0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_esp32_generic.bin", 3148 "0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_esp32_generic.partitions", 3149 ] 3150 elif fw_filename == "rnode_firmware_ng20.zip": 3151 if numeric_version >= 1.55: 3152 return [ 3153 sys.executable, flasher, 3154 "--chip", "esp32", 3155 "--port", args.port, 3156 "--baud", args.baud_flash, 3157 "--before", "default_reset", 3158 "--after", "hard_reset", 3159 "write_flash", "-z", 3160 "--flash_mode", "dio", 3161 "--flash_freq", "80m", 3162 "--flash_size", "4MB", 3163 "0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_ng20.boot_app0", 3164 "0x1000", UPD_DIR+"/"+selected_version+"/rnode_firmware_ng20.bootloader", 3165 "0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_ng20.bin", 3166 "0x210000",UPD_DIR+"/"+selected_version+"/console_image.bin", 3167 "0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_ng20.partitions", 3168 ] 3169 else: 3170 return [ 3171 sys.executable, flasher, 3172 "--chip", "esp32", 3173 "--port", args.port, 3174 "--baud", args.baud_flash, 3175 "--before", "default_reset", 3176 "--after", "hard_reset", 3177 "write_flash", "-z", 3178 "--flash_mode", "dio", 3179 "--flash_freq", "80m", 3180 "--flash_size", "4MB", 3181 "0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_ng20.boot_app0", 3182 "0x1000", UPD_DIR+"/"+selected_version+"/rnode_firmware_ng20.bootloader", 3183 "0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_ng20.bin", 3184 "0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_ng20.partitions", 3185 ] 3186 elif fw_filename == "rnode_firmware_ng21.zip": 3187 if numeric_version >= 1.55: 3188 return [ 3189 sys.executable, flasher, 3190 "--chip", "esp32", 3191 "--port", args.port, 3192 "--baud", args.baud_flash, 3193 "--before", "default_reset", 3194 "--after", "hard_reset", 3195 "write_flash", "-z", 3196 "--flash_mode", "dio", 3197 "--flash_freq", "80m", 3198 "--flash_size", "4MB", 3199 "0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_ng21.boot_app0", 3200 "0x1000", UPD_DIR+"/"+selected_version+"/rnode_firmware_ng21.bootloader", 3201 "0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_ng21.bin", 3202 "0x210000",UPD_DIR+"/"+selected_version+"/console_image.bin", 3203 "0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_ng21.partitions", 3204 ] 3205 else: 3206 return [ 3207 sys.executable, flasher, 3208 "--chip", "esp32", 3209 "--port", args.port, 3210 "--baud", args.baud_flash, 3211 "--before", "default_reset", 3212 "--after", "hard_reset", 3213 "write_flash", "-z", 3214 "--flash_mode", "dio", 3215 "--flash_freq", "80m", 3216 "--flash_size", "4MB", 3217 "0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_ng21.boot_app0", 3218 "0x1000", UPD_DIR+"/"+selected_version+"/rnode_firmware_ng21.bootloader", 3219 "0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_ng21.bin", 3220 "0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_ng21.partitions", 3221 ] 3222 elif fw_filename == "rnode_firmware_t3s3.zip": 3223 return [ 3224 sys.executable, flasher, 3225 "--chip", "esp32s3", 3226 "--port", args.port, 3227 "--baud", args.baud_flash, 3228 "--before", "default_reset", 3229 "--after", "hard_reset", 3230 "write_flash", "-z", 3231 "--flash_mode", "dio", 3232 "--flash_freq", "80m", 3233 "--flash_size", "4MB", 3234 "0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_t3s3.boot_app0", 3235 "0x0", UPD_DIR+"/"+selected_version+"/rnode_firmware_t3s3.bootloader", 3236 "0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_t3s3.bin", 3237 "0x210000",UPD_DIR+"/"+selected_version+"/console_image.bin", 3238 "0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_t3s3.partitions", 3239 ] 3240 elif fw_filename == "rnode_firmware_t3s3_sx127x.zip": 3241 return [ 3242 sys.executable, flasher, 3243 "--chip", "esp32s3", 3244 "--port", args.port, 3245 "--baud", args.baud_flash, 3246 "--before", "default_reset", 3247 "--after", "hard_reset", 3248 "write_flash", "-z", 3249 "--flash_mode", "dio", 3250 "--flash_freq", "80m", 3251 "--flash_size", "4MB", 3252 "0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_t3s3_sx127x.boot_app0", 3253 "0x0", UPD_DIR+"/"+selected_version+"/rnode_firmware_t3s3_sx127x.bootloader", 3254 "0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_t3s3_sx127x.bin", 3255 "0x210000",UPD_DIR+"/"+selected_version+"/console_image.bin", 3256 "0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_t3s3_sx127x.partitions", 3257 ] 3258 elif fw_filename == "rnode_firmware_t3s3_sx1280_pa.zip": 3259 return [ 3260 sys.executable, flasher, 3261 "--chip", "esp32s3", 3262 "--port", args.port, 3263 "--baud", args.baud_flash, 3264 "--before", "default_reset", 3265 "--after", "hard_reset", 3266 "write_flash", "-z", 3267 "--flash_mode", "dio", 3268 "--flash_freq", "80m", 3269 "--flash_size", "4MB", 3270 "0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_t3s3_sx1280_pa.boot_app0", 3271 "0x0", UPD_DIR+"/"+selected_version+"/rnode_firmware_t3s3_sx1280_pa.bootloader", 3272 "0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_t3s3_sx1280_pa.bin", 3273 "0x210000",UPD_DIR+"/"+selected_version+"/console_image.bin", 3274 "0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_t3s3_sx1280_pa.partitions", 3275 ] 3276 elif fw_filename == "rnode_firmware_tbeam_supreme.zip": 3277 return [ 3278 sys.executable, flasher, 3279 "--chip", "esp32s3", 3280 "--port", args.port, 3281 "--baud", args.baud_flash, 3282 "--before", "default_reset", 3283 "--after", "hard_reset", 3284 "write_flash", "-z", 3285 "--flash_mode", "dio", 3286 "--flash_freq", "80m", 3287 "--flash_size", "4MB", 3288 "0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_tbeam_supreme.boot_app0", 3289 "0x0", UPD_DIR+"/"+selected_version+"/rnode_firmware_tbeam_supreme.bootloader", 3290 "0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_tbeam_supreme.bin", 3291 "0x210000",UPD_DIR+"/"+selected_version+"/console_image.bin", 3292 "0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_tbeam_supreme.partitions", 3293 ] 3294 elif fw_filename == "rnode_firmware_tdeck.zip": 3295 return [ 3296 sys.executable, flasher, 3297 "--chip", "esp32s3", 3298 "--port", args.port, 3299 "--baud", args.baud_flash, 3300 "--before", "default_reset", 3301 "--after", "hard_reset", 3302 "write_flash", "-z", 3303 "--flash_mode", "dio", 3304 "--flash_freq", "80m", 3305 "--flash_size", "4MB", 3306 "0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_tdeck.boot_app0", 3307 "0x0", UPD_DIR+"/"+selected_version+"/rnode_firmware_tdeck.bootloader", 3308 "0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_tdeck.bin", 3309 "0x210000",UPD_DIR+"/"+selected_version+"/console_image.bin", 3310 "0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_tdeck.partitions", 3311 ] 3312 elif fw_filename == "rnode_firmware_xiao_esp32s3.zip": 3313 return [ 3314 sys.executable, flasher, 3315 "--chip", "esp32s3", 3316 "--port", args.port, 3317 "--baud", args.baud_flash, 3318 "--before", "default_reset", 3319 "--after", "hard_reset", 3320 "write_flash", "-z", 3321 "--flash_mode", "dio", 3322 "--flash_freq", "80m", 3323 "--flash_size", "8MB", 3324 "0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_xiao_esp32s3.boot_app0", 3325 "0x0", UPD_DIR+"/"+selected_version+"/rnode_firmware_xiao_esp32s3.bootloader", 3326 "0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_xiao_esp32s3.bin", 3327 "0x210000",UPD_DIR+"/"+selected_version+"/console_image.bin", 3328 "0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_xiao_esp32s3.partitions", 3329 ] 3330 elif fw_filename == "extracted_rnode_firmware.zip": 3331 return [ 3332 sys.executable, flasher, 3333 "--chip", "esp32", 3334 "--port", args.port, 3335 "--baud", args.baud_flash, 3336 "--before", "default_reset", 3337 "--after", "hard_reset", 3338 "write_flash", "-z", 3339 "--flash_mode", "dio", 3340 "--flash_freq", "80m", 3341 "--flash_size", "4MB", 3342 "0x1000", EXT_DIR+"/extracted_rnode_firmware.bootloader", 3343 "0xe000", EXT_DIR+"/extracted_rnode_firmware.boot_app0", 3344 "0x8000", EXT_DIR+"/extracted_rnode_firmware.partitions", 3345 "0x10000", EXT_DIR+"/extracted_rnode_firmware.bin", 3346 "0x210000",EXT_DIR+"/extracted_console_image.bin", 3347 ] 3348 else: 3349 RNS.log("No flasher available for this board, cannot install firmware.") 3350 else: 3351 RNS.log("") 3352 RNS.log("You do not currently have the \""+flasher+"\" program installed on your system.") 3353 RNS.log("Unfortunately, that means we can't proceed, since it is needed to flash your") 3354 RNS.log("board. You can install it via your package manager, for example:") 3355 RNS.log("") 3356 RNS.log(" sudo apt install esptool") 3357 RNS.log("") 3358 RNS.log("Please install \""+flasher+"\" and try again.") 3359 graceful_exit() 3360 3361 elif platform == ROM.PLATFORM_NRF52: 3362 flasher = "adafruit-nrfutil" 3363 if which(flasher) is not None: 3364 return [flasher, "dfu", "serial", "--package", UPD_DIR+"/"+selected_version+"/"+fw_filename, "-p", args.port, "-b", "115200", "-t", "1200"] 3365 else: 3366 RNS.log("") 3367 RNS.log("You do not currently have the \""+flasher+"\" program installed on your system.") 3368 RNS.log("Unfortunately, that means we can't proceed, since it is needed to flash your") 3369 RNS.log("board. You can install it via your package manager, for example:") 3370 RNS.log("") 3371 RNS.log(" pip3 install --user adafruit-nrfutil") 3372 RNS.log("") 3373 RNS.log("Please install \""+flasher+"\" and try again.") 3374 graceful_exit() 3375 3376 if args.port: 3377 wants_fw_provision = False 3378 if args.flash: 3379 from subprocess import call 3380 3381 if fw_filename == None: 3382 fw_filename = "rnode_firmware.hex" 3383 3384 if args.platform == None: 3385 args.platform = ROM.PLATFORM_AVR 3386 3387 if selected_version == None: 3388 RNS.log("Missing parameters, cannot continue") 3389 graceful_exit(68) 3390 3391 if selected_model in ROM.MANUAL_FLASH_MODELS: 3392 RNS.log("") 3393 RNS.log("Please put the board into flashing mode now, by holding the BOOT or PRG button,") 3394 RNS.log("while momentarily pressing the RESET button. Then release the BOOT or PRG button.") 3395 RNS.log("Hit enter when this is done.") 3396 input() 3397 3398 if fw_filename == "extracted_rnode_firmware.zip": 3399 try: 3400 RNS.log("Flashing RNode firmware to device on "+args.port) 3401 from subprocess import call 3402 rc = get_flasher_call(args.platform, fw_filename) 3403 flash_status = call(rc) 3404 if flash_status == 0: 3405 RNS.log("Done flashing") 3406 args.rom = True 3407 if args.platform == ROM.PLATFORM_ESP32: 3408 wants_fw_provision = True 3409 RNS.log("Waiting for ESP32 reset...") 3410 time.sleep(7) 3411 else: 3412 graceful_exit() 3413 3414 except Exception as e: 3415 RNS.log("Error while flashing") 3416 RNS.log(str(e)) 3417 graceful_exit(1) 3418 3419 else: 3420 fw_src = UPD_DIR+"/"+selected_version+"/" 3421 if os.path.isfile(fw_src+fw_filename): 3422 try: 3423 if fw_filename.endswith(".zip"): 3424 RNS.log("Decompressing firmware...") 3425 try: 3426 with zipfile.ZipFile(fw_src+fw_filename) as zip: 3427 zip.extractall(fw_src) 3428 except Exception as e: 3429 RNS.log("Could not decompress firmware from downloaded zip file") 3430 graceful_exit() 3431 RNS.log("Firmware decompressed") 3432 3433 RNS.log("Flashing RNode firmware to device on "+args.port) 3434 from subprocess import call 3435 rc = get_flasher_call(args.platform, fw_filename) 3436 flash_status = call(rc) 3437 if flash_status == 0: 3438 RNS.log("Done flashing") 3439 args.rom = True 3440 if args.platform == ROM.PLATFORM_ESP32: 3441 wants_fw_provision = True 3442 RNS.log("Waiting for ESP32 reset...") 3443 time.sleep(8) 3444 if args.platform == ROM.PLATFORM_NRF52: 3445 wants_fw_provision = True 3446 RNS.log("Waiting for NRF52 reset...") 3447 # Don't need to wait as long this time. 3448 time.sleep(6) 3449 else: 3450 RNS.log("Error from flasher ("+str(flash_status)+") while writing.") 3451 RNS.log("Some boards have trouble flashing at high speeds, and you can") 3452 RNS.log("try flashing with a lower baud rate, as in this example:") 3453 RNS.log("rnodeconf --autoinstall --baud-flash 115200") 3454 graceful_exit() 3455 3456 except Exception as e: 3457 RNS.log("Error while flashing") 3458 RNS.log(str(e)) 3459 graceful_exit(1) 3460 else: 3461 RNS.log("Firmware file not found") 3462 graceful_exit() 3463 3464 if selected_model in ROM.MANUAL_FLASH_MODELS: 3465 RNS.log("") 3466 RNS.log("Please take the board out of flashing mode by momentarily pressing the RESET button.") 3467 RNS.log("Hit enter when this is done.") 3468 input() 3469 sleep(2.5) 3470 3471 RNS.log("Opening serial port "+args.port+"...") 3472 try: 3473 rnode_port = args.port 3474 rnode_serial = rnode_open_serial(rnode_port) 3475 except Exception as e: 3476 RNS.log("Could not open the specified serial port. The contained exception was:") 3477 RNS.log(str(e)) 3478 graceful_exit() 3479 3480 rnode = RNode(rnode_serial) 3481 ports = list_ports.comports() 3482 for port in ports: 3483 if port.device == args.port: 3484 rnode.usb_serial_id = port.serial_number 3485 break 3486 thread = threading.Thread(target=rnode.readLoop, daemon=True).start() 3487 3488 try: 3489 rnode.device_probe() 3490 except Exception as e: 3491 RNS.log("Serial port opened, but RNode did not respond. Is a valid firmware installed?") 3492 print(e) 3493 graceful_exit() 3494 3495 if rnode.detected: 3496 if rnode.platform == None or rnode.mcu == None: 3497 rnode.platform = ROM.PLATFORM_AVR 3498 rnode.mcu = ROM.MCU_1284P 3499 3500 3501 if args.eeprom_wipe: 3502 RNS.log("WARNING: EEPROM is being wiped! Power down device NOW if you do not want this!") 3503 rnode.wipe_eeprom() 3504 3505 if rnode.platform != ROM.PLATFORM_NRF52: 3506 rnode.hard_reset() 3507 3508 graceful_exit() 3509 3510 RNS.log("Reading EEPROM...") 3511 rnode.download_eeprom() 3512 3513 if rnode.provisioned: 3514 if rnode.model != ROM.MODEL_FF: 3515 fw_filename = models[rnode.model][4] 3516 else: 3517 if args.use_extracted: 3518 fw_filename = "extracted_rnode_firmware.zip" 3519 else: 3520 if rnode.platform == ROM.PLATFORM_AVR: 3521 if rnode.mcu == ROM.MCU_1284P: 3522 fw_filename = "rnode_firmware.hex" 3523 elif rnode.mcu == ROM.MCU_2560: 3524 fw_filename = "rnode_firmware_m2560.hex" 3525 elif rnode.platform == ROM.PLATFORM_ESP32: 3526 if rnode.board == ROM.BOARD_HUZZAH32: 3527 fw_filename = "rnode_firmware_featheresp32.zip" 3528 elif rnode.board == ROM.BOARD_GENERIC_ESP32: 3529 fw_filename = "rnode_firmware_esp32_generic.zip" 3530 elif rnode.platform == ROM.PLATFORM_NRF52: 3531 if rnode.board == ROM.BOARD_RAK4631: 3532 fw_filename = "rnode_firmware_rak4631.zip" 3533 else: 3534 fw_filename = None 3535 3536 if args.update: 3537 RNS.log("ERROR: No firmware found for this board. Cannot update.") 3538 graceful_exit() 3539 3540 if args.update: 3541 if not rnode.provisioned: 3542 RNS.log("Device not provisioned. Cannot update device firmware.") 3543 graceful_exit(1) 3544 3545 if args.use_extracted: 3546 fw_filename = "extracted_rnode_firmware.zip" 3547 3548 from subprocess import call 3549 3550 try: 3551 RNS.log("Checking firmware file availability...") 3552 fw_file_ensured = False 3553 if selected_version == None: 3554 ensure_firmware_file(fw_filename) 3555 fw_file_ensured = True 3556 3557 if not force_update: 3558 if rnode.version == selected_version: 3559 if args.fw_version != None: 3560 RNS.log("Specified firmware version ("+selected_version+") is already installed on this device") 3561 RNS.log("Override with -U option to install anyway") 3562 graceful_exit(0) 3563 else: 3564 RNS.log("Latest firmware version ("+selected_version+") is already installed on this device") 3565 RNS.log("Override with -U option to install anyway") 3566 graceful_exit(0) 3567 3568 if rnode.version > selected_version: 3569 if args.fw_version != None: 3570 RNS.log("Specified firmware version ("+selected_version+") is older than firmware already installed on this device") 3571 RNS.log("Override with -U option to install anyway") 3572 graceful_exit(0) 3573 else: 3574 RNS.log("Latest firmware version ("+selected_version+") is older than firmware already installed on this device") 3575 RNS.log("Override with -U option to install anyway") 3576 graceful_exit(0) 3577 3578 if not fw_file_ensured and selected_version != None: 3579 ensure_firmware_file(fw_filename) 3580 3581 if fw_filename.endswith(".zip") and not fw_filename == "extracted_rnode_firmware.zip": 3582 RNS.log("Decompressing firmware...") 3583 fw_src = UPD_DIR+"/"+selected_version+"/" 3584 try: 3585 with zipfile.ZipFile(fw_src+fw_filename) as zip: 3586 zip.extractall(fw_src) 3587 except Exception as e: 3588 RNS.log("Could not decompress firmware from downloaded zip file") 3589 graceful_exit() 3590 RNS.log("Firmware decompressed") 3591 3592 except Exception as e: 3593 RNS.log("Could not obtain firmware package for your board") 3594 RNS.log("The contained exception was: "+str(e)) 3595 graceful_exit() 3596 3597 if fw_filename == "extracted_rnode_firmware.zip": 3598 update_full_path = EXT_DIR+"/extracted_rnode_firmware.version" 3599 else: 3600 update_full_path = UPD_DIR+"/"+selected_version+"/"+fw_filename 3601 if os.path.isfile(update_full_path): 3602 try: 3603 args.info = False 3604 RNS.log("Updating RNode firmware for device on "+args.port) 3605 if fw_filename == "extracted_rnode_firmware.zip": 3606 vf = open(update_full_path, "rb") 3607 release_info = vf.read().decode("utf-8").strip() 3608 partition_hash = bytes.fromhex(release_info.split()[1]) 3609 vf.close() 3610 else: 3611 partition_filename = fw_filename.replace(".zip", ".bin") 3612 if fw_filename == "extracted_rnode_firmware.zip": 3613 partition_full_path = EXT_DIR+"/extracted_rnode_firmware.bin" 3614 else: 3615 partition_full_path = UPD_DIR+"/"+selected_version+"/"+partition_filename 3616 partition_hash = get_partition_hash(rnode.platform, partition_full_path) 3617 if partition_hash != None: 3618 try: 3619 rnode.indicate_firmware_update() 3620 except Exception as e: 3621 RNS.log("Error while indicating firmware update start to board, attempting update anyway...") 3622 rnode.set_firmware_hash(partition_hash) 3623 sleep(1) 3624 3625 if rnode.platform == ROM.PLATFORM_NRF52: 3626 # Allow extra time for writing to EEPROM on NRF52. Current implementation is slow. 3627 sleep(14) 3628 3629 try: 3630 rnode.disconnect() 3631 except Exception as e: 3632 RNS.log("Error while gracefully disconnecting device before firmware update, attempting update anyway...") 3633 3634 if rnode.model in ROM.MANUAL_FLASH_MODELS: 3635 RNS.log("") 3636 RNS.log("Please put the board into flashing mode now, by holding the BOOT or PRG button,") 3637 RNS.log("while momentarily pressing the RESET button. Then release the BOOT or PRG button.") 3638 RNS.log("Hit enter when this is done.") 3639 input() 3640 3641 flash_status = call(get_flasher_call(rnode.platform, fw_filename)) 3642 if flash_status == 0: 3643 RNS.log("Flashing new firmware completed") 3644 if rnode.model in ROM.MANUAL_FLASH_MODELS: 3645 RNS.log("") 3646 RNS.log("Please take the board out of flashing mode by momentarily pressing the RESET button.") 3647 RNS.log("Hit enter when this is done.") 3648 input() 3649 3650 RNS.log("Opening serial port "+args.port+"...") 3651 try: 3652 rnode_port = args.port 3653 rnode_serial = rnode_open_serial(rnode_port) 3654 except Exception as e: 3655 RNS.log("Could not open the specified serial port. The contained exception was:") 3656 RNS.log(str(e)) 3657 graceful_exit() 3658 3659 rnode = RNode(rnode_serial) 3660 thread = threading.Thread(target=rnode.readLoop, daemon=True).start() 3661 3662 try: 3663 rnode.device_probe() 3664 except Exception as e: 3665 RNS.log("Serial port opened, but RNode did not respond. Is a valid firmware installed?") 3666 print(e) 3667 graceful_exit() 3668 3669 if rnode.detected: 3670 if rnode.platform == None or rnode.mcu == None: 3671 rnode.platform = ROM.PLATFORM_AVR 3672 rnode.mcu = ROM.MCU_1284P 3673 3674 RNS.log("Reading EEPROM...") 3675 rnode.download_eeprom() 3676 3677 if rnode.provisioned: 3678 if rnode.model != ROM.MODEL_FF: 3679 fw_filename = models[rnode.model][4] 3680 else: 3681 fw_filename = None 3682 args.info = True 3683 if partition_hash != None: 3684 rnode.set_firmware_hash(partition_hash) 3685 3686 if args.info: 3687 RNS.log("") 3688 RNS.log("Firmware update completed successfully") 3689 else: 3690 RNS.log("An error occurred while flashing the new firmware, exiting now.") 3691 graceful_exit() 3692 3693 except Exception as e: 3694 RNS.log("Error while updating firmware") 3695 RNS.log(str(e)) 3696 else: 3697 RNS.log("Firmware update file not found") 3698 graceful_exit() 3699 3700 if args.config: 3701 rnode.download_cfg_sector() 3702 eeprom_reserved = 200 3703 if rnode.platform == ROM.PLATFORM_ESP32: eeprom_size = 296 3704 elif rnode.platform == ROM.PLATFORM_NRF52: eeprom_size = 296 3705 else: eeprom_size = 4096 3706 3707 eeprom_offset = eeprom_size-eeprom_reserved 3708 def ea(a): return a+eeprom_offset 3709 ec_bt = rnode.eeprom[ROM.ADDR_CONF_BT] 3710 ec_dint = rnode.eeprom[ROM.ADDR_CONF_DINT] 3711 ec_dadr = rnode.eeprom[ROM.ADDR_CONF_DADR] 3712 ec_dblk = rnode.eeprom[ROM.ADDR_CONF_DBLK] 3713 ec_drot = rnode.eeprom[ROM.ADDR_CONF_DROT] 3714 ec_pset = rnode.eeprom[ROM.ADDR_CONF_PSET] 3715 ec_pint = rnode.eeprom[ROM.ADDR_CONF_PINT] 3716 ec_bset = rnode.eeprom[ROM.ADDR_CONF_BSET] 3717 ec_dia = rnode.eeprom[ROM.ADDR_CONF_DIA] 3718 ec_wifi = rnode.eeprom[ROM.ADDR_CONF_WIFI] 3719 ec_wchn = rnode.eeprom[ROM.ADDR_CONF_WCHN] 3720 ec_ssid = None 3721 ec_psk = None 3722 ec_ip = None 3723 ec_nm = None 3724 3725 if ec_wchn < 1 or ec_wchn > 14: ec_wchn = 1 3726 if rnode.cfg_sector: 3727 ssid_bytes = b"" 3728 for i in range(0, 32): 3729 byte = rnode.cfg_sector[ROM.ADDR_CONF_SSID+i] 3730 if byte == 0xFF: byte = 0x00 3731 if byte == 0x00: break 3732 else: ssid_bytes += bytes([byte]) 3733 3734 try: ec_ssid = ssid_bytes.decode("utf-8") 3735 except Exception as e: print(f"Error: Could not decode WiFi SSID read from device") 3736 3737 psk_bytes = b"" 3738 for i in range(0, 32): 3739 byte = rnode.cfg_sector[ROM.ADDR_CONF_PSK+i] 3740 if byte == 0xFF: byte = 0x00 3741 if byte == 0x00: break 3742 else: psk_bytes += bytes([byte]) 3743 3744 ip_bytes = b"" 3745 for i in range(0, 4): 3746 byte = rnode.cfg_sector[ROM.ADDR_CONF_IP+i] 3747 ip_bytes += bytes([byte]) 3748 if len(ip_bytes) == 4: ec_ip = f"{int(ip_bytes[0])}.{int(ip_bytes[1])}.{int(ip_bytes[2])}.{int(ip_bytes[3])}" 3749 if ec_ip == "255.255.255.255" or ec_ip == "0.0.0.0": ec_ip = None 3750 3751 nm_bytes = b"" 3752 for i in range(0, 4): 3753 byte = rnode.cfg_sector[ROM.ADDR_CONF_NM+i] 3754 nm_bytes += bytes([byte]) 3755 if len(nm_bytes) == 4: ec_nm = f"{int(nm_bytes[0])}.{int(nm_bytes[1])}.{int(nm_bytes[2])}.{int(nm_bytes[3])}" 3756 if ec_nm == "255.255.255.255" or ec_nm == "0.0.0.0": ec_nm = None 3757 3758 if ec_wifi == 0x02: 3759 ec_ip = "10.0.0.1" 3760 ec_nm = "255.255.255.0" 3761 3762 try: ec_psk = psk_bytes.decode("utf-8") 3763 except Exception as e: print(f"Error: Could not decode WiFi PSK read from device") 3764 if not args.show_psk and ec_psk: ec_psk = "*"*len(ec_psk) 3765 3766 print("\nDevice configuration:") 3767 if ec_bt == 0x73: print(f" Bluetooth : Enabled") 3768 else: print(f" Bluetooth : Disabled") 3769 if ec_wifi == 0x01: print(f" WiFi : Enabled (Station)") 3770 if ec_wifi == 0x02: print(f" WiFi : Enabled (AP)") 3771 else: print(f" WiFi : Disabled") 3772 if ec_wifi == 0x01 or ec_wifi == 0x02: 3773 if not ec_wchn: print(f" Channel : Unknown") 3774 else: print(f" Channel : {ec_wchn}") 3775 if not ec_ssid: print(f" SSID : Not set") 3776 else: print(f" SSID : {ec_ssid}") 3777 if not ec_psk: print(f" PSK : Not set") 3778 else: print(f" PSK : {ec_psk}") 3779 if not ec_ip: print(f" IP Address : DHCP") 3780 else: print(f" IP Address : {ec_ip}") 3781 if ec_ip and ec_nm: print(f" Network Mask : {ec_nm}") 3782 if ec_dia == 0x00: print(f" Interference avoidance : Enabled") 3783 else: print(f" Interference avoidance : Disabled") 3784 print( f" Display brightness : {ec_dint}") 3785 if ec_dadr == 0xFF: print(f" Display address : Default") 3786 else: print(f" Display address : {RNS.hexrep(ec_dadr, delimit=False)}") 3787 if ec_bset == 0x73 and ec_dblk != 0x00: print(f" Display blanking : {ec_dblk}s") 3788 else: print(f" Display blanking : Disabled") 3789 if ec_drot != 0xFF: 3790 if ec_drot == 0x00: rstr = "Landscape" 3791 if ec_drot == 0x01: rstr = "Portrait" 3792 if ec_drot == 0x02: rstr = "Landscape 180" 3793 if ec_drot == 0x03: rstr = "Portrait 180" 3794 print(f" Display rotation : {rstr}") 3795 else: 3796 print(f" Display rotation : Default") 3797 if ec_pset == 0x73: print(f" Neopixel Intensity : {ec_pint}") 3798 print("") 3799 3800 rnode.leave() 3801 graceful_exit() 3802 3803 if args.eeprom_dump: 3804 RNS.log("EEPROM contents:") 3805 RNS.log(RNS.hexrep(rnode.eeprom)) 3806 graceful_exit() 3807 3808 if args.eeprom_backup: 3809 try: 3810 timestamp = time.time() 3811 filename = str(time.strftime("%Y-%m-%d_%H-%M-%S")) 3812 path = ROM_DIR + filename + ".eeprom" 3813 file = open(path, "wb") 3814 file.write(rnode.eeprom) 3815 file.close() 3816 RNS.log("EEPROM backup written to: "+path) 3817 except Exception as e: 3818 RNS.log("EEPROM was successfully downloaded from device,") 3819 RNS.log("but file could not be written to disk.") 3820 graceful_exit() 3821 3822 if isinstance(args.display, int): 3823 di = args.display 3824 if di < 0: 3825 di = 0 3826 if di > 255: 3827 di = 255 3828 RNS.log("Setting display intensity to "+str(di)) 3829 rnode.set_display_intensity(di) 3830 3831 if isinstance(args.timeout, int): 3832 di = args.timeout 3833 if di < 0: 3834 di = 0 3835 if di > 255: 3836 di = 255 3837 if di == 0: 3838 RNS.log("Disabling display blanking") 3839 else: 3840 RNS.log("Setting display timeout to "+str(di)) 3841 rnode.set_display_blanking(di) 3842 3843 if isinstance(args.rotation, int): 3844 dr = args.rotation 3845 if dr < 0: 3846 dr = 0 3847 if dr > 3: 3848 dr = 3 3849 3850 RNS.log("Setting display rotation to "+str(dr)) 3851 rnode.set_display_rotation(dr) 3852 3853 if isinstance(args.recondition_display, bool): 3854 if args.recondition_display: 3855 RNS.log("Starting display reconditioning") 3856 rnode.recondition_display() 3857 3858 if isinstance(args.ia_enable, bool): 3859 if args.ia_enable: 3860 RNS.log("Enabling interference avoidance") 3861 rnode.set_disable_interference_avoidance(False) 3862 3863 if isinstance(args.ia_disable, bool): 3864 if args.ia_disable: 3865 RNS.log("Disabling interference avoidance") 3866 rnode.set_disable_interference_avoidance(True) 3867 3868 if isinstance(args.np, int): 3869 di = args.np 3870 if di < 0: 3871 di = 0 3872 if di > 255: 3873 di = 255 3874 RNS.log("Setting NeoPixel intensity to "+str(di)) 3875 rnode.set_neopixel_intensity(di) 3876 3877 if isinstance(args.display_addr, str): 3878 set_addr = False 3879 try: 3880 if args.display_addr.startswith("0x"): 3881 args.display_addr = args.display_addr[2:] 3882 da = bytes.fromhex(args.display_addr) 3883 set_addr = True 3884 except Exception as e: 3885 pass 3886 3887 if set_addr and len(da) == 1: 3888 RNS.log("Setting display address to "+RNS.hexrep(da, delimit=False)) 3889 rnode.set_display_address(ord(da)) 3890 rnode.hard_reset() 3891 graceful_exit() 3892 else: 3893 RNS.log("Invalid display address specified") 3894 3895 if args.bluetooth_on: 3896 RNS.log("Enabling Bluetooth...") 3897 rnode.enable_bluetooth() 3898 rnode.leave() 3899 3900 if args.bluetooth_off: 3901 RNS.log("Disabling Bluetooth...") 3902 rnode.disable_bluetooth() 3903 rnode.leave() 3904 3905 if args.bluetooth_pair: 3906 RNS.log("Putting device into Bluetooth pairing mode. Press enter to exit when done.") 3907 rnode.bluetooth_pair() 3908 input() 3909 rnode.leave() 3910 3911 if args.channel: 3912 try: 3913 RNS.log(f"Setting WiFi channel to {args.channel}") 3914 rnode.set_wifi_channel(args.channel) 3915 except Exception as e: 3916 print(f"Could not set WiFi channel: {e}") 3917 graceful_exit() 3918 3919 if args.ssid: 3920 try: 3921 if args.ssid.lower() == "none": 3922 ssid_str = None 3923 RNS.log(f"Deleting WiFi SSID") 3924 else: 3925 ssid_str = str(args.ssid) 3926 RNS.log(f"Setting WiFi SSID to: {ssid_str}") 3927 rnode.set_wifi_ssid(ssid_str) 3928 except Exception as e: 3929 print(f"Could not set WiFi SSID: {e}") 3930 graceful_exit() 3931 3932 if args.psk: 3933 try: 3934 if args.psk.lower() == "none": 3935 psk_str = None 3936 RNS.log(f"Deleting WiFi PSK") 3937 else: 3938 psk_str = str(args.psk) 3939 RNS.log(f"Setting WiFi PSK") 3940 rnode.set_wifi_psk(psk_str) 3941 except Exception as e: 3942 print(f"Could not set WiFi PSK: {e}") 3943 graceful_exit() 3944 3945 if args.ip: 3946 try: 3947 if args.ip.lower() == "none": 3948 RNS.log(f"Setting WiFi IP to DHCP...") 3949 rnode.set_wifi_ip(None) 3950 else: 3951 RNS.log(f"Setting WiFi static IP to: {args.ip}") 3952 rnode.set_wifi_ip(args.ip) 3953 except Exception as e: 3954 print(f"Could not set WiFi IP: {e}") 3955 graceful_exit() 3956 3957 if args.nm: 3958 try: 3959 if args.nm.lower() == "none": 3960 RNS.log(f"Deleting WiFi static netmask configuration...") 3961 rnode.set_wifi_nm(None) 3962 else: 3963 RNS.log(f"Setting WiFi static netmask to: {args.nm}") 3964 rnode.set_wifi_nm(args.nm) 3965 except Exception as e: 3966 print(f"Could not set WiFi netmask: {e}") 3967 graceful_exit() 3968 3969 if args.wifi: 3970 try: 3971 mode = 0x00 3972 if str(args.wifi).lower().startswith("sta"): mode = 0x01 3973 elif str(args.wifi).lower().startswith("ap"): mode = 0x02 3974 if mode == 0x00: RNS.log(f"Disabling WiFi...") 3975 elif mode == 0x01: RNS.log(f"Setting WiFi to station mode") 3976 elif mode == 0x02: RNS.log(f"Setting WiFi to AP mode") 3977 rnode.set_wifi_mode(mode) 3978 except Exception as e: 3979 print(f"Could not set WiFi mode: {e}") 3980 graceful_exit() 3981 3982 if args.info: 3983 if rnode.provisioned: 3984 timestamp = struct.unpack(">I", rnode.made)[0] 3985 timestring = datetime.datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M:%S") 3986 sigstring = "Unverified" 3987 if rnode.signature_valid: 3988 if rnode.locally_signed: 3989 sigstring = "Validated - Local signature" 3990 else: 3991 sigstring = "Genuine board, vendor is "+rnode.vendor 3992 3993 if rnode.board != None: 3994 board_string = ":"+bytes([rnode.board]).hex() 3995 else: 3996 board_string = "" 3997 3998 RNS.log("") 3999 RNS.log("Device info:") 4000 RNS.log("\tProduct : "+products[rnode.product]+" "+models[rnode.model][3]+" ("+bytes([rnode.product]).hex()+":"+bytes([rnode.model]).hex()+board_string+")") 4001 RNS.log("\tDevice signature : "+sigstring) 4002 RNS.log("\tFirmware version : "+rnode.version) 4003 RNS.log("\tHardware revision : "+str(int(rnode.hw_rev))) 4004 RNS.log("\tSerial number : "+RNS.hexrep(rnode.serialno)) 4005 RNS.log("\tModem chip : "+str(models[rnode.model][5])) 4006 RNS.log("\tFrequency range : "+str(rnode.min_freq/1e6)+" MHz - "+str(rnode.max_freq/1e6)+" MHz") 4007 RNS.log("\tMax TX power : "+str(rnode.max_output)+" dBm") 4008 RNS.log("\tManufactured : "+timestring) 4009 4010 if rnode.configured: 4011 rnode.bandwidth = rnode.conf_bandwidth 4012 rnode.r_bandwidth = rnode.conf_bandwidth 4013 rnode.sf = rnode.conf_sf 4014 rnode.r_sf = rnode.conf_sf 4015 rnode.cr = rnode.conf_cr 4016 rnode.r_cr = rnode.conf_cr 4017 rnode.updateBitrate() 4018 txp_mw = round(pow(10, (rnode.conf_txpower/10)), 3) 4019 RNS.log(""); 4020 RNS.log("\tDevice mode : TNC") 4021 RNS.log("\t Frequency : "+str((rnode.conf_frequency/1000000.0))+" MHz") 4022 RNS.log("\t Bandwidth : "+str(rnode.conf_bandwidth/1000.0)+" KHz") 4023 RNS.log("\t TX power : "+str(rnode.conf_txpower)+" dBm ("+str(txp_mw)+" mW)") 4024 RNS.log("\t Spreading factor : "+str(rnode.conf_sf)) 4025 RNS.log("\t Coding rate : "+str(rnode.conf_cr)) 4026 RNS.log("\t On-air bitrate : "+str(rnode.bitrate_kbps)+" kbps") 4027 else: 4028 RNS.log("\tDevice mode : Normal (host-controlled)") 4029 4030 print("") 4031 rnode.disconnect() 4032 graceful_exit() 4033 4034 else: 4035 RNS.log("EEPROM is invalid, no further information available") 4036 graceful_exit() 4037 4038 if args.rom: 4039 if rnode.provisioned and not args.autoinstall: 4040 RNS.log("EEPROM bootstrap was requested, but a valid EEPROM was already present.") 4041 RNS.log("No changes are being made.") 4042 graceful_exit() 4043 4044 else: 4045 if rnode.signature_valid: 4046 RNS.log("EEPROM bootstrap was requested, but a valid EEPROM was already present.") 4047 RNS.log("No changes are being made.") 4048 graceful_exit() 4049 else: 4050 if args.autoinstall: 4051 RNS.log("Clearing old EEPROM, this will take about 15 seconds...") 4052 rnode.wipe_eeprom() 4053 4054 if rnode.platform == ROM.PLATFORM_ESP32: 4055 RNS.log("Waiting for ESP32 reset...") 4056 time.sleep(6) 4057 elif rnode.platform == ROM.PLATFORM_NRF52: 4058 rnode_serial.close() 4059 RNS.log("Waiting for NRF52 reset...") 4060 time.sleep(18) 4061 selected_port = None 4062 ports = list_ports.comports() 4063 for port in ports: 4064 if port.serial_number == rnode.usb_serial_id: 4065 selected_port = port 4066 break 4067 if selected_port is None: 4068 RNS.log("Could not detect new port for NRF52...") 4069 else: 4070 try: 4071 rnode_serial = rnode_open_serial(selected_port.device) 4072 rnode.serial = rnode_serial 4073 thread = threading.Thread(target=rnode.readLoop, daemon=True).start() 4074 except Exception as e: 4075 RNS.log("Could not open the specified serial port. The contained exception was:") 4076 RNS.log(str(e)) 4077 exit() 4078 else: 4079 time.sleep(3) 4080 4081 counter = None 4082 counter_path = FWD_DIR+"/serial.counter" 4083 try: 4084 if os.path.isfile(counter_path): 4085 file = open(counter_path, "r") 4086 counter_str = file.read() 4087 counter = int(counter_str) 4088 file.close() 4089 else: 4090 counter = 0 4091 except Exception as e: 4092 RNS.log("Could not create device serial number, exiting") 4093 RNS.log(str(e)) 4094 graceful_exit() 4095 4096 serialno = counter+1 4097 model = None 4098 hwrev = None 4099 if args.product != None: 4100 if args.product == "03": 4101 mapped_product = ROM.PRODUCT_RNODE 4102 elif args.product == "10": 4103 mapped_product = ROM.PRODUCT_RAK4631 4104 elif args.product == "f0": 4105 mapped_product = ROM.PRODUCT_HMBRW 4106 elif args.product == "e0": 4107 mapped_product = ROM.PRODUCT_TBEAM 4108 else: 4109 if len(args.product) == 2: 4110 mapped_product = ord(bytes.fromhex(args.product)) 4111 4112 if mapped_model != None: 4113 if mapped_model == ROM.MODEL_B4_TCXO: 4114 model = ROM.MODEL_B4 4115 elif mapped_model == ROM.MODEL_B9_TCXO: 4116 model = ROM.MODEL_B9 4117 else: 4118 model = mapped_model 4119 else: 4120 if args.model == "11": 4121 model = ROM.MODEL_11 4122 elif args.model == "12": 4123 model = ROM.MODEL_12 4124 elif args.model == "a4": 4125 model = ROM.MODEL_A4 4126 elif args.model == "a9": 4127 model = ROM.MODEL_A9 4128 elif args.model == "a1": 4129 model = ROM.MODEL_A1 4130 elif args.model == "a6": 4131 model = ROM.MODEL_A6 4132 elif args.model == "e4": 4133 model = ROM.MODEL_E4 4134 elif args.model == "e9": 4135 model = ROM.MODEL_E9 4136 elif args.model == "ff": 4137 model = ROM.MODEL_FF 4138 else: 4139 if len(args.model) == 2: 4140 model = ord(bytes.fromhex(args.model)) 4141 4142 # Initialize selected_model from specified model 4143 selected_model = model 4144 4145 if args.hwrev != None and (args.hwrev > 0 and args.hwrev < 256): 4146 hwrev = chr(args.hwrev) 4147 4148 if serialno > 0 and model != None and hwrev != None: 4149 try: 4150 from cryptography.hazmat.primitives import hashes 4151 from cryptography.hazmat.backends import default_backend 4152 4153 timestamp = int(time.time()) 4154 time_bytes = struct.pack(">I", timestamp) 4155 serial_bytes = struct.pack(">I", serialno) 4156 file = open(counter_path, "w") 4157 file.write(str(serialno)) 4158 file.close() 4159 4160 info_chunk = b"" + bytes([mapped_product, model, ord(hwrev)]) 4161 info_chunk += serial_bytes 4162 info_chunk += time_bytes 4163 digest = hashes.Hash(hashes.MD5(), backend=default_backend()) 4164 digest.update(info_chunk) 4165 checksum = digest.finalize() 4166 4167 RNS.log("Loading signing key...") 4168 signature = None 4169 key_path = FWD_DIR+"/signing.key" 4170 if os.path.isfile(key_path): 4171 try: 4172 file = open(key_path, "rb") 4173 private_bytes = file.read() 4174 file.close() 4175 private_key = serialization.load_der_private_key( 4176 private_bytes, 4177 password=None, 4178 backend=default_backend() 4179 ) 4180 public_key = private_key.public_key() 4181 public_bytes = public_key.public_bytes( 4182 encoding=serialization.Encoding.DER, 4183 format=serialization.PublicFormat.SubjectPublicKeyInfo 4184 ) 4185 signature = private_key.sign( 4186 checksum, 4187 padding.PSS( 4188 mgf=padding.MGF1(hashes.SHA256()), 4189 salt_length=padding.PSS.MAX_LENGTH 4190 ), 4191 hashes.SHA256() 4192 ) 4193 except Exception as e: 4194 RNS.log("Error while signing EEPROM") 4195 RNS.log(str(e)) 4196 else: 4197 RNS.log("No signing key found") 4198 graceful_exit() 4199 4200 if selected_model in ROM.MANUAL_FLASH_MODELS: 4201 rnode.serial.close() 4202 RNS.log("") 4203 RNS.log("Please reset the board by momentarily pressing the RESET button.") 4204 RNS.log("Hit enter when this is done.") 4205 input() 4206 sleep(2.5) 4207 rnode_serial = rnode_open_serial(rnode_port) 4208 rnode = RNode(rnode_serial) 4209 4210 RNS.log("Bootstrapping device EEPROM...") 4211 4212 rnode.write_eeprom(ROM.ADDR_PRODUCT, mapped_product) 4213 time.sleep(0.006) 4214 rnode.write_eeprom(ROM.ADDR_MODEL, model) 4215 time.sleep(0.006) 4216 rnode.write_eeprom(ROM.ADDR_HW_REV, ord(hwrev)) 4217 time.sleep(0.006) 4218 rnode.write_eeprom(ROM.ADDR_SERIAL, serial_bytes[0]) 4219 time.sleep(0.006) 4220 rnode.write_eeprom(ROM.ADDR_SERIAL+1, serial_bytes[1]) 4221 time.sleep(0.006) 4222 rnode.write_eeprom(ROM.ADDR_SERIAL+2, serial_bytes[2]) 4223 time.sleep(0.006) 4224 rnode.write_eeprom(ROM.ADDR_SERIAL+3, serial_bytes[3]) 4225 time.sleep(0.006) 4226 rnode.write_eeprom(ROM.ADDR_MADE, time_bytes[0]) 4227 time.sleep(0.006) 4228 rnode.write_eeprom(ROM.ADDR_MADE+1, time_bytes[1]) 4229 time.sleep(0.006) 4230 rnode.write_eeprom(ROM.ADDR_MADE+2, time_bytes[2]) 4231 time.sleep(0.006) 4232 rnode.write_eeprom(ROM.ADDR_MADE+3, time_bytes[3]) 4233 time.sleep(0.006) 4234 4235 for i in range(0,16): 4236 rnode.write_eeprom(ROM.ADDR_CHKSUM+i, checksum[i]) 4237 time.sleep(0.006) 4238 4239 for i in range(0,128): 4240 rnode.write_eeprom(ROM.ADDR_SIGNATURE+i, signature[i]) 4241 time.sleep(0.006) 4242 4243 rnode.write_eeprom(ROM.ADDR_INFO_LOCK, ROM.INFO_LOCK_BYTE) 4244 if rnode.platform == ROM.PLATFORM_NRF52: 4245 # Allow extra time for writing to EEPROM on NRF52. Current implementation is slow. 4246 sleep(3) 4247 4248 RNS.log("EEPROM written! Validating...") 4249 4250 if wants_fw_provision: 4251 partition_hash = None 4252 4253 if fw_filename == "extracted_rnode_firmware.zip": 4254 update_full_path = EXT_DIR+"/extracted_rnode_firmware.version" 4255 vf = open(update_full_path, "rb") 4256 release_info = vf.read().decode("utf-8").strip() 4257 partition_hash = bytes.fromhex(release_info.split()[1]) 4258 vf.close() 4259 else: 4260 partition_filename = fw_filename.replace(".zip", ".bin") 4261 partition_hash = get_partition_hash(rnode.platform, UPD_DIR+"/"+selected_version+"/"+partition_filename) 4262 4263 if partition_hash != None: 4264 time.sleep(0.75) 4265 RNS.log("Setting firmware checksum...") 4266 rnode.set_firmware_hash(partition_hash) 4267 4268 if rnode.platform == ROM.PLATFORM_ESP32: 4269 rnode.hard_reset() 4270 RNS.log("Waiting for ESP32 reset...") 4271 time.sleep(7) 4272 if selected_model in [ROM.MODEL_AC, ROM.MODEL_A6, ROM.MODEL_A1, ROM.MODEL_AA, ROM.MODEL_A5]: 4273 time.sleep(5) 4274 4275 elif rnode.platform == ROM.PLATFORM_NRF52: 4276 # Wait a few seconds before hard resetting. 4277 # Otherwise, macOS fails to set firmware hash on NRF52 4278 if RNS.vendor.platformutils.is_darwin(): 4279 time.sleep(5) 4280 4281 rnode.hard_reset() 4282 # The hard reset on this platform is different 4283 # to that of the ESP32 platform, it causes 4284 # disruption to the serial connection. 4285 # Therefore, we have to reestablish the serial 4286 # connection after the reset. 4287 rnode_serial.close() 4288 RNS.log("Waiting for NRF52 reset...") 4289 4290 # Give plenty of time for to allow for 4291 # potential e-ink display refresh too. 4292 time.sleep(20) 4293 4294 # After the hard reset, the port number will 4295 # change. We need to find the new port number, 4296 # which can be done non-interactively by 4297 # comparing the USB serial numbers of the 4298 # original port and the one we are currently 4299 # iterating. 4300 selected_port = None 4301 ports = list_ports.comports() 4302 for port in ports: 4303 if port.serial_number == rnode.usb_serial_id: 4304 selected_port = port 4305 break 4306 if selected_port is None: 4307 RNS.log("Could not detect new port for NRF52...") 4308 else: 4309 try: 4310 rnode_serial = rnode_open_serial(selected_port.device) 4311 rnode.serial = rnode_serial 4312 thread = threading.Thread(target=rnode.readLoop, daemon=True).start() 4313 except Exception as e: 4314 RNS.log("Could not open the specified serial port. The contained exception was:") 4315 RNS.log(str(e)) 4316 exit() 4317 else: 4318 rnode.hard_reset() 4319 4320 if selected_model in ROM.MANUAL_FLASH_MODELS: 4321 rnode.serial.close() 4322 RNS.log("") 4323 RNS.log("Please reset the board by momentarily pressing the RESET button.") 4324 RNS.log("Hit enter when this is done.") 4325 input() 4326 rnode.provisioned = True 4327 else: 4328 rnode.download_eeprom() 4329 4330 if rnode.provisioned: 4331 RNS.log("EEPROM Bootstrapping successful!") 4332 if not selected_model in ROM.MANUAL_FLASH_MODELS: 4333 rnode.hard_reset() 4334 4335 if args.autoinstall: 4336 print("") 4337 print("RNode Firmware autoinstallation complete!") 4338 print("") 4339 print("To use your device with Reticulum, read the documetation at:") 4340 print("") 4341 print("https://markqvist.github.io/Reticulum/manual/gettingstartedfast.html") 4342 print("") 4343 print("* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *") 4344 print(" Important! ") 4345 print("") 4346 print("ESP32-based RNodes are created with the RNode Bootstrap Console on-board.") 4347 print("") 4348 print("This repository is hosted directly on the RNode, and contains a wealth of") 4349 print("information, software and tools.") 4350 print("") 4351 print("The RNode Bootstrap Console also contains everything needed to build") 4352 print("and replicate RNodes, including detailed build recipes, 3D-printable") 4353 print("cases, and copies of the source code for both the RNode Firmware,") 4354 print("Reticulum and other utilities.") 4355 print("") 4356 print("To activate the RNode Bootstrap Console, power up your RNode and hold") 4357 print("down the user button for 10+ seconds, then release. The RNode will now") 4358 print("reboot into console mode, and activate a WiFi access point for you to") 4359 print("connect to. The console is then reachable at: http://10.0.0.1") 4360 print("") 4361 print("* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *") 4362 print("") 4363 print("Thank you for using this utility! Please help the project by") 4364 print("contributing code and reporting bugs, or by donating!") 4365 print("") 4366 print("Your contributions and donations directly further the realisation") 4367 print("of truly open, free and resilient communications systems.") 4368 print("") 4369 print_donation_block() 4370 print("") 4371 try: 4372 os.makedirs(FWD_DIR+"/device_db/", exist_ok=True) 4373 file = open(FWD_DIR+"/device_db/"+serial_bytes.hex(), "wb") 4374 written = file.write(rnode.eeprom) 4375 file.close() 4376 except Exception as e: 4377 RNS.log("WARNING: Could not backup device EEPROM to disk") 4378 graceful_exit() 4379 else: 4380 RNS.log("EEPROM was written, but validation failed. Check your settings.") 4381 graceful_exit() 4382 except Exception as e: 4383 RNS.log("An error occurred while writing EEPROM. The contained exception was:") 4384 RNS.log(str(e)) 4385 raise e 4386 4387 else: 4388 RNS.log("Invalid data specified, cancelling EEPROM write") 4389 graceful_exit() 4390 4391 if args.sign: 4392 if rnode.provisioned: 4393 try: 4394 device_signer = RNS.Identity.from_file(FWD_DIR+"/device.key") 4395 except Exception as e: 4396 RNS.log("Could not load device signing key") 4397 4398 if rnode.device_hash == None: 4399 RNS.log("No device hash present, skipping device signing") 4400 else: 4401 if device_signer == None: 4402 RNS.log("No device signer loaded, cannot sign device") 4403 graceful_exit(78) 4404 else: 4405 new_device_signature = device_signer.sign(rnode.device_hash) 4406 rnode.store_signature(new_device_signature) 4407 RNS.log("Device signed") 4408 else: 4409 RNS.log("This device has not been provisioned yet, cannot create device signature") 4410 graceful_exit(79) 4411 4412 if args.firmware_hash != None: 4413 if rnode.provisioned: 4414 try: 4415 hash_data = bytes.fromhex(args.firmware_hash) 4416 if len(hash_data) != 32: 4417 raise ValueError("Incorrect hash length") 4418 4419 rnode.set_firmware_hash(hash_data) 4420 RNS.log("Firmware hash set") 4421 except Exception as e: 4422 RNS.log("The provided value was not a valid SHA256 hash") 4423 graceful_exit(78) 4424 4425 else: 4426 RNS.log("This device has not been provisioned yet, cannot set firmware hash") 4427 graceful_exit(77) 4428 4429 if args.get_target_firmware_hash: 4430 if rnode.provisioned: 4431 RNS.log(f"The target firmware hash is: {rnode.firmware_hash_target.hex()}") 4432 4433 else: 4434 RNS.log("This device has not been provisioned yet, cannot get firmware hash") 4435 exit(77) 4436 4437 if args.get_firmware_hash: 4438 if rnode.provisioned: 4439 RNS.log(f"The actual firmware hash is: {rnode.firmware_hash.hex()}") 4440 4441 else: 4442 RNS.log("This device has not been provisioned yet, cannot get firmware hash") 4443 exit(77) 4444 4445 if rnode.provisioned: 4446 if args.normal: 4447 rnode.setNormalMode() 4448 RNS.log("Device set to normal (host-controlled) operating mode") 4449 graceful_exit() 4450 if args.tnc: 4451 if not (args.freq and args.bw and args.txp and args.sf and args.cr): 4452 RNS.log("Please input startup configuration:") 4453 4454 print("") 4455 if args.freq: 4456 rnode.frequency = args.freq 4457 else: 4458 print("Frequency in Hz:\t", end="") 4459 rnode.frequency = int(input()) 4460 4461 4462 if args.bw: 4463 rnode.bandwidth = args.bw 4464 else: 4465 print("Bandwidth in Hz:\t", end="") 4466 rnode.bandwidth = int(input()) 4467 4468 if args.txp != None and (args.txp >= 0 and args.txp <= 17): 4469 rnode.txpower = args.txp 4470 else: 4471 print("TX Power in dBm:\t", end="") 4472 rnode.txpower = int(input()) 4473 4474 if args.sf: 4475 rnode.sf = args.sf 4476 else: 4477 print("Spreading factor:\t", end="") 4478 rnode.sf = int(input()) 4479 4480 if args.cr: 4481 rnode.cr = args.cr 4482 else: 4483 print("Coding rate:\t\t", end="") 4484 rnode.cr = int(input()) 4485 4486 print("") 4487 4488 rnode.initRadio() 4489 sleep(0.5) 4490 rnode.setTNCMode() 4491 RNS.log("Device set to TNC operating mode") 4492 sleep(1.0) 4493 4494 graceful_exit() 4495 else: 4496 RNS.log("This device contains a valid firmware, but EEPROM is invalid.") 4497 RNS.log("Probably the device has not been initialised, or the EEPROM has been erased.") 4498 RNS.log("Please correctly initialise the device and try again!") 4499 4500 else: 4501 print("") 4502 parser.print_help() 4503 print("") 4504 graceful_exit() 4505 4506 4507 except KeyboardInterrupt: 4508 print("") 4509 graceful_exit() 4510 4511 graceful_exit() 4512 4513 def extract_recovery_esptool(): 4514 if not os.path.isfile(RT_PATH): 4515 try: 4516 RNS.log("Extracting recovery ESP-Tool...") 4517 import base64, gzip 4518 recovery_esptool = "H4sICPKKwmMAA2VzcHRvb2xfcmVzY3VlLnB5AKRaeXcax7L/X5+i43eUYewxYtGK3DkPIWRzre0CsuPYeXMGaKS5ghkyiySSk+/+flXdswCSb26uTyJ6ra69q6pnGoVz4brTNEkj5brCny/CKBET/8GP/TBwFpEfJJgOxgm6W277SlrvTvCvZ6FzKa3u4LrZeNtpUvci6w6alZFKvKZNo+drow0e/ViMUvcf0rroXri9y2G3f9k+p6FevqJB3Q/SGt/5C9efUO+9maT2GdrzkZpM1ESczbz4jga7vOCwcbhHvVNp7V6cvB3XqdORViPvnEgrTaZvD6ndltYwDO/FdrUxjUUSChV5sRJTgilGs3B8T6uOsCMeuZGKVUL9Q2lF3iO1DqR1WJtTax9H6NYeWvvc2pXWJEx5SxNNP6RWQ1q/mcE6BlWixtypSeuTN/Mn4l4t9dEiSEFkFIt5GidipIQfiMgLbpWovSUaf5dWr3914V60Bx/RXUprOxaVSGlBiu0Jsf1JWv1hxz1tD9voPYJHPw/77Yts4EFajsBvKkfWt6fJCM3E8HF/H51YWvX9ixO0IpDKjd+ktVdvfKTmAqTu7XMzBL+Z/gD0MaVzBjmdojmT1k890p97jU0PGKD3L2mdfBl2IZ9OdzDonZx3MegbhPWSO2mdgka0bqV13T497V2+R2cqrc/+mY+WklYQ5qKZFD0XjXgZjDE6ZvGjMZKWihfNxnif1BIDXjZAKvmL6cRNVmWMfFkZIdX7ORuhzmfTQfMTNw3TbkgwjP6Q0OfWgPURjT44yo1/SstLE+LUNYbwA0OLSACXWq5owbzeMd/OmZfjGpofSWmmXjpLcqphSo+0scerarTqA+3EL6yG0DvDDH66DI8hwj7ulVqg1ZHDKFVb7ok882YxGm15GQZqyzgGL7pdeFGsnBEsY3/XGfmBF4993xmHi6VzB0OZ+SPHD+IFFNnxQ8dPVJSE4Sx2wtjBrHpy4gRO5ZZ+UqyJl7GT+HPl/I6dW0m0bJmjYhX53mxLPY3VIhE9HuxGURi12ClVrOulXiL8WARhAoOIE282gxeYhpHYjquic6dgOMmdEv1u+/SiyxNmmUcOjTuEBtpxFRazjKvqSY3TxBvNlH0ceT54QEhtCfzzp5Y+0v+dt1uwQT1Qdd1JOIYD9YIJJPJvVrUYbpmmyqtvAVSGOFVdLMVELVQwiQUwXBgqHTFKE6IlUkSwJ8ZhMJ3540Q8+skd9dMoUkEyW5YYsfDG9x5cRODN0TPIW9VvwbfgS5iKubckT0K0krt7DKN7SDhMgwkOwiGjpbAW/kKkgQFp6DgWNJqNZRhaBkNsZMCR8u5FSBiXMLpeJnegKg6nyaMHUpI7LymTu4LjMKM2CMVtGEKw/hPLkA+J/Nu7BFOPjvCglImY0mVGbPFvU1Ix8eBHSerNVPAAZRgoJe6SZBG3dnZuwbJ0VB2H8x1wHZYT+9Mdw/8dP45TFe809o/+h5tYNQdj3zYP9xpHh/WjfUZh4sfjNGbfGk5Zx8A2Fc2WdDDvq8R29ZWdKfBwuVBGfb043lT0KltJdebHiUvDEHEsit537ABcKjH4AXcEIVXZjm3CLJMOeLRQMF0S9IjEHYpwpk2lpHgVIx8AVhEYg7+wJPtvmFKFbOlTtz/oXV06a3aVGZahqcs/2JdZmaD1C8DFIXMJF+dFj35gtQp2SLfNaxV8lLamLdc1pLuutJpV+NCL9s/uDUKKZkPuNo52j/YPGkd7+WhjV9b3Dw4OGvW9rdPuWfvmfOgOexfdq5uhbG4Nhu3+0D07bw8+5KON2lbnQ+/a7fbbg24+Wscwwcz6m0teN7YGXy47+Ypatb51cbqX9d3rbt+9OJGHW3pXv/sePFufbdbM9Od+b9hdn90FDoifupenLgUBxUm1grhBt99rn6/ul/VaPt+5urzsdoZuezjsXlwPB/JgC3eLIOeMEMVdqMidjyox7AG2arpO7P+u3NEyUbGtpWeJwRgmF2f7YvF454/hoGDJtPgtXQ3+1B8L6xiGh5tLroJ8XSlg7tRr/K9aszPV0HverUmsBVVNo0CsDfMmM6U3MkkuR5IJYoNw7s5CD3ZboSHbgPnD/dQyUQ+46bifWxxscvtn3R7o3hfTa550h2099Et5qMlDnh7q6N7I9PZpAUb+/Epn/8qY3SJUyS50MCXALaomGHnwx/AnbMguWYFDVuCYFa6XJGq+SGJcvH5CS0ZeOnEIqnT/6SSRN1bSPXFGCvaE1kcjKheGnxkS+wFvfMfmxbGlInNSk/KpZh/9M75noH0Lb9qOre0chJ2vzG/P7B+EyKgBtxajAHacsxCqOgBm8VRyUKtUaSI0URsMsFcOYufAoh4jhIfPeEnsx4xGsfKls/lQvbpqjq4YfL6PCd+F+Yhxe5UzD96TXblzNeBfGz5fRdEGv7RQdJjjto3DK68x4kDIP/V8ugbg5Q1KLRZMiSQcYBuSjfCNhdAQvMEQTqB7anzfoPdLdyD/qB+23IVTP2q5vzmNWsvtO416yx07jUbLHTiNZsuNnMZuy42dxl7LalKA7TT2W9Y+hbl/smqP6QZx43RBSECps6SyQg1Hz1LTKBkCIvo5hW+IvIQum/lipugi1tcNBw6PkbfgWIhvJxO6eIHINUoDGYVhouUtslOZQbwL7PUTKxY5Zhx60LYhRRn4L40NQ70Fpcl8C85hof4ELi6gKwcmMPGnU0QrwRiuzzcAssMQCiYUaSWPSgW83XgX8aNgX7BDDoX+NHfITQh4BaERjh0KKWmPBpkHTnGSjjQTojTg0GmEWKsqLqEiixCBiQnqJqGOlsYz5QWzpYby4HtAEtGVn3jAWHgzxPhVPfeK2U8S86E/UeU1Yv7Yef36/pEaJRcQjv4laehr7dd8zM/kTKRXsCJzqcyLdViFReTXOOHfyyStJr0ArNARMoA5rB9llWUUWb2IH7lOuSFIrWhtskQ7QcaB2FSxl/MKHaBVhdzJ65FsVlls9IYuKz7x+2o88+ajiSfCVljtDdzB8ObELrCDIF1OEf9LPBGWr+AI6Ucv69Hfxpyg+jHHeNCRSuhkl6Btb11/GX64umxICtSysMsPpiF04V1zC1pgFrRyZaL7vDLyEwSJcKoT9ZRrRhhNzMRXnvgVMTPpw1/YurKNY2rKS3WCmUWXl8h9dLxczEn8sFx0fSsECY+wBVWZIxVAwgRuINKc+QHdm5lDEte0GHLJFolsH4X8pDlFCsZwISoCkfuT3lTkcMmvcIbtwEIDK+HwnJyXCNQjL/A42xM0WFFPlE/TMV6gM6oYYle/pTgNbglJ2KOPPzkd2uTJpu3s8PaUYnlP6F2weeQGtDPW+WNBsMGKoYa0cG2e6gNlkhDjLdL8dvLEcPhFVDicePLIkGGqEz/iQAazSByV7VBCV2beHVMfG1cHULnqo802CSFSQqTvOXvFVZl8IU6ovFb1Y1zBy4qdZUffIrr+MpGBm9L6hpS8LArSNsuyi4RCbzV7tAG7cy++p9ghvvOnmEMvD3j7WhkTzsOgiVlCCI8bI73zbwOKdz2ICvqaeRmCANtkcLLGkBAoz/TEj3Upay2eeyPrxzT000+yXvZ8PMmomVLTf+lUwiC/lP5zj8E5z2X7oougDqEfB1HFJZzZzwlVU/XcIgofcH2SQo9xY3JWivXssH5c88DF7R0buQ/SEYNRGaCNC1XkWcbKHZzr7SnbnPZtiO8yixkVGGqdnS0dofyihmEWe2R/el1oYgy2l+cjWdukQGyjnFlH4a0+U3EozHof5VqkAcIkohCaSAvz0zJvnBPy6rjgvdXNahlCZwzWsXHl8GLHWWp0fdUfSmsHK3ZgKDeDk5p1DLxNvHeC5PNSNkojVBuWzdIAkky5y31KOfWGvbxPs/t5jzcfcJdSYHnITZ2CIs+VR9ynkgJ36zW99LqHbBWotvvti4Gs1/NR5KbtzgdZbxb7TJx6fvVZ1jVanQ/ty/dd96R9c9pvD7uyvlcmp3t2bpCu76+PM7r1g/Vhoql+mCOhZ5DBy7rG/32XsuvODcj64vYuz65kQxOiM3Zej6HD0phO8jF4tEaJbBgm9G8u3Rvk7MjLT7sYra+IoNP/cj3U+DbqjWNK+nuXn9rnPWJk55N7MXhvhEL1/ZPzq85HuV/f3T3WALQEKLgHzxuabQSDeAZ+7zVqGoneRRucxJ9eRzaaTcPebufj4OYiHz4yQMGC4VVfQ92tHe0f31ANBTgyuW779LQPntdr+/XG4d6+VlydbmhQpVW1g+bB7t4R2MgwOucfT3uf+G0Dc7uHewd7x+a1AyKhUg1t2TtsHh4d7BczLLfawf7h4cHeXuMYC4c3A5deGQbueffy/RDcPqZnAZfjM22zakLWArtw/zFsv8+qJte9U6LpIA9KXJdSQ9dFfjybcjouyxbmUMIoy2zV2aOrAiqBTYq4gq1Yu8bQlNDgqKc6pNPepJzAsP/w2KVlPqBwaOyEdLhfwHrWgxXljQJGFkVSlYI6yHte8mjV8tnDNdd5h4uao4epuRO1z0LclNyFk5hpKygq4KymS8XV5I0jJDTiwYv8MI3ZY8Z0S0BmxebshihfDpFazMBzxiIDzvHE0gRd5owCCsc6iJuyqlVyF4WPL+ckq3x4dUy6UI3VmB5TJ+FjQEi583BCYWQ5RyqF1ZySF5Gp3WIYnKlLUxk2FRiwzU2jWWW1vMIBS2kP/ckn9Xg88xdupIg/stSuFLscbpryhtmkdKmVKx/IwBGBo5HNrurySm+l8FMcUeXA1DVVQfm9ouR6oWSV/TqWfxGwKWn8L12eKkqWucEaLtIWpjxPIkqgcuaxhW+ygG2d+dB6icxsraTGOiW9K429TnWL+k/FOsvLNjhV0F5BUMT2pCqowD+JfMTL/LBCYbZRXPMQgoVVa5vx0sTHVCQZa3vL6Skb8F/xV1lJS+vvxw3/tVHzki9VksueTtyA8uxJIo/9NG5FQJQsF2rFtj4rdke0gG4ECwEaqB4DNVj5rR9TcpO9DDm6oDOnpALeo2TeRSoxmVCkVI5688gxFI/Awgv4QPiCYvtczcNoKch8OGQbh/MFP2KFDPl5jKjwxkSV0Finjj1oBPXlxC9NwjnJDz4UmaSK6BWkFE7npUdbVOgtCV6jgFQWmgByIBaI2Jw70RsZfDLyv2iZp6xArFp2YUZLWEHyIzMvldVAn7d8u7w5x7KM0WbFWu+glNztfKdmbJK502x1SUuqVUundRacU5EHTmdpfFexj7mqO/du/bH74M1SJcs4kiThDW8rBXNfjkpWS7ok2vGMInLxtfRWkF+gpVeC9SeC9feB0uPA6svAr6tMoH+meF6miFAAJs9gjnjwprsJg+Hg/pHYVSmzw/2LYqbNtDjOHynAaCMirjbzgjwxycTzPCK5Y2IUNqOxFqPKECmVQSzYHgwq1DeIPBfAdZ49Ky8axsm6Z74J8nijE87nsO6XPHVppTBLBU8B/FjBS8NjX0fhCLxamtdw5SHsGHBYgEBNhwXigiwUs4arDtu9MQe4JU4WydXTq3xVXCp9O+C4lP2CTnCXhRGX+Dv1A1qTlUBKM8+Ro4tLRA0EJlixhFas2tN27fCpKorLqcCxZIHW9rpK2nllhDylGJwDMH2LoMwjPfvh0tORlV9SZJGr1zPwSyobgUx2wmcud/FjYeaGy4B1ScX4UPIbjIqKx96CesW5uvynY3lG1G6N0ql0z9/obtXEkhU3dfTHUfh/YtnF+HkxPrbsN+75cRFUVSyN5/ZEo8pvMs5MBRUcYjsf1NMZ8COnGPFIFmYV8Q0P59hqoIxtVtl6PWUI7lqRnopjmxHbqk8IwkdJ4VOV/lRWzZQ88UTNEk9i1VsNiitnDG/1uU1bUl5w0gakN9eqtZW164AIOjyImvpP0hr2252ueLNdbU6Ftc0AjHfRK94YmrfLJBt96HizcTqjyImLV3E6pyvPo2/oRg7dgj6XKsFEXLVcSudvGqhO/p3QKYNVwRXvObREyc1UuMR2TnHIMdOGjec8sprKyKbXpSBpMbj/k6PNt0szwwXykg2bsJUmsyd3XTqOqLYbJ6ac5PGzEb17L5D2qJKyj7Xb0goU0jOgQ3hK98wZ393LmvPo+fxBGW+EN3XWw3YTqZcojj14vjwKL6mvGToO1GM+DVOsmLZT+nbCLqttaf0PcgV6axO6LK3+7stzuCi9o5YN1PAECyS5vcYTi07ARuU2ssEVhsCMs3NZSZl5ZNHhgo2a+rZTp/NWNrJURS3j5qrd8yZco/eJ1Jl7lTxPZUTf237oWU5tFToEZb+hpnYV2klgs71OMd8hZSyMY92IZjAcLfPPSiv1Ws3ejB0WWrTspjcvdCqrA8OF/e6whcgAkVqqNhYRHqAF+CRED//i1sioTgOmOyN78bV1+OuzJxGcH2T95YNYLIuvh61fn9uuNcFtU6VDYyNluMhuHSDENvHcTn6PYk9Qs3+QNTa3YqxuS82j52pyWuU4NnX9YJFS4KTv5BcCkMxGNy/3DTn/R/aysuDl8OBVP1PdSahiKplAW5HPGE/zyl51ke6aZ0FEFo8jnz/uetbR/Hu/YhXJSZe/IFP87aM2Vi7XfMts95vlmDd94/fSGcIUCrP0uxKVcpDWlQrrBeyCZv1BAH9HEa8kafqZR79HvdLQX+nLJU45kZ2mswyzUkplHWe6pNUiYxGsmfUFjMjZYH5X/GBu8u94+zNFzO+WE+g72Ct66rmFHzDxB98daZxfDfS53pqw8lPtAhm9S3+ZJWny69sXUVr5JICNo7xbG84G3tXPkGefObtKgrW9hl4Z2PPc+ullbmkLZwJaL1JQEKA/TsjdQq7xZSvWEWvJzniyZyz875ThiroV0hsDP0eJNMpdVSfuZO8sHIrWDvT/9Yaw3jQbr0fWjZVrWvmDRIPgM3kUuWVZW4lq3OKGOLBbzyDyIrQfy+CyAtvpsK+dBYczKyzMZnliZVN/OPjOJp7lieNnoBUjkyQqgaUv7xb+ZJ3TbfNUZq0oWfGRLl8hJtF69S04xwy/aubfSot8XfF1AD+UP0C/KRivImYlxwoEzLOduO6dVl/Zx2v3NBX9H1RWGd6sWWb3AAEvllbBCiBAnrJSsTpXF5bTtovX8eA0P1L4E6DHT9XZ0/vmGzEAsDdlpqyQiAR0HWNg8wImbZtjrRgxTnJX9WEOwX2ltNZulanN1sFgZtRYWVl8ksgfBheyIY3kRilQIQ2mL9f14s3v7KpaAlKWTshsf1GFfqx/APktKPwUSZD4yHeCESVY9sefjkj/n703/0tcyxbFfz9/Raq+H0+giEiYRKnUbQRUnFBw9vjNhyEgyiQBpz79/va319pzEtSqPv1u3/f63NsWSfaw9rT2mhdI6QCBjrutGRDnIOXlphhJM0k5GW1ccm9KXQl18qB7Hy2JZilyvIatVxDHglfS/bzVd+mq6Bom06iBTToyCaSJ1flkVTZr0YuUtYgSB2ZLN0F068vbDJbtrLm1CrqxVWb92Vn4c8LU64NiVZV7UN0WAVg3hc4BDi8BXCoh4NDCC2RQ/aHnTWOppB0sUFZe8CaW1sAC5Yg+QiXe7zRUYyuglolgeX+it+Dm1BZ8UylsJ9Pxd7qKmJtPN51KZgPyAPVbKheeEolRA7JeJq5gyoSf3b0lA07Q0OMCbkBQrOEPdy8hwVDSAHYXM64gYiNHeBy3yw+5LBgs9cVxmaItdB61wQRHEl92feYC7JVQJOnsQZCoqKO9lXrNxjjiNZSx4SakEhmFtgX74kihsr4DGHJzzaWiW8VOykwuLxYhkA/tIGVtJA8XvRzcRJ4qYkByD9ZIuLHAxUnTM3FyvWXMF2CGRrByDG8gC2zq4kxb5HGlzqg1Je+BA8bdBF/6BBeP0XcK1eHgB8eYA84MeS/ktlG0J5rogQ7hhnUKffaAn+NP1jg+YMrHw+phvXEFJgt4gTsO9Hmr2tVy/SGYlzHBtLbiVKpDRfHaOftQN6dqYPQjd0rmgp82cnnNvKlHiKouoQgWhEwYKlwPTg+ZLSi2mOqYHhVRg/GN61lu95ZvmYtS46h2tLNpHM+8VeVI0xNrfP37P74az2SzkrEg7SivSGgvbpllWQd0osCtoWSNKxCFT6BqOwF2FeDMC/aTYFkGbalycwZcWQz6I+WShlaKQRTGcEtpAzqmx5hTmVxGEGFmslx8FcAfwtUAyCz2+0eKypiE2yUhghZgMRk+9ArsFLYgvkbwUWv3IbJeivKUjSD7Y0Sz7hTxz4NV/leAtVyVEqqrOmcsYcbZWChTyxwzcJRSVybbjOvi0MncEEc1fHGEhhbSewq53S8qPBkcIe0jO2ZyGJ9VQRJiF1VZTuDWElP/r9CwRqhU3xvcB6pVMQRSqBi9n1nLvKTOMAo0iAYAiLj41ULdNZm3ZssAZ378HtbPxRV9I9VuLMaaswAqIblf6KQn/D2/hjZfSI8XrbNV3DXUfS4HQf5doQcCxE8Xswm5GFZXqanrrL8AQ57/IlufT4oCgQ5QoP8PdLXvWF/p+mBOUqn6a0m3au+pOEARQpa63QE1UjOYbxhz8zbuJpMHi/tUg3X7bNAlHDXXMcHopfMjmqgpUsIdb+6j+R5YrKCpPoOA+v/QV6B9XRUq4mSQt+L6UzzgSAWAmctnZKxUUcsoIW4bA4bqZLUIT7dMhClkTtyA11I1F+4u9h9/V7oZkKV/ViSI2i1hZsMhhtNgruCgUQYYpCa59I6rjUGfvnTMumo4MKd4Oiywy1dciy28iNyF76TYT1w2fKHMNps9TcvjVoPtisa06dKb/ZHaZI0lgq1R6iJkf2ulrCBsYcUiLrEmzjeZVwmdGjpnpiWWX9hzW6yCPE2LKVhFBWYPxwf6CjLc+OZX4wwLKetJph8KmpZmwgnePugeAaKoxRzDE8CjGbDfZC0n+Q9AR+guAQ45Xm9O7SjmHUrlHzS3ACnylsLy/vuFP1+FCZh7Y4Z4Jz0BrGY8Rb05onxFiqjh0q5gPBzw/nfnf+Gw4PefDgP6+3es/Tt+kCo+URO2iuD75MZuAnthcOxHPYbgLhkKqd4IAkKQGWiUDpVNTtbUbZMhMW4KXLAtjDvjs3/gxaTX8z3VC3jLMefey9xFpsYslsBVf95ijyEbBOaMEBCLEELcYUJ4sO6pV6pFRNzYiEO7pG+AEKfPCQAnRBsrnBYSDTFo+6Z0a7F/E6AcwN8Ipnkbj1v0eYuV2VLLwMigTLQVloTxOyrfyf84kD/wdfiC/Nrkfi3clhtVMCh2hT1Pr/NVbnVDxcJoKwRhVgBLzfFeGZL1xJVlSIuFAdJrV6nNd+cOP7IGxJ6gNYWVN5yD1dXxZBXZIsaEgVnmwKfBQVS3SAo8Ug+Cl5WzYfFZiH8KsYBJ7wz3It+zyJUpyEU4mVhBFPfeHuX2IMwygoZQAsHs+ycAitETQC09vEfdTDgaLU44ZoToPjrk4KERBFwq8Un7gJGpIt+SPXBTEzGMA6/15OlTRK08FtSbTD3h+pAIAzMgvCoVGYN8YzohpKd2IS0hEILnlnKWEWEmipQyUAd5aAF9q/RHOlS61y0935ngIY77o+1B4KHUCU7jUnIjJA4Lyf81NBXhZQ+RW5bhWbILMPqYERMmu2SzYQgx4YTJrxQaywv2I924Roz8hO1LIQj66MQxOg/sNXbbyCVGeUYIc9NDYOFrjHbgjTtk6udBvxMCB93zvoNRNxKR3a/a8bW1yC9FHJ4LVSkOB/mGfBdjcMDveHEeaWQWnPUlm1EXrAeWmMcmeSd+CoVBSnrAMtoPEk0SckvOjBU5co5n1HEoXh24FLG/wgY4jpoytPlXp4kOIED27VKbo4hlZ5ZHii5/KS6mm3jZcVO8BC0Kw7vkPcwlN5EJjwGZYLdUWokpO2N1Hr455Gpo9DvF6uRs0BB9oVMRxubLmaHPoHc6NZQXI40ZK11zBRoNzM4/ifOjJ9Q0qnQxDRpxxBCO58uGzxb/56bhp/Yz7tclG1RF6upyBEF4f0vSdZbb+KfXQvWU/BesCb2X6fyrl/LazAO5c2hN1NuYFkGEHDT6IycZjgZMKi3FLYIjr8dt0X3ooMLNKIwCCZu/0JmBDhXOhzYQGUEIRnUl8bqBCSsqb9nYGMC8R2DUm8c1Pket8aIHYp8ZsHlkrpiafNANTZWwxSC16WAalVrFsXMbRXVvEVBdfzqgdfjEaFUsd9tKZyVLCncUSokG81cMWiHszX0napL7qNWm5Q0or8xyyPOXdAZcmR+yZqxtsf9MMql+vEgA7vsWAxvPaWc8tx68V3e6mE0nPmmClLtJ3ULxG5v+k9685YP/uD4O+G8oaAqYM2MIR5fWxWBb5EjFOkPfIv8GPNt4JIChn1QCAt2QgiHl1L73usRn4ivdoUjgkFNqcml5WEopnQqMpvgI9VBaTlgO0rHlPiXvCRUZCwCVJIP3Y/G4YjmBm4OwNNxaiLCabilgHQ8cDxXKRrCiITIlwj+Fh8JiDbWGwOG/Qt9jUCEZRxNjMcW7FIbtgc6M8GLSXMZQukTfFtyOQaXU2ZQTmtA7KKZ0xXJv4A0xhA1jXC3G44ZoXFEQ2gmztkDXOMgAY8GEybj52yJBl/35nSMYZPx+Gy9yApJ+T0ihIHc8l/Sj9priDil4oPUtlerSyiPJFRZI9yb0AhB6MdpAfBM8TVw6HO/xW0T/8wn9LApGwK4ASa9ROfIbUW+TtXSLDGNw5Rp0I8h1k23y6wDlDcgemWRCo22swTzpi9M267ulmvm+BynyJtidobj3CPtv1DtNQ3DiBua7Nvmp/fm39+IaBfA5+TUMMSpgohUtWopisaA0yC3IYNA4/zMsF/y3nO36CZ4LgSqqHBMH/he5pn+K8focM0VFhpQ9I3+Kn2KulEoqoN+WAPLzrJjsIEL1LlaY+0qhsrQLG3Ilpu0ZcJaIZONk+/9T2bhPMWrKaegtpwBlvJN/N37NIq0zJ2o2DPMXkcpfw+dFYJfeJxk+HjvmX8ZhRK51NLPx09O3hCcJGh+wTt5h9uRh/qfYmZ/Y15y9oa0Nxq409uERN35mOkbdHKyHVFQhmgkLSgO4LhzLl0m8lnEUHeGkSHtUBqfFFwpuJwETbqUPvUaAz3CcTJoT8eQ52cUIBeQQa7aqWgU7zyvceS+D3iu+Tg4nz94sFtevijAVQqBuLkbCQYc2RFZyodEhiyGEJZ2ZKzPuvvG5ZaLaDIzHocbi4JcHfAU6QYbPwLuDhcYo0jjDwOyo9vM8YEe0uDtVjNZ4B2JMWQHpN/rQy/7iwj0eYQR7to/irEQb2mqmocrERU8Xvb5xb6seKsHbRGa5UDaiErpKbLRwaOvPwqBaaqpCYQ3L0A8rykWtRJSKMg6ImXWsouTqAO9yniGjRaO0MuNPiEhlhi67n+qtCdTipCeuB9rnp7r7VYl1JJ6k/dI5DS0ZbSy4IdVJj0YcwnqEyQXIgdfWi7Fn09mkD9jZ7Y3jH4VspXS+UHrLhqN6/KCzEOP+rsQxOJDlY3iX0EIzk+DJkLHagvhZ7yV6Z1n5LMEGzAtSdE7jFEhXP9pEIAaKyhZiCwlnGuTug01QxTT1yY0GKGKflyez2WI6x31uGQJxp15WXhhNDpkeeLgL5T03ZwyPmoIQsOeSZg2xALkQ5X7IRqgsHgwuJoquQCQ7hxCvk5mcBseh8wCXhKgn67C10rlsWfDzlaTfIVu68KxSG6sJTXoxFtOqIInuoO/5c7cHUXuWyQCwI6Vc/Ate2KHeqnzVaGEL/D8p589uda0RVUk6ZUHn8bsTVTq5mE6BGCjyMjTvS5JQM3QSkqQW/RYThTUzJvz2xQl0FjGMCn4wRgMfLXY25W5c8S3q04rRzfWGLPpPmDERLt2S6POnA7AtbnUYBrrDF5og1EQrApBi04KCvIE7YCwsJKSYezoAnynFc5nFmII4E2DgSYWdz3fe2HidLCgfpIjWLRaKSovOd0eDV0BsPewpytMYSRv9JInhvIs7B7479PqtzquTgkZ0htSkYmtRBo3IPmBReRqW4KyYwn5XkLs0pqclYFSWBmgjHsPKl2Kj+KZ56mGkQwxQqJr4wytZhYcgpOG35EwBgkNpWFdEjfmKXULBr/R+pzOw4DlqWJiQwHqMwTZB8xL/wwQI/0AZN3gfAFrUCpQgMiLa5rBZkdCCdY30Er1rzbpArHeNmAxTpsUyI2cFqowmPqb96Y8JckFZOuwzqGqxAGdkL9X+MEdM6D7DsNBy1xAecNAlCz+fkHOnSIuQr6bP+ew3jBNKSNr5ZEbfsVfTVp9Ji9K5fJE5ZqNlZD6Xy+QiCRig2WBfUIlExJ6Q0V+1O5YmrKrVTAthtiTElgTWUoC0BHSWAllQQxBUH9GNFngrIhcgbQG2iJoRjYm6NfL/M/JpBsZWcuPLoxoI+SaCMS98jwUWOGtCxNfDw9JR5WsgIuZA5A6QzUAnsFHAMssCnucOD0GLFiV8dKc1Bv6LgeBrOhd1M9MmkkaTHgdh5xicBlRxjJk9O29WNgT7HQU71FQN23oezPRwdzQWOVdg84ZpVSQHfHKMYLrJMSLH87DerPEsCEorsA4Qd7TzQEqLRSFV0DpzSk4EmdjDWrOeNKgBMc2bRYXAGtqEHafMu2N//56xxVtoAl6lC/IVgQhfrRchdCdTGpGPYGe7RahwLFk+pFGNoUgiJSqLV6IWvK1vbzd5ETu6jK0VSkcXSstCF6mIIuQlFAiJjOEbDMutHFSPsIji06LRYDSY5txFU09K7fgxgoMGOP8Wuaon+CvC50jvJAycDgAOAmZ/aXHxTRuSMjQB1o/UpqQ20Yg2BIslCq8GnG95Y3xkSxpTIZXToDYW9gr+/GRCFyBjdPlcqJulKEazVTuFAk0IYy2gEi8LRWwb0XNKmyAgnFHiocxCEbtXCnNoZGE5yqCRchBeSzT2/XsIsD8FWOyjOhQ5f/xUkWHD8bML8jCws4u9NXdr26dOuqBucoEgfoBALJI4B2Qk6XNyZ6LBOSIYlCu0FJQOwWVFmATF38ZcwhXksxGdXjAMKDvNZxmDxaUMPCCxSvIt7xJ3ES6Q6PlboTgZdpHKXfizgAm6gpLiarF0dLk0LYimB3z78YmPnuvUJhb+U5SGVdc4AQ5xRFGyBxQaM3BGREV5H8ejtiAbnoWNLyuBA7PW6d6L3k5/Bi/CeOQwMG9DuA+KilWiOQIVYLgqMpb5hFpJZS23Fi8+T2bdkDlJzfwm+dG1tWycOrwUIVgidOrITkNm8tAg0BLYcBBY3oAFX2V7CSerk/v68NhNZynnU9mUHguD1iX7OOiJGvBptaNijvErStuOrMv47ypOAKldOLhZhCBPOUzdAVVogNph6M3RHgoEY8qxUqBnBG7E+aCz/e4eVE7Y+ztRPYpSDY8965Iz+o55wIPSDWMzpTW6FMQOiykQUuksRWkxURRTREqsJuJCMUccCK1GSDkl/gdkjmpUmg0L/6bpPxlBXqpUHmSigjg4SvMUZdHo8PO7meeJijTKNYoKDBskOGmdpvS5Y13qZXs7SLxxWy+yA3L6c9rJZfQ3GSdt80VMaelX+HbsjKgdjVbN0pvVHzO3N6lNMae3m7T5hLPcQI30ofARhTjz9imylC/y5opee6bhVhffe2bP6j6waOrjsfs0gSydQy/g40/Vsu9uDtJwcFuoiywKY8Rssj62ZaRh/TLJqL0Q2AfouiAdKgWTwk0vZDYUinhAn8KTBM1YLDPG4gdgTBoXIF3hPEZwN1ncCZZA5U9QAAO8gZ1fxTYvGs2GQBAxiEMwGSmA0FGAmZrP3GcGNDULHDUCD1RPJzUTlBqDMrAeaMaH/mneHLIijVfFF1wMiAKsgI4NgAjoGcQf5F9RGnMWxC4a1SOark6FpnpO0LG2HMqpAVgdW39OO9kN/U2GkZPsDbToFFJqmeqRk1efKzUnW6TCMZfsdkcrSlF6YCqQoNT70GRWYls76c3lJ0t2yVDs+waiMLiAiGHXVE6TIkKMxA1YX588/TGEG/5JyAFz6MI5Fdzf07lcPF6UL378UJDJp+ajUtNNZTuzV8jy6/Zm3mMwOtqiNSMX4+ApcB3ik5q4BS/q0FtI51IEqfLLnDtH6irZb7x9mQeWmkNdnpYOoBWXNFOrVBvF8WQ2os1kUzScDW31RyZD91U6r+6lVtuPiSqrvHD8hx2KT1JhVp4GmwYDpsFYSaZ7h7tvcHAfF4CnePZHVEVCy+AWDgYMWLpLyiZVt3XR2gBFP+ALz4GwBFwRZjX8k1ggkNsoQcGEKnxXRBmb00TY5NY+bYIEVrFA/FSkKzViFDKrk95cDUNGhgFW1IrlxQdquqA+J9xAFA0n05x8wgTdDRo3RPTx6bbKwbagiIhQ8MVxo9QW4OmpzL8W/W9p4jUzwB1EWx5oeaGCkZq3lORrLGCGDPUhEtaVaN4NWpJlGhL6iWBmHlPJMObO1YxiS6JiODfZ9EZmI1+w0xu3mDGqfnpMCpRTkIwpnd/IZNIF9b0t3mfS6vuMeJ+l0jMuXWNZpDLprBTJgQTIkSI6KitzSIOaXMzJ5KWoREiOIHhPWJ7E3zKJmZPPFgM4jWezWs/li0GU5KR5ViyaQdd93ExZ7nTTzltuYzOTttzOZrZguc3NPOHzSuXNAvlcqmxukO+zTTtNPvmbdjb7j+JWvX56UC+RNpllEEBTPSXUq4wb5dzcsLkic2Lxn6RlswLZXcxbC75n8tlMLp2yaHotu5AmPZ/ST9n1jVQ2hZ+yhXTGzpP+z+inXCGbSRWw1fx6PpXbILAf3d5ql4TXAwm2dKSgab3VO0EuJSF2N/Is9/efS0pl8qQUmfAPSqVJKbLE75dKFwQLxXKAc8gxHJN0g2BOsjgUBYnNUm7WoW9/t79/z34hMz/LuOmc8tJO2/x1Xnud5q/XtdeZLwrTARIT0ocwrcPGw/Gd2Zd1rZyw1bID6DJUQyudDln3cQB+qfP0T3WeDdxrq7ZOcEAQGiXYb5DooPOo+JvSvRcvDny3kC6IZYnBYv1J/hQwtIhu4oslN+XaM/IjsCHYXiiOWi8uRJ9S1jAHC4ugQrA25+/2polYtJDbTRUgvyivQ9OLsm9H5JuVlkXt/PKi5Ns/YIAxCRDBENW4zM7JOg9Mp8mwefXSDM9rz2uBTtMPTip/79y4PS12M+lRhHeKXJ34Jq+bIJVL27dBqoV//nm9e9CcnVya3iymxILCQSRD7YV13svJh0+rnH8RqkB7wuidmjOSuRR+bsxZrgwuULUKFTnjfjNWaSZgQmo+tYaoWqPcZLOyDxEWydZxxdJAcDmpVzSLg24qijpXr2Vycrr2B4Vsse1Igz9+pLN/xkil3yF5w1nt6DSdJZi4oMumRq1Q3Go2RNIeHR5pnOWoJoU/AScp9QlASanMB6UymvQWKoDF32QxcOD3jx92Hjgpiz4UxG/4oVODABEv7rAmyLWazmWtXPq9ojYWtdfTVjpF7lY7r+/VqEQ9D+PJ89ion9XMEGdA2krEaAcCWpv/wPVCzlC/s6Wf/jt2mWpKFe5cQz35aHjMlky1i28jbR6OaZZeELEo4Vl5BE7FfIM23170kyazNfDRVBK1+5CwVTVAiDY1QxcdVlc414hKzKVGPBfRWYqVZ9Fl9AJ3KGdl7YVgWlXrr4Q+BwUYrMB3tdFNrQel1LLK6W9adboFYkqJBAwy/U0ZRWBzhWqsqi3GQzVhy7CIZp771O363cFEyh2fJsM5JMr+yBzUrNMmgCM6r1SalVrd4BwSMkXomuzxZNCZtBngZsBDjSWkk+j2Y64GXTx5pOvZYoxp/+aTKaBRMDrhfE44NW4+U8hKjqcsCUct+Su4AXLWF/dkZAw6WSryuxLnVy2KLyKTbqqFtC/LLMU/de65vBlWXs6x4v3nhNZCrhHa8IT5zaiFodZtGrMZwW6Wdoo0AzG+q1UIyfUZ/tPOEVbH3sjdRiQJzuY2Musb6VCS4PWNDOQlLlaCNfK2nSts2Gn5hdbI5zK5TJqgpKi0wtkAv0pYkY18hnBSfw2/yvjhAL+azRar22dgAl7Rus6QMRQKBfaxQmawUr84ApaSDOforHQgoiQATxtqIpHOflwV1PHrZIqwVvOqWa4fyf5t204xkDkzDVMd4qZhlnK5LJnrEDdtB7jpBnDTHeSmm8hNz5Cb9gk3vZxjxqTU9fNqo0HadBkKInurXiuTRm9MO1k4Ny3yzwb8QyqZtzqPnbLApo302UfWmG8NS+4Gy72jn/gz+bmRy64D2+0+MzY8ba9DK/gznVsHXn/APxUyZBY0Dv2efcoS1juXjmLeyc4lbLryKUX4/hr4MFcgIhUrxCrBGUinUrn1PLDxlPlnz/iTsNXZrGWWS+XdqnvcqJu8DP2AP/Nk5GlepnR8LMrQD/Azs7FuF7JCiMCf8QSmC2kCLQOxJkGkXygcZIKysKIP/BMeW0ueVAZ9Jktmk0xhysKfBTLXZDe83N4WtQAg1QpD7KWD2s4RnDMa4JrHkgQ6BB2+bSPGwo0A/fzgvcZpqlfMK0Sjf0M01dZQC1lBGEot6Aq4wUAsBiwXpoK3SCspDMUK9L07A7tljLR2k9lM3UIHLHktAY9Cpdp9QrZtCkZrbkwnPqrPDDsPtBc9qFsH+ylyhEunJVRKE6QK6nyVwMaOY4Skln07WAYpVFslPWQJEXS8rNMTGvcPw4aWnJvU7bf1kLJ/IJX9YMDAS8cj9P78283gNgS5nU0MIo20lEpfhDmAEQgAy99uaRejFkwD7ZfDK7fNY4gbyi6ZeUAeK0aWz54xFgk7Hzy0uZUrSOCfofkwehe0RiOI+crMmUlT3lOLsHiU8+uAoXIHpMb+hAX+UYJdMuWmDI9D9cLaThSKTkwoSggwnjQULTwVzf6Es8DqBLD4w2MyyDdvNmEh1l/R7Ji6c7YU408yHqotSXJ9J+0Ei2N9Qv+3ZhgNAcFpe9Tx2+uq3AJs7gxsk0+cjjDInzgpG3/VSdlYdlKAFTR5HmmBYCLmly4zTpMEHP4j692fGL/TymQMsSC8OYD3JmNvpgu3cQZ3LgR3Ls4+4F/CCBZ+t3Oclcd3UayBYed0kpHvMEmzsoGGkFvIWIey3p8hPZiK8MOyy9BQ9KmePvRdFvyawQrDzoRmKgO5F0VRnC/C+2/8vq6+Tjgx9iH9uw3CZz6VSpmw0G3mPQ38CEnmUkCWrmVr2nYJOgBTuGBEd3wKE2AJO50F2c0TmJqk+LDs3O82f2mLvZGSL9OO2tWPHxk7sNVZgyFJMW800oyLN86XKhMqpC5kUKqsbM+g/CMVnvR3JMglx0QWZLWSuqiY2rILOae6bVgWcb6MspC+ujjNGUcv62SKNHMNOfEzL2KxQZisSZJTmwy6JoHuBAXESgtCRszAJwUsW60RVd4oMaEzVEqTMlaWP55lLyq7ppXjz8e1cn31PGPS1cqo3eGnSta08oHCq6k0E1Urs2aZCyax4ry8shHEcMNJwaDXTfE94ZgATXC93deVmChjaTMeD2+FTwm9i0tPI7ZBRgU4z23P6RlCPU5oSEopcgMognFz69SUknGtLLnL3M50IZtNLW2WFdUabtK8SGWy1mYgaaFarALuGoFC2DZpD207XLC46CqDy4TA0IvqJ1wvMJw8Ky2lg0giVFgD1c6nDnffzNvw8VdLpbOBUh8fY3VAygdIT5O2slbOyt8uVWboVRwnrwFTHbW9LrhqHTeRndFQfDa0qQhGbnU77tPM6+E0ZUFsm9G2E/+udXPeAK0B4Sna3PicEUlKj+3hQwZUDmgWIPB9VkffWiF9nx7sZ8AvCzIbELITbBhmT143MKR8aEh5ck4mINpzfcIrjSiNlP89U1QbL2MJo4kl0HMTsB0kVkIkllnLonasgamGjNjZUfPsGPTX1UrctDKbZm2MDJX5jxutr1sZFS+oa1IgpPJKoWcBgmw8v6Pm0EyhApjqhVKGzL0+SSirkGN5gKxRZSXZb+Nl+h0hHRWR7RQvebe0G3CK/4T6RAWUUYAElwVXJh23QtxT/LYI9oXzGeR90jxGh9Y3bCeuFBC/btKbBbkRlPiAmGIrFqOZykXyc1FP8y/GYIGnr1NPS2RFW5A1fk1kySt9QlgtIFLeOsrvKL9l5bOW92aZTCkiFCKYOVNhdwcmgqAMLgrnMFN2iQmjDCqNQhkESqS+SoAap2W3fHR64NIu60fbUoxWKKTX80VR4vK4QkthAXRvE98qjer2Lv166GS+f09v6N8O1W/r+rcD9VuuqEO0XW+Uq+gmlw58IdBUj/ALEJx9EJM4EXX5tz+diOpL1uWLg9O0Ga6qzsGSyo5DpzuitjpLf0bOz5+RM6MbX0p7/+jFs1jHIl4M3xzkPejgMNbhnCdFVvezjjx+IoaHABAFTSDABkunXwlQQV1wyRdnNBjHRHOs11UZ06E4iwyLJKNt0KYiY264zYP6RXR0k4SM0CBACcePwEBH8e+ixHuxE3i+bCxsqSm0/aTBsp9iri4fTDhFkxbtI9A3jdgxu9kU5W6DsP1rY1uoMRAUnU2Ta21QfcM2hKqEqQWUMOkotQoV/QbVKoVcIZXKZiPUKrn8Rj6bTxVCahU7tZHJENy1XLuzsZG6DepXSOf5dTsV1K9kA/qVdCGoX0lH6Ve41iWgX9FVGIVCkdzELqMBEPHm7XQ+lyXAh1UbVFeQSb8rlrbzTNMiRgXtpQoReh1ZLpHl34/PGsd18u9+9Sql6HKwUC4dVYh5SmbD3+zPNGALV8vwt3SogXxUIdpAKvwp85n6GVY/YgDZz9TPsvoR8Oc+Uz/H6tvpX9WufU61Zm8Uea/kFLiXp023VG2mc3kAwrXJkXznc9rJRH220wX47FCz1kr13N0623aP6g5TFqXYPpZfyLnZIl1B0koRINdJp8j+2zkml1jztFE6ltt9PZche0b5AocHVHNo+E+mXFyB9ePTWv3IllU37A2VekFiQE6PbMN+Vz3HUYyl6Ny4ei6dBkNX/RNXz2WyqaxN1XOZbAEaEOo55VNQPSc/ZdOpwjoY0Jb2uOrOJv+XjVTdSa1cppAtoFbujX0qrKcLG3lNYXcWobBjnT0EdXCFtJ1JZTPpjxRlH0hLwUoE9TkZGhDEdiEMoaPwP3g68oWQAAXIHKUK8EasrUhRa9pWRNPvylPDoj1NfEZuLEUklt4GK0sp/4IXGS6vipYTBERXpIoIhgzhy1d0y8t/zr5yqcGILuTBAkaFZ+U4hPCfVWrv8VeJQGwrHY4Wrsk87GiZR/pwi8ZhDolsgg2koxvILmlAEwqlcmUDjWRB9DHuK0XFDs3SHZqO3qEb6WJQGiN2aDq8Q9l7dZNmf/zI/r6uTmGgUGCGSpUy9VYkYEO4AFhC3xv74EimC3G2DvbTQh9sfmxCu8QxixtYp36GEf7IaouwIG4Eg6oH8BdmPXhYPpRkLDP31Aiq5faeWrFENr5cmAEtoA1kHJIn/DeILpYqlcMJvKE4qK8pf8DSONAFE691TZt4/R05A/H4IxfmatxSSmUF+hb1fb5RJVlBAtJa9g2pHsieF/ndfqeu/UHd9Dt10x/UzbxTN/NB3ew7dbMf1M29U5dRiPHbG7E6t9ESRfK/+I8fNOujon/9KdMSkTvkRqD/8I5Stjk1xMjHtSupNX6NxaYsQ/lywhHP/BRa4b3G4xGqWfbmo0YFLRvVLKbA/WQD6agGxGyC+4a78FlGELfT6tx5zs1thAI78k7WVcysOAZJwpY224ueO54sc4eVlHQczLGLtFISjBkIs8/qOhHFgfAO8fG09k1KEnBRqYtD4xIzwIxVtXQXMiu9eCVbp+KaQY90PNacTqWAWgmxhwZT1DN/xhJWirgSkKTCw4jLwkQCxFqQ1BUC36i5x0JZjtlYJj7sbm/8FAO67rReP3BPq83T2tGOGZeBq5gULVBCONxjyDyam4PDiIHVcaSC5pMiPCiF4VUiFlhngeJFsgs7ZPsMl5WP4n708MS8t9+D7euMFAvXr/b3u97DUv7JSfFM5NVGo97YNP7+D5qC5xmskyDxLri7QyJQLfPkAvTLBgCUIsvD04lPX3EpYea9lwGNmKjXw4hwZHMljdOJmrrSYuvPQzVimihMDP7HmJQEKgMTnaJBD8rbLBSDrq7S0PwyYak5ntCdaSa/JiEjSEuh8cOuSyCKxHuyiV40VQJ2zJbIIuRd/eFhijojgUCZitf1e81pDtnpsEN2MVBA1/wGS2vyvswH8r79kHyP2i9DfEddvmdnU4VcKl+IkO+l8vkMqZMKyveyKbuwniG0eDg5NXenLeSD5tPwPp3aSAfdff/F4r2fENWBBa69kSb96TJBnUP+jxjv/20xXjr1rxbjLfFOD8rn8EBt2Ov5dwVo/Axbdiqdydope10I0MSzPNFMgAaS+FzGBjdz+Jm3yW8hQEuR4kxwlcqRlsHb/T70ab2Avv1SgBZV7WMB2nomm8+ksrTFVC6V4wK0aBBRgCYwnWXbdi5PWgT3/J8QoC0RUAlubz9cOiAzokVv3J5lbh1UzUDzn2H6/8N3/ofv/A/f+W/Fd/43yOEy/8/L4TSil+dsi0kiOIL2PQjourPL9dCZDdC1pLO57M/dAAdhuDIfwnUYgOsd/fjGz4FzqIJT/og3OArAkfs8r8Cv08/zCnYulYZa/x7cwPI4PMtNFdbzmfWN1EZuw8rm8mlCahTWb9/hfNYzebDXD3IW+ex6LvcfzuI/nMXPchaSN1iuoP+Ayf0kf8CPKucP1lOEHy5Q3Xsqmydfpc77HSZAjWBFqPoMerJSpNDcj3BktTM5wn8wiv8d2h2YgY1cNpcPMwPRZP3D8k+DpSyESSbLrR2dVhtHpYO0+T9RtR5wVfoALGUAhOBx7ALrI/YpSH9f//6dVCPEJ/n7C1r+o5/R4ZcJOfR5N55/3smEcXHmxWB1e/ArTNz/eYqx/B+K8T8c9H846P+jHPQvM87FT/K37zDNGuHPE1XHJB8QIv+ZBVU5b0BhM8AKrC+ng9PpAqF8NwqZaOKX3LA/jf4FLD9lyUWK/6W3gMrJacGJVP4pOjRRJv3PBiaKCjyT/r8iXJHIB6bHHVInWbdgD05+8xPTv9r8zArQNYhttfxBBz3iUKVNtgKEXw/AZGFeTBkLGrIV0V7ZUpZbYwhD4A/aQ0xmMCK1qIK1ZYwGL4MxT8z2r1xzXR3577f4yi2p1PtZswXacmgWI+wb5IYJb7dlG45Kk0K7TgiZPth6zcz/kzvv33TDBdYuvAuCqx0hwIveCpn/bIX/sVshs3wrZKK3Qjm0C8qf2ADl/2yAf7cNUI5c+3Lkskup8OZXyGHEcrIShpGw+OO5L4JDgc3bpDeHLJsiMqUMf2hhYFQaGGl+Z5hPadMYjMDPuDcYEr79KyO2z9OUsnbSmax81azuHFaPTp3sbzDpAOL2YDaCrmrQBtKvFjQENKzYj1COLHmPFaX9JXF7kF0BG5QwF4qkoDkpJ6OyGmM9X0R08l5ao8GYjrgLYY/hSYscNZkN+gNI+AMhIPXufUMJ7akNQklZSyD7+pT+atRPS2rwWNqABmIDaXiIO0uOjqe1RwbWxlW0DG+AaVOXdGzEnuy4IeOJnqeDn9NxQarDTGMEmORw8gzu40lIRUo2XcxcNS2TCVhoVr+pN47xRbHcepwc5Z4mJMDGHPeCSxxwG+or2wtkp2BVLrUqzc9VutIr0VvvUzWvI2pmPlWzpdUsf65SW6+ErOs7FYMR6kiJQccBsRFLsG7H40VARt5DLBXyF6alHRHVFClPevLwJCqwRO0eFRQxDtmmRB9J/YAHmj1fvhBikGFHZhYvg2ES7JXlv900VrrmCr7hXCw23PT6IEfkKOICzGIhEZy4s1qEbcAiIEtoIXy0eXZFEdab4IzBaDBsoZ0vlO9wNxcof7BNjw0k3oorIRT1qwJkxZjTENGWCw7ejlsKMi8ol4Y/FJWj0zr8YZhd1BS/6IfBuDNcdD3ICIP2mv5i5CiWA9waExr+wvM50sSMLpnO/hiGH8sqcT4m01cXzrQL4lmoJoW18ERjjlBxDSZUU6eaIgO8y3G8eHuzAHetuURltCY0R4gIJSyJ2laM92iJ+VBD3PvTIZlhXC2e4xpegJv8+xDeDTp3tDa5iUSlr9QTXsmLBkFJMKI3hNfHMH8ssApAAmmYR60BvqYp4wDrP4BgdjAORO3je2zSvie7x4ix7M8GRovik4Bx/br3C3/OQy3OO3fJOE4NZiCBdUnCHypAYq/pNhETdLMpBnSrbCP5XXzeZN9Rj+GI18HN5pZ4R4F3kSlIXHI/zNiuV5OOOCZp2ki9rKRyL0gb4u/Ci7mCHv4CPBbcBbdZaAcLAJRczgR005AfeKt6+aAVzUyTz4280WT26kICPLqPcEvFN78GL16ymYYDnxCjKMtrw8rDKtL6Bq0fh8y3GKOxA2oKmsAeNoGa+A93g+nr66+4Bnxl03szApN5kJHepG8BXYlnQD+UwAF1IkO5UrlJMT2vm7r97ohp/S7f24pKL4QQKN7ij4zCVZO6yjMpSkGGV05LHmw3KaaMqYcv/j4qDqBWOkQLo4uKz3Saxoxb4KkD0dh+MhqR6tMZpDOeD2je4ZaOU5JLcTTSLQJRx3kiDDEOlghDVJJF6ZFBUS4Gget6HULZx9ySkmgscDKE+y1GPomJBqylvbLqQs4dogDpzBLa2d2tolkHxD4pFJu7JbDpqtR2qs1TfIfRgrcEG0UxNps7nXwVmTf8ZbMmOB+cXN+5YUiFPMxepxPChkFaUnwz7Ln+XYvAAugj8MqlIU8cKThHBokC5UI0f49dQvgeDrblsbAmLr32VZ0eebY4SBZji0BzAQyX+gxBllBtaQWB1hMUu6W67JgSWYW4hp+wzy+ODtM7JExgnikFRZDXi6BiAiiLD0dMEI19G+HJIfAprxL/YeffAYVfTp3JYowBYWKj1oth5+NJ48xfMPYcM4J2CbeOFx1Bg+MHcnipSgMOHGFAR0lzJdx3XF9R9p6uJcG0vjsAhpa9DuRzpSwdRjbzXuYCUHEvS46SXJIKeZScE6QQixeVuFrBBT20emIdWe7i1mwMni6L8QJGLSBVGgmCCzXxB+bydliLMimOtiCyYPw7FIkK1DPG0G04oBnL4M4HDXvDMmgMHFykVmcOMRDFmzhBIyqsoV7jqucOnW6N2OKVlTqSWmWTxJeVO/mJidB3qVjzZZOKy//ezIaSLgYKhDz7aWM/MMFbprCxngcWl778LmPKw0vo7wca/oTSYzYX/nTQGUwW/rJ51ydZ2d2j1mvbc6dAs7nqFPKdri2FssdrPYNiaKM7IOy+SIYEtx9uBIi87bPA1YOx76HsAsVbKmlpGV0QyCSFiIDDj6SqKTYqhLMK7Qx6dsDkhR+dEOkVQtU/HF4J3fWiC33nZRJK//rS0Rlj6D+6lVXeSnDN1brfsa5++8Fqq2UStEzwQvyhAhc+leXWGDYgij2CayV2vNGGHECt2WssfKmtdC1DzDA88P4QMdGTGz1yi1ezFBADcbjUAa46EbMQnDV16W/U2pufmKvbL45b+xb9LWruWMZ12NV8pgh1HNzx0CGeNGHQAwHkIcQ6bOgeddAkOx4oaow5l2QUfmjG9Lkh1A05L/JGlMXjzMQgNAgdpWtzldIm6DYRaDKxdGKXzOVmKDCG2oJkc1tPXvDe5Kde8vsl7eJskjrhixNxhnptWiIVLxTjjdH47SyAGyS66hpmcFrIeJZiPAFekmEXGicwFohyxwshHR1xW8l66ntdoMgAVthBIWZi4i1eRmtFzFvAQEwpDNOsiBFEg7JTmtGOXtnAta/S2PY4uWYReSKXn1+M1E8mhlBV/KrUBXZS+MLTk+nALElhJoCZUPrK63KWCdQkXQVB+ZpUQpzF2az1qvqkF8WO0iWE5d1qeb95dkileQKWHkZw7IvIobyzcFpkrx8lp9qM6E5dsWTEainnhX8Qs0fJkuA6ygbIgpawSNSCsuOBawk7j2CuTyxn1O5uQxpz0alcXBovM4KrEUfGj29GHhfCgCwV2aqHx4//Er+jwMgJLSn302V+vnE6o9iBcNZcdEXDnsM2QAQDJ4MFHJZ765wyPkomMwgcq22+kOw5qbtjfEfp6Peo79WjiibUUYnFEJOkfANTNbqNQ5tYED/6lFDh1G2Qwtcajf8IpxIIF/ri2BF35jZmE1zpypOLEiXCng27SASSNlJJwsB3BZH4X5Tz0lvXr0Ke7lItwoNtKN8DVqBjsk+1KmwqNaZNmJxpc84R3fvT+8VRK0lx1Mib9T0y4feE6AJKSe8+yBzouCcY5kLKJjS8FUjdo7Otq7aVslbtQBofj3C5jlbwZrBq3xbh+nSjPoZCohK6JhaDkskoySOhSkRT0QUs/BQl8VdqRny25FfUMjjiJ4aZxSd2726Kh4TSpiBJ5FxgYAbGD1LOJJaS/YS4TaWQPkup2wB3yckuP5T9XdcahURgbLm+GuY5CwRnmwFRiyWPFbswuwOWk6hN7RD0RH5J42tRilgdCcoSKaIQEwX0O4GsuhrYQclivBjA4VyGFsDi/DV3j9Bi4gtAFOpIW0BltkkbEfI2KWpbevXoOAZOlStPlbzSZA8cO4i24wGYhj1BACgG9+JCl/XE0EEKJq8v8r/WYjh3J4v5dDFHK1cm1AeTCFqV3GUVUo+QyC1e3qDlqVhZCJgAv6JEV95NspmEuSqFokCk036A8FLsEuA/Ro+3MMoLodTObW4d0OP5twiDkzSOeWZlVAVRSxeuUQcQPoN11S2gflm2C6TunrBUVD+TbA/GEP+ZDcRSm0GUsbr0go5b7h5V/HMqRqusU/BjCGgzdPV9GH3ryEoBeFPwHwVYwMtBCO+sKPqrZwXgiP8kDcx3vqJIDrS4qe9ojaEL83IRJyJI1ioUrZrWVHHpxDPqLME3OloN6uI/gVXTH2PV9qu0D1KMWsA2TFJ8sQE5Yz5GQ4Lok7IYmc4ZPDIrmb8eBQcGvQQDc8Sa/hci1qUmEyHTDd7ul2WWFsxuSog1aW66TeM8bdCeUZG2GHMthfGVNflVEf4vmMw/GwcZCzsSOighNBSN2i23HC+GkAe5tLR3kdYLW0Wyv3zutIRXYOBK1ArIGzHqmmRFVe2Ozv0U//qrcABifH0EX4JDCImfMWgsjS3GRDBj2gpfvhgg6PRL3IB0df2Z5zHlne91JoRn4J9BcUN1B/ga20oSjB6EKMgqhrMLRE7yl8hZXjIa+L4G3//1Qwpwt4GXywYnt8GX4L4IDakqPkmjjajxFJaOJ1Dq/WFJSJbw6+r18z+C8FJIop+nwP4i8gdfo6LwJwgb3f5ONiGTmwmN/qpGRU385LQ1v0uifQ3hjWLKdBDOxxJN/f6/ZKpx5ujRrJZP6w20VF5VZYSS2AwYwcJ/UcaYUQRRtNzq+xb5r2a+ezEt+8iun18UQP3c8n5iiXlT2ipH2LalApdUyBIGpHxRhJumsNQg+xnKtvh/LWX63o5k5sGdWcfx/CmcOZf8zqS5SDz+bnWz1TZ1NiOwi8kOJs3FkTrWCL2lJHE9YHPrRBOKaJGuQ6xodFnA2HKjnEkbrWF/QmC7G0GUXiSIUejZrOyrlC7aAva9MSRDJBgjOX1VrK/J7JB3Lb8zGCRlZ1Yq/ns2vZHdyEOC+d/YUSDff0/b2fVsIZPPFrgihLz9/wNl1cyj5HPCVp0+PscKmMzjM2DFApnEA/ayhNXV7OotOjbmwY5LS8XIKFJvDSHvN+Yo4pn6+I3ZmnMXErUdmLy212lB1uEJ5r5iGZdGLP5tpzVttQcE9YI1mEhavMoMUqmMmpnmdaijgIgTS/VneF7Y+GKgGjD+ZuSzD1tgMAd8DtXw+XLRdE4F/T6LF8fuce0Igp6Utg6qFSedKRSrl6fVo0q1wjW1zdPGWfnU3T48dSgW3t0yE+aW+a0Af2n0IRrGBG0XfpH3yXyK72GOKmSIwlbr54VRDLFNXbIQFL0EpoEW6Awf3O7siVd/VB+66kPHV5/utG+kF+VpNIBgteKR4SiqbQ5YRn/MzIFmw5HGX8y06C/hGIqyMqEMCIxeN1T9v5HQC/OfwcncJD+j5ga20Hwy88SkB4znMul4Ub5CTwWcZ3LBtYYdXokwq3fDQTtJ1esx7WNyMYVMzrFAwwSeVdoSA0NtT61O/4lFzE2IsBVxD4IaMTV4gmKFG6myWlqI4AA0Sgp8r3ymkUqU8utfLAAlBC6lbD8gbP8CE7DU5wje+WSu0lopnXAYTJJbYJ1fq8d+Tiqo64MUAjB4VHufJMuKDGEKdRTa83c9b0pt+uNSTYYOarpGCiKvOMPWqN1tGf4mU0IqCkp1izINZXHWGv3V3QltW3SXUTpPfdghzSj8R0rMaVQmvTDZR9hwqEKAqg3Usjdvw30IJCbZvbU1HIpytToCktC3iOSnTQYBE3m25sz9gIxn3KVTC6cpn93fCpAlLWmgF6yMHovmHGhCSLLjGUABzl6TRnPRR3Opzh3B/dCIboQLenVyWsFxBHSn3FreT35diamDtsQQA0KE8GKo1bSiXD0seCS0qnHBVNGTRqHhNaBWFFPSgdb2SmCqVyOs2IrAloH1YrAsN1kMNpKQnYXgGFB/B2iOJqEUT6FFpzg+FWqC1fjY4k7v7zsYkeGvRLCvUC0e4IyW17U2In/pJ04XZ4RDZ0vM6cdr+c6QorpkJTBxaEszi0CSndeMmLvotviM86GojQKSUF3BWOPRm3sJYNAGS0Ka2lQ/JKeTadCLkv+HjJQKlWZOnbLc2jcGi8U3aXRD7zLhSgeSty7ql15CUf1q0FEDyBjvPlEIHRPn3ZNYjABN20ZRUoLARcem8AOQAxhdXYOfFFJ80FEgIQ6Z3vCWUy86neANm+vIdlgi7CnZix5at7TA0HVVsXQFte7QnzAzNermJywyzfDmUPClXMOIQxNaVbFurj8l7Kxj56PGGOT1HPd4E8u7mIhE6mMy6UQ2kc8m7HTENotsaGdZQ8sQqRMLInY5+FV9NKtRTYen4CdPZtRW/+dO4UcCsiIuu0t9C8Km90s2KzvSat3EpybEcfRbjLmI23qPEJCQC9U6d7OYPsQAAgsFIuQ10SE2dqNXvg2rYCJOmPBcLy5jA3UOkAkM1flQbIcD7J4OwFJROWlxiJzMpvjF2kMxLqppYgGBfAAvUm7lv9FQG6ZUdelIKFai9K6LwiSqo4g7Qwdnws04gUaDW+s3bUkjGvieyW+qfSfAiyCWya9Glo5wfvuscFo4mgW5NF06pqyB9CSHTRsbC75+/Luds8aQI5MFQcRDM/CGXd8BN+CY7lKGMC4X6gX9B+18XHieUREZbRrIMlUuZilSMQVMVtjmpnNdWZbJycKF07zwnVKaSc7CpTO3LLTgoMtBy2pGr+wj05qqsg01jiLXoZ5JqwPMiTXoShdmQ8tQv5izfQ1Zu1a6SaPGUmWx44CuwjIeEM2wNel6w//ifjTLYLEYyLrrJoac7FFTLKrOxcHmN1ftW9ASf3FS8XjIpsKfjMJCalrVuINDPZ6MV8GDhQ7GTxqncgyjFkYqQjELBiggdWXesf9SKAFddslAI4BpA1DLQOrblGXfbkaIPvUn5bZS4m00FmNIxRXwEaULglOvdYYAKUr65h23Uka2yk6allYhgDIjDylFpuFDej8ZjOkOHY6tO3FOyRM5oPFELHaHP75/zy6bPjv6ysHhG6kiO9s3yqG0ZKdLDmU8WCR0EkMlwsePGe0v3beqVPuWwZlwCKr4VpBP2piAt+s8eF0tDPFHKOobbYtgCb4E7NajbSmBLKMszQKqBT2ipa5XCpcGkSQravAPBo+sMprAIr200Eo3FCpMD18UVsBgLEQ1LGI08M13wI+KHfTeGDJ/+RhEOL9QeL8lo4kAOSLO33/7kDKBIWU+GlJm6ZCC0ZbeGU35Lx9NWQ6k/M4Yyu+AHxH36b0h5P/yIfCYyb/pj8uGEoZXhv7YBtoZsXCzWnZPr46r7nGjvrNVO206dlG8I7jntLTlZIqQpwTeUpyE2UoIWYploGdSh5bgpCpnJKOjeATjKonIHBE2BaKAsEnANy4qxcgvRIM8PhWXezLZKruv6IMb6Dno9EKlsWHHOAqdo7YiNEuSDsa7+RxuYXYzH01EuJQV7Ib7AoHVpgKQnCZ9RJQ/UeCFCd6uHVT5DOckqw48IeEuIL4LeKJAGPu7wZjMGNP1Bo15LHd6N+mRmcG/wBj1fcv17lDNQ74BM8h+jhcjUoy/8NkzubTG3ZdAzAbzu533d3cP4L9d/M+0GPMXgD0cKp+1hMlW4UBERV/YbpF5QZEMOnK2DEr6wLxy8g7otRV/MxizxdMJSrzp6XylwMcrvQ60EL64sTezt1+ctklaNSNgWPHRRHcwln3TMGcUAub1LBeW9cgWhAZKGN9sZK10NnMb3X534lFNN9AL1EiDkGutsXFJaDG/ZWCcRKNRa5bPxZ5KGp7L+nBWUtkXbfTsgz4HYk0Ze6Af8OUjV4yT+fZmk4/bC81Jqad4DMYAv+J6/By5mcL9BmEcg99Uajk0qQAQfuQCKAiDH/MY3/z6juZ5b1lZZgLW4+dFnAdWOnB2ReNCtkDPOYUNjQiD79CsWzaoYCemdQ83ES8CL44CHCeqtW8R81rUC/JIKKKhiHAoavl49CIoKI4fQHSNbImQAbAXVdyXlMhPHdEHvX9xBKARUNRBqIsYAbcdDfWGmCAAXEzsXCiYjIOf7LiDwVIVP81YBACWnCh1V4UG4s19hxp+pKyoZqJ2vMZCqduI81w4Q5uwo8UOolie4e3hqIXv6MdQKB0XpoLgZkTLB2ZgA95Arc1b4fQe0Q22T89rRw/W1hoOxZ53bpbC3mOBXuhdGzVphBmaTfpKW4pLqtoJIoUb+9ZxGAmTDFEvGuONOgJ2tqKOxXKIooL/8IAvgHjGE4PSRmKTtWRXGIZGRxLwn6ssHf7UZtWJmr/3gA/EgKEtfwnPDIUzbCqvDmcw7kxm4GUZHBW0aVDpAR5d7CQegaV4ECJMbOPOIeCxiLfEhqpv9eFk8rCYurQC2+Sz1rOjtsB2p4hl2Hq+gTJJEEK+xNyaYs8gTg9GuBBnIc7F1RQ81lBkIKjAFlTi4+mgjulQ8VxE9Qj7nRWyAicIKw7GeleI9EatLw5VvWL0Iy5jFOBoNULXjuJ7rUQy0q8d7d3H106oCf3aCbembtEdee2oBT9z7ajl37t2qBz+Z66d8Ig+6P3Xrx0NuJ+6djQAllw7oYGErx29mYiVibx21FrsRJKXdA/DD7qln6iRCt/T4AJPfzA2AtWC791B9BZSOwveQqJX5e7pR9093HRq6QD0uydq5sTdw9sK3T3MbxLvnpR29ygc8L8AE3GAFEzUNo93Kw3zfdzzPtKRo1mOdFjPWg0pGKZqPjRMVvh4GjkyqAv8HEOPhQP6wnhYyUSLCctQBGaI+ZFwwSFsvEUj7vOwmNwcfofaak1mgntsHtSODRSbzn0u5/e92aA1NKAdZi1+RfUFkzFE2hwO1VqAbloGiOItyv37jJeFaxNuTvJlskCzL84tYqg3Hi6+4vnknFB+tvU0GcAPiCk8foUQQ0OMKEcB4jGILBYYGe3gJ33aDOQqwChCw8mz4b/6c3Iahal72TFlDGTonR4DHGV7AQoaZJWLW1AOYiMpgNIvJcc8JQPpgvWp8dwazKElzO1FJwHKTFuzOWnRpa/AIH0wdj2/05p6jruFC0DNodyy4gVF23JwsgfjC/qIQUnItqbXDH6jg4f9yquQPYsKAfasZ/CRtR13e5MVcQnEjslYdDSMUiEGqw+3hG2aHRoPzSzq+yhWspSmCJDB66C0on5XBDNaK3SWQYeGlwrMHiJsCXXc2vVetsGnYT4nW1r5oBBdIjmb+BqSWmFAknYco4LON9sO0/e3b0Ma/qi5iDS4aZMZPdgMLvZ2tF1TYOBby4cVnOmyXlTugeBOAfXs8qCpsNjgccKGBRowP26u3HkvYDzeDtpJgJGM2LURww9u6PDctM0/XrodMzBBCTJn0SY5vEo3osri33RSEf3RiYAZ7bYt4xPzChtnsalMYTm6zEFwgOS4dLwBqG8R+dLpoadGH442f2QEr6j11F+H0dRvoQkOLkQb75jWrO+2FvOJCwzUizA/wCcId49G/YMndwZU6GIaa1ltNb79WtvAL2QYiylg9bHXmlFF8NzrezPucfW4GJBZBsKRxmyHotDHCIz4yTwMY73hpAVm/PNYKx431gz5TOadXA9g3kYbg9sWMDZLu+PRooC7mXN0p7Mgc/1qoMBVXhhMY0u7SNCWV+342hr9SacjMtIaJXFMowRfUbfBP3JPLWQ2meEMWslg0kkMPEno40Fv4FE6hAdzc+Bh1Vbsh8kzz5uBJSybQqQ4OlE8R4FhnnA7ZAdBZPiecHfnIVyRuZ2CvRbaOpB19hWnKZZgYuAZOTu9v4Xq8sMtCB/vjTlJgjocaBESwULXYibJlbq/hffn4RaNQkN2eQnvX4wtLPcQPN9swl/G15bit/FvdiqdxT+/iTOy9anaW7z2b2Jbhw/0GctSibCDsJwSoDCZ7CT71gLCv3daPhxYNpk9PHLACDvmSip9CXepKEYvUJQU8PGC/OX46nS3fpTmMJtmErTtsZhsa6WDV1oHh8cQiOoDGVUFgjN24lo1qttSEYPm86nHs6eU1Lz1QMg2ar8v6CNIOoHplICBmcykH+NMJPchc2RQaQDoKgbzP0xyawJcrmuMPHL0uoLMo0YleA1Ti1KA+euw9fbKZhNO5VfckYD8YnHm50NrD8bYFy2ZNEpAJ/oYZRXvtxiQkOTkEIpwLojAzmRE6nszn8VmmfS4qwL6JEzIHwk/IVrhdAxo91CAwAqRESBhlRjE0WTu0SB3aOcDGorxH+YcbUQwaZOACAkKPFCQEYBVJwdrwc3mvq58pYiKLIE35oUBLdDh8y5rBNEAzkW7JwPjKJNbnPlCEVwHveJYhqBXif3gfql+nCeGou2g2IuC43sEvxMOg/QNRwVfQ23fotxJqVmu1QzMDYArO8fjTVZNzKPXIpsGqvBjvkTHSXcUExhZchx4kqiGwXe0QkzvoBSVP5VOcItFxNgL1sb9KoLmuTTCvHbfsZQlpllkLpKuHr1ZOjCgzwIM2vFvNu38bRHdnV18I45mBw3OHMc0THEmcWBJXADYTghTh6nA+FfSDVkzsI6l6CNpijON88wyNZjYp2mZLLuVibZ5BCAyrFuW/iThsJO+smrnffb3T6oKZEgNmrzZLNxa7lbcUt8VNuk7ObTI4IxKMpUgkuJok04lNPabzNoRC2ThmKI/J9mMHXJOHXck7qrjVle7IkUVEb7aMNFuejTpYphwtBNdEcU44mVFIJsQtebUevwWExVWWUk6XDYWDCVI8aRyaaiGZzziVBRqhQWc0bKMvKCogxA94DaFvuDobQ8HczZGVrTv0zxEbUa/cJd0MuFTgoohUciMRnYzGIfGTuDXJSdw5Pk+WmOoUCejy2ATf/PnBEF2KPYWjV4QvNDARY+x0hbdA9HJZ3owW3TQPJUQHTpaflH/dsC91OKQYBQltipt1zRoJhV6ubAbAtAXQ6FaDhoGEiTYiTGaEU0yFYKcQcvFTcpyitHTVSMovjaaDr0R4sXauFE/pOVklfg79yksOmJLHPv8bgbUBWL4FqXDIR4yBpZVYytwSp9ddlTtPZBgcNIxEPHxA+QrkxxC+/FNOYLABgAtMjQtNO4s96CAi0wlBPtRuhbJybFt0h4qzNy4Mo1NnsBwyfyFISab3OJdMiuV5TBvS9j4jMmUibAMFGatPehBgi6APRuLmuXJaETw89KTvnTRcY1hEtmSCiJpLKRJHdq2OMyCrFiSvQRWnKdY9AGVoPiKbIUmOlkYFZae0zgkV8MHmwGmdjLVL0uYjKhMn5sjv++YSLBxkGNovxCe5yXAmCuTqX47YJOcfY4t5ITHRR80qYxS8T2U5ffpvYJAz1qjGAwQGBc2RDRfdsIZObUNYFFOh3tqF5lqsFGipyyZTDIDZyV6ODUEFx5fUu4LCmemdGDh5FlzfGZg8bjIC4gq9KFNwh7lkn3qm2xBrAJCP5CL/dVP+vMuIYRpttRYvAjwj7yRi5neaEWV5cYXUEZL0x0Pv5LdEYrrUfVFl/QOHwghe0S3wKqKLzf0g9bwLWn5EcNO8HxurGyoJKZ1e1R9kdiEdSdj7wubefaqRChZIbKH75bhvZCNN2cEKs2lRtdGmnfJ6eqRzUPmL1TgN6GkIKWUPcS6ZV7SjswBhzuGhVTDWcXKM6+vfYEUNjKMut40VKLvg7XodsQ7kf4ctfwHKyU20sUM2A8AxDLgE/4EMkmFLVhdB4vKaBajaQAoKpwWqgntXISjcgWCUmNpDJ+3lg34UnedpXOUGHzLLgtKD2GRuhFx8LgEBNh60InixLgi2UbMFOcLuiSHy4iRNysrYHnFKwtHum92KrW2JoAPdBc+e/o2VXvVpNnmiugJHdSRP3DL2nausB1Ow2DMCX2kRAYMobJeUCLjOG7J/hwqD4s8KtgfUlVSEBSF2Gl7Bm8PXVeSxpHHkz6O6aVFhVavSmtJxS2EAj2g+4A/QOgVEFKBww579YMwZsXgMCvV02r5tFpxWZS52nW1CW5tMVZb947RKzMBftAbpoxuH2hVSJjEVTr5SgRII4a/axWH5lNqknfsgTDbjO1mSV6yh1uwr9gILA5UPDQOt6lfhhypSQjInEoYNk0r0ALdKS5VDFLvcbZhgJMf+XTLKFiEZ4Zk7BS5k2GJRFgpGrrzdzpiemAwT/TAR3MU8nfwgKaWFMki1xpM+MxpDZ4wADv8LuJ54TMWoFnuXEsJtBcKhqrbz0JQKZMO4WYzy1Q1sMR0hF9wMynG3nR71Le3m9XTcP+DXkyZTuxfecYYf8HpdpyYW7Hi3zJRrYkUeuxCU3MS05X9KrZbTSZ+a7HEQSg0Muc40WKeVQdKf4L7U0TPAAkPO1weSskgUgbH6MUQgGADTRaUBRkIThUCG1NizdB1ixdlFT2wELNLrnKVbuhERQyRYRNumLzii9jLHw8N7jHlelWI9fBQ9VNPQ9e6lU0lAtjf3fFmynJL6U2b/M1spsnf7GbmHzeBetRqQe4IJ7BDuWel3iONMSt6xIp/dyfYYw57zGOP65t2TusSSqpdIpYIdpnOppbgf6VPrAkTRRCB76nxZUmdWAiNyC4p4nD0exeOXeQxTUi4xWlU2/ni0NOaJseV7xCK0Oh3lFoSlAl0FFhlBw78j13eLy0eZy7nrNXUZvo2oX5P0PdZlphK2xiS7sIay25TkA7zZMZuCWVyuGfh23ji8s+bWmFHLQIRwrUmvXGH0JVo6aA+o4rIjwrb1p24CGkg1BsUW3qhB2kiKAieHqwziGTGy3PxdSweQQZQ9g5cUsHukYPOuWVybucgmDLOSo1TnQSQUnEh04d7ha52lbYDbHlnMu4N+gsQt0O6VjIpx7NJd0F5dmxoMPbnTDtd8Z684WSKEj6kMwIxJuhEdnp9F5Ks0R0Po6abgn3FDsNxCQJ15UJQQaz+9Ytj5yI8gekNU25cHZ+65frRdm2Hyq42GdcaaCVelEur68nJRLsP3quLmBGHIeJTeWLuZInwaHBzqI3ox002YpASfKxgwETOzcjrmtGg4RaFYLKsviModZezAaFtjrYn4Y2ucQkxhShBT1nqt6x3FtrTrNIKTA+d+eoRzj0hBS8atdMqi3nEblswQoNLhke6Vu8gRpVTAbLXtcBoiFy+PXWyvO5XZJ7QQlIkV+Y30DsAxKMnk60S/6TjFgLGZOaFlxz3jcuuwYgQXjTW1tSbgSxUQs5Ggh1ZqK4D6WfoMHYnHRSbysM4mkA01DGVrJKXX+PvXTYBeh4kFCFlc+Rl8+5GCG8yfSvwNaFxNqyJn2xWq/sQRDB0LDhTyatQDuyHADfKjYptm5jMTKuaznap/gwWrjdAVY1gLifs8oOg6J6xukpDtSIxzeXTFtwCSOd4Im+KyJ9uBrebBrTYfQL4AGuqT4u+bt6sRbYRYcw26a/AHSh1yv/S1SnSCxi2SfTCFANjKOLEIk/thE+/El48uO6y3hcuDwhF9ud44e+b/1/KTr38Q9CnQ2l4CB/JF8QVnCIFrXdS+eAbba8HxwYZJd4q07IaON/dpJmkRyoWxiHKMCwJdzBLqorN9bapeaYYBQGb/wYxYqDfVdmDJiEUKxMNWPxb5GsR1d00yjMPgv1Bam2fIDkPovq1cXcTWLl9C6S7p9ElCJTsrFRBaUyd4+4mYE1K+J6vDJF9hULMknPUmj1Ae893HmluJm1maIJh5YYjO5esI6NDEBw1BzGNFimE7NuQqcBiKigo4kl4yIGeLGaGft9hg4xl7msXHWuQdO+9tEBLY3EUpNyWvnETS73YqVTKMr6i6gdghpCkX+MWCNQL9JPkq7/Gb3m06OGQUY18Byxty9IgW9ZyoBTvh0zxjABPht8h4/c9vRiMwRvgGmAmRTLe7RbBHdy0R0Dp3FDTayEx1DoDU2ztczwSudwWzaPJM2SHFg7hKJDUqWhYFJxtQuYPxri8AExtTA8l2N9YctEGNAekqhoJUDEh4lzS0ZRMgXZC43PL74xKa/lWmSUWvlS8SET1pcYzfdl8gZxzQgURRNOWaCDOrfSxIYmnBeOisTEqpSL4IKBSRHsck/4xFrh0NbKoMZlS5x1w7x8t5lQm6L10hgsfwvaaUnoNO5mi1hVwn+WtEZp0Rb0N40UBtE5TqXwXh69KEKMQZUp9DdZABpLZHfAOqEX9R3Qd8lWCwkI6N6vJGqXAy4m6dRhhAcdnNJ2/hsc3JkTewguA+vPiPYwzPermhLcB+c3gSt55L9xBoEgmW6in6OeoHbBJiilcN+PA36BdXiTGi1gbhPT1xEJhIfk8ad+TXlFX5CuCX3Jkh0xxxSCyJER8fIpHPJApoUb0+nxS8K0LQZnFujnycEhnmQDVgaovRnv4EH6PP7CsuuR57oAOMgl/yJiYM4NTqW6Xzg5O3dPaYbV+JpOuSO0ZHVUw5GhIdXAxG3AdFouqqyktOPWkQWWRi+BbDHRnYJFKZygeqS3Eb0KIInc93etwsYcCIYvdEDK9xsZc9eBSyxux7MoOiGHheFyfzoQTboPC6PKJHbVeYoHJtdg3lzA/7qgdqzZKzSobAiviHlcb7uGWFW4+IpCpImGpNd3m6dnWJu9dgyVsex7YyagLxb+g8+RgOuzfyI5/rlOdUNfWgdZKuKNvsahlXYWVYWsQCYdA9HJQ/N4KjCtcH8FSTuRH5QN7QIL2W7gUnEK1iCYHjBppWIscmGZNASnDnqNgmSqYqOzebVR33FKl0li+kBouWJ0X/SkhN100bTAjMWrwcM1/pJKpTaWaEfN6PVDHkatyJWn3jIf2YL4Gdkscxa3NvxXWgPKLYBN0RILKYc6iAgpRToES8hsYWeiJZvLyV3xqg8AxqlwGgVznloA4SqcZvVOjRxsYo7YzfnGkH45Mx50/MSgpbWOUABfkfE44C4qY0FkgfSuXGbmvIdQqh4mtQeSJJQW+OOy+FyI/uGwMeIHWneyroLhYlj7+mbQgPh1WckD1p162t4FMgc8qDUHQCodFJSSiXGJYS8igCfsxwocS3oobkuNYv0REDVaVobsAK2kIK6EGagDcdGQY12U2eptgRa3q2f8YH3itJ7CvFaRhADvIHRygMFJgeIH7Aj08JFkhqGyIbniT1gMcapoFvl0iWtkM3CXMPgWsZbXJkcUCJXhnVFcnh8EGfo6vgbCATECrbPMrZDLfCDHknSkRjybvHoEdnbItRqwzO1NK4Y8Jh9YaPrdeqYWF0W51HgwM5yuzA8fNeGhOPqsaCZLSqyCvhW90nKrcE2oiq/cqmGQqW+2aoSOs9rlJmwpJxjCDCpLeIBWNKSqjkmOipN0sLrMqo+t+N5gusSijOlIWhGoTw2IIVSu8EgWrKI6gOR2XGDVhNrnAuy/MFVStzwKDz+Var3SFDZW5ImhUmTqEFySXbveFmYQtt3wjZRKYO6mPcRBCWeAZiwIO6xg4zLSYjTzUEDDxzBQrXTLemXGz4t8SlE3aBgM1i1dmRAzmxREBiik88G4xBL5JRK4WjZfZG5jK9Au5ffDSYfV4vvmSnFCZol7riU4tt+JcNUSXXYPGA9FKM1g17E8RKL0EjyZzEVZdoxooDHqM6YHPNEVs8rVcRcoMsFdFtSMQbdMRMxvoUPF4cjh59maEIaaTwDrTxiu3zzk8UgUCIGt6qcgONXOB0nw+G7QXc0/By3DARq0HxuCqB2zwXsK7mEB4Q26ZRrYFSqk/CovhS486c3krGN5CvGIZk8KtLkZtyPeseulR7BW49cYRBdkF7/mKPSmcA5qfCQ7X22CqQWXp8EgGUwkf0BeBA9DSkscLKOoHle2omBZZHkNXoHFnXK6AglAchjFF4mHWJqQ3wk/UOYtiTW/YS4fW1OPhIWh59FnQry7Al47jnnCkj4JltHAW4ZPZlqA3lqzknkc1dKFYxPENFcwTGL6ZlID1fN5kSrlj5TYOV3Cf0hF1dn7TyzPoLiOhC8abfQ++n+ruKrq7qPiwf1mf1+/0mfkX9dmK7DMYYfUv664d3V1ERNR/vkuenxCSXXwGQ9KEv2rB88iVDp11L3zQf9mESp2vL+SgsnHyRIfoUI22yfSFCpEItyJ/iuaobpy9RIGsJ2JCaSBLm0L6VolUrRiRqrBDjdvoNhI/a80lyN1hz6VRWlh0HTYN8j0MkqeJCH5jdZzoprAjqoh0Iog41hqm1nJb3ftWB0L9iNBY2jUYqPrFoc1udr0huU/ow2pUH5yLhE7Q8hkCOLH1QD9DbMEyfRM1NeT3D5tSFaZ24WiWhnzyWH4/ar+rvGErGpU2MHTFvHNbUWv/VidoGEZeSWN1+C6jWlIRBLwbttreEAKJChcBEWWVfjI3GZU7ak1jQo1DvcFXXrBmPK7YgmOr5mGpbOI3hJAF/w8CCBQlfGJ2Q7yUQu0OpkatsimcFWhhjS4LuWWFzDlZQNHxxGDtJQ0wb4d7mUDJbbaS5opunVkMTyulDaKsECK1N0YMNXiQcQDc0Y0WFabH6eWvi+Ghb7VlfQ7wC0q/hh7GZ110OoT6gtgZr1xIBCumCfPiKrw03e5ygOl3I8bSI6C7OeGcCZkFXyc0nsAHkLM+wg4gWLeo9PjRWLjAK7lkTLPFOOB/Am/UkBHaZltmss8PfWu86LU6c3JvzTYZAySM4H9P53KQ1IZwCi6ZFBC9aSb+8F34RXlPg45Hmwg08+NHAUpaajtx6VDFTealywE9g0v9BdR2LB70wYwrCOFdW9HxxEULOhDpMr0ce+TRU3T5p5xYXi7Gf1gs8Y8uGaT8WldR/Ijy/Ad4qyRTa6w6+D7DXnPMP15SBfMbIGnBgymEh4DToRU3Zb2xqRVWtEdUzkqaS7DSUbolipK0vY1ciEChdEajt7elz07gkKzOi++72EhDc/0A6PJlKv0VzuGaJ5Q1t8QHKXeOkAN/5BMl0jdBS7idosVLzOWVJYMhR5grt3/J/Oo93XY2bFz1y/plWjSgOy6GZIw0zDXYz9Hl+ZsikadntNVvwc1hcJm8bNhSfgamgarLRXqtpWJz2QLBr8xiN5iaOVozrtLoXKgSaIGj/tVVLoOs7xssWxfl+L1u3IxQ6odVItrql4NHleCp1nw+w31mmVDWtMzxxCRkmfnq+WYYkO1S7aBakcAMfIQnEhqcuNDhjJhAmrWNyeyZJQAA49wMgn5/SjXuDnAzuOWuAIPb2+ISkEECqs6GZfTAOEvsG35sSQlhF5CAJ7CG0e1K0VMNPulTzST54vq56TISn76jEHZDym8eo0epDgF25GNcbQU+ycdIMz7yCw8Cyh/ZJScGZMl2LaUdQQ0rMxQhkKLHz+hhMoCkGbzLXIjjsPDDNEwT3yvm6+iQIXYGq4bYhwfcC7hUhJvujeYOOMuupFa6whMVa39LM8EN9ufIn7/H7O/flXKF+KrN9gvEWRtA/DF+wWN3m4aZIP3E3wVVtNCkhtTRLUgoKEFGx6Y2p3jRYssWowPG7tNk2JoDjhZdlaT6Q3RkfAbY35QsHQsCwivVPGiUWJ+bATMPg/EcDMTc6WI2nfhMlxhuQlUB9n3OEXBxMG22PRjTX/G4rjF0y9hVGVQQ1LshAIEovk8gOWaQMC5IBc5iQ6S8KPSnXIXIvmD4BMfF3/MJ2u9Qg0WpUSEnTqRrlyZ0obtxmdkcUwApbURLjGlMFSrQDQqNETnRRNNKO6QLnj0esJ8oQzf1nPwly8K4/4ioyZTJpbHgsCw3PB9QB4akURMg8U/csUExQ07y06Z1aElw4jpzTY2EvxC6tRDhZIDKQCZ3wjhPNNUbjVPFnIOpu485az2bLEQXC8/yNZJiokUYvTTp6UQyo2Do/qKBliecogLTFvlhNTzI1YlIiRrha6DRUuryc6oJYx9QykinoD4ml+TetXhXgmQS8IcN7TgFOVTc9Da1KQh5U+iFlXFqIQGUwNtgbk3NDy28DF6FATb8UPYR2Tdi/ix1pcITHReErc85Vn6buDyzjuuys96ix/yJ7DAws5RpRFnklMMWEIU8dgssF9tkf8ggaU/GqlGfUj80DKk2G3SpQoAJf4SbhQ/+hciSxzDA0AJCJAGzAq3ELRoiWBirg53zwp9DjGNen/Xp8zBwSaMkmgb9PdnrgxmPUzRmvu/QYrdLg6lBuJanQXdBIB1AzGEetgo79JL9pPF1tW3Ydi6dSn0lXxasyzbYypE2b/4wV9t/mJbxh0kL/WHeCpt1MjWRU8HiNhIqb0w54S7y0mRXkH8nCx8jsoNPJA0wBUvJxWaijkvrxOLCnNshhBoNYVC8Jte5WbwibyZm8RI+0N1hFi8ckyqUzOI5ed8zi2eOCTIlNHngHJ3Bi59C3fFklX8wi03HLDHXid5kCApIhJEFHxT4XESpw68YGc0sNhxToUPM4glq5hfky7FjHoHJOJkZHsZwMZqaxToBjkW6ZEIbsj7005Fj/pdZPCQAEjgPHEJru5Amc44GxmZx3zH9SW9O35nFPVKuZRZr5B/S3a5jfmd03A/jOwf6h1ncIdOj3ktmcZu0QyAwi1XHvGvNurzBCmlpiqGpZdktWhvnqUTqgbrXnc9ggJCBEoKEuXCoYF8Io4kiuv7OAIvhryTfwcf4PkZdNHAPOaZM3mk8rfhkc/EIYxC6aEu6+J8RGmcApsvKCUdZhGgCFMPYAVzELj9QQOwDeiS8y2rHtO684dQxT+kFh96mYBtgWvCXX9edzQ7XQydZWDvSCmnAjFudu8kA2KUb98Ryzy33wnIvLffKcq8tt2W57VuL7WtnAurKp8FsMkaJj0nGdVqvH2BmStNyT+JLwYVLzbQqDNamjH3OTpX5YR/H9cYp6aO0vI92a9GFKWmbEd3ARwO2uoER4TCSFLemWZtR4SubspYSgvdDsLZKZxXTkiaPGH2nfojv3wEViQsO5wUGu5yAWwajOhgCYTEwEA3hesul2idTsWG5nuV2P16frep2vVEls7e/HCQ0MDKtvTBM1PRI2dTkVFBLKUK9SZCq1j7Ac/AxOKXt02qDTNpyYAgyQwTBgKlQb2hj2CJ3Gnr5iSgbBDIoycJ8EirrAaAOxHAzmpCft+e1QKDqS59DuGOeCG8HjUNyWrwvndJSsDBsqGkdMrCqY4QK364OwbuaE2uSrMP5gtiDtHH/M93wK2j1qdv1u4MJn4Y6v5pQIWqcVyrNSq0uQxsStmkOWn2ChMF0Bqg08MVGYrHTmnlxuVoid2v9vNpo1CpVl7VGznK9Vq42rTGQIM7RcgREt+gqhAwcTec+B1LacPAvsCCstEWu934LLXEJdCkq7BjDZpp7SaNC9w1ILpIgaKYW6uX60REYEJdOT6uHx6dNekg/czaDNQkCWtImWPYv2nSkvqOMWL4FFE8YcEJ0U297Pt7GYixI+L+zMGv/MFbvaH7kbnfA6AoobUp1FzY+HXBSAXA/mV2ILk7mN75J/w1NOqmxKmsAsvPFBYBLuorHgFdJGmLLcKKueVxjHpuynSTNJckJOVLEMnahICbXxjGtSkqBU3k5o7ZWZ5Y4uMaw12DQUJNpm8oH+9YJQfu7FavcjMu935wOyqL7Es2eQfV1ONkujzjnyPnHuaC/Yyb/LhAED54gwrwAEigdUi8qjK/jibtUtK5Pb5m1JRKpYkOyFo/otQwm/l3ARJ7JSrQHBD8QUoma7SFKHfgP4WZ1YLZYI1ugmeLkSvh6+qCVbdpK/acrsrk4FsV4+LZlg+ffxanApJnBwQ8nHXp2Qu1Gj57TsCyPynsTIKLALYNQFFBBXB1h0KZVGkMAfJQ/Blk09CHMWPJTQOtNnbCm8FT+Qn0TgtLxYR5CEDuk2OfvAsURvsCqJhiu0/8i8BZjmCV6pBjLagG34T543pSGoqahtxRVnGmQGUKkMhmz465wmjr+ILQwQbfzAbmzkTiftbBlFyG9cSu3wPCLDlEbcKPbiksQEBXIwpu0uZ7vsti0jmmxMHEYVgDKmIqeQW8ssjayzvA1WO/9TiM6A6P0UElZYMnlIA1l4GIA1QYjgwXKh28Lb9x5lYRAYFYTN+4ETHHAEAfMcD4m5sjusNyKEV4Jw51QAm85rGDShLCOwrDSb8vBHIO5FBhLganUJ8A8XArm+CMwkbUEMP0wmDwhwyGha7aoV5B9uGUZafiThT8F+GPnD+PGdLjwOS9IL+pYOpffJ58x6QNWWu3YWI/8GzcTwV3A70/sG3hueneqR81Rfn88K82ls9KAGHvvEinqha1IDJYg4AZnLGi4Fi5BaA8nbSE+CyJZ6kqhLcqONfLmhGifObu0waagpwni3WZc/nFrMGNkxYctkmVGY5FVMgWmVePUFNqEQLgFKtKQAUlormFw+TBY6BlyN/lxzrrxMEDIT0pS/z28GQBPwaGOW9bW1i1/PJ5TzmafhTiNdyeBag/5DmeaL82zBd2GJlzdHIME81Rk7c16w8Vk4VvMHUl1XOkuZkJHBGYOYf7n/aWhDiUcrNJ0OuTh7JQgFcjOPzM3X4QhBqhuMPPgqqHJRmlgKh4D7xfhWEX5thnchOglxmNiSP+ZyVgyqknjFyVyCsf46zuceu2wIDpy4lZxUlbZpPBJrmHh8CzrM2ioM8j9sejVvAQgHrrAFaEL3P5sspiKtXLcrUBTEWwn/Qr4+I0DXObeYNwbjmDNsd+DfJic71mMh1BAiBYwywfXQSmbQaBMNziqKCGFCs0iKK1AYHgJDBBFj0IYOIJ/PwRM5VB10nuh8aItckIGlGY1OjQCXBC5Sg+sZcSyLKFxMyzvOUthqPcV4JZkC9FclhLRkpwarCQrSw+WZQDKEmIPsJA5EVBReJWj5kd1FVhfJlznohdmLyug/kQLhF2nfiamdc6X8UKIJqlLllSJfrZBYAZBTLekwfYnGcZ3OpGG6vI65LgLBFvS/e09gWlKdCVcV5Ytpijw6bXkuWzNcCeB4eAEC8SGs/3pypfWlb78IvAQOdK9wYsBSaKEKtc2WJANoOhFSa1I2gA12pCJFuJs+vz57MNh4PXso8ynFrUlxVcpIT623B1JF7vHn+iDEC2rM+8JsNlM8I+D8WC0GFHFAqi+Av1cW9iTZWZM2dv1JzqjbhmQE0bBvrRPyExDx+VPyCXagbxnGEVsMKfCW4guBWnQjXx2f0ukq0liuCiWKwH0LMYTWxQfxcPJj7eLCtbqU/odyAjWgs6tMIg+/kRZMwtwRoH2563ZnGd6wow7pAEaaSIC8vRPQ06+rFIvhlWqUxbbHow3IaLTGM3OmrslUgYs0AkRl0nzTFZM3znQzghmmhcudtx6gpI1FKEm30MCbukTcKt+J0GQqc8D80nhLsgLmkNPBDZVHCOQBsMMZIhBcEDY6c/Q4gJUjRLf0inxreUXMrPS12RhYNfPo9khCqufHoOawlzWjFvaVeszR4FgXQaxMCdfAhL/roEk5Swjxdwc5SRM1V2rmO8xgWrPukSPGmK9KyxUrdyWgEU/IgPmg3LqI1iUrqPA0TdeK6SvEMYdyLzE7NUMx9CwpQXCs620lZGoLh2gv98fetjCz9T44l8fvNp7JEgR9Ot4lVva6VCoX6jgUFUkLZDrWMo/fW6y2fDEnFNW+p+b9Ggp6pH3TG1L3iOH5Jb8eNMu2a08m/OndilWieg9WqbcpDfHx1TdsoY+oQhYVjVaFfCTcgfVXn/ZBKtlAlIIXVrE7dwDXI1aPygz0olYwHAKNwDbj5lOz2msxgizmE/y4MuBIAeO2ZuvCjTcvAOfJmmBrJBVYJVuoU263PVoqf7h7aWC8DlRkuLltZREl0U48McsTDLeTFRcRkgycR4+PAdKkwFIqMTtfVBoGaH7pNI6bn7ESJmfgYPWjATkE2fSiI1AHtiG8IzD+WCK5qRGNrWRj793XJf3ss17CdlVUce7X+tPGAkvZat5AUH9wwvZyaz1rPHRYHsAgX0ZW4OnCRgeCNwho9WYYQA+yWhJDsmSMqLyR82B4B6tZYHxZvI5fOYbYyJ7kUfOLRUUurXw8UETvb93yrY+ASw1BBVEOzcvOdVMljGMbBB6QRJTW5goDWkEQ/7OtJHmlEjbQWqcRgnGLJoK+uQwTKm9JlIJ29vsaqdZyNHrVHoSSmo8oMj4CMSfl/4vv2x8BX8cg6GtMOPgH5fWDjkD8HZ2yEL5YGvEvxJ2mgaxAWGgEphEmJFgEijZC9uMyQfv1Y/FN5mjkFa6T+7A1pB8xZSS/h0mW0L729Gku4ADyAx/Ic8fq/cbN/oFzyty/dFIWULtisbEILbgVr039uYt9SnhxjD4D8pBsbBwT9BtHTVTxoDHuRwDTRrF2kUnRJi9GPWA9F4G85i9pK7jNKgeV413FXwRCngV4RnAAuoKuT42EpDyG4hjmZ2YEmdmLoMno0MlW1YBpAsL4IhlutGHILwZuB+UrIfzTUgb8I0DPR08k98xveU4rJAvPWKX1wcH5g/bIJDoLdykbh00QDU3Q4b1VMf1xXG7mwPqPuSCYaUzGoxj0TaQzL+HFApEXdPqi0KhHtF4k+4YNN0hzJADxw/e45PihwNiIAMDcAm7TxaEi1eNB4AQTd6IzqT9ADcCJufiXeNu3ogF1R3RkMUKutwOjseH1t9a2kSoDxbGQ2mJsGdoZ0if8afFAlQoK6O5QjAD5sgDIFO0MXjQ+GVsVH2qrehxEQBI2cav/OIkkysMJrVpTi6d5/cS56kBBTBC4vJcp3okBH0VQ83Q4rBo6EWiGGfHFD8sZg5KnarcJxpSQqvILUZjivtWefbqQ+Jl6Kh7uPum9EW/oAUGxnEMR2tQJiWY5Sgy6dB7sybi9zVBayRz2H4qeazF1XhS8cSjPzF4gt6zPFSiw2IbYKGICDzceNVlxqsYCyH4MhZZNNwYHIMf6pkIR/+kYTrAVxYLxCLwDYL/UVRLllSOTypP1QwJYXimZpFSTRiTJ419QvTRjMHUm1Lama90v66ooGuju2v5iiOyzpyYcXGj6R+i0lspm0UcD/A824k69HBAVvxwCuqgYSmLVRkKQBLlfVtmKahgEhSJIuSXwtAc0qMcBkMG3eqwiAX64OIBoyn1aIjAjQTvoIZfMWb9VH+pdyZfsfEJRIyIGKFCw2Jv7yQWDS5RSw+HhCl/FGDhTgPPN9IgYTGWuKpFeanBIdBv9wgYkGofvoaPj/YCS/5ErAT2OdkZTnwvFnngooIUKpMSIJocbporlvxlwMLVjbzugBwr1IVEbBV0UnCcKk6pdAGKvVN2X3hrT3pzA4vPeRhb3EbSNSkQNDbQ0IHi9v3K0oerzgk6xMGIBeG6iueCGdpGwbDbKpARcZUVjyYsjoSTtmABIhI3UEvEFgrQWiJmCzy61FV1GY1xQIrwnDUqrQAVyI0ENmTDV3oPCicM43jogXxjTk2led5boD0QU6GhD8/SrvAeZNNSV1Ja9JX31+o8MGUd+gtCyve55O9wMCyxIfN6jlFyhlE/cBgYzGNlyBh+GH7EeBLq5WyVyA9bGkvDfKoLxAQ8wC1//dtXmGiQ9qNrFmOiYeKYVySqJKjZhTEH1SGeO+pVeMqfFTtaZsrvT4eDOQgxx94zhFyBeRJ2RbT2Oe0ADN4p//615a8OfMhmwxLWQvBfkD1IFmjAMsFMZkBzMCsnnmIe+klyf0fSL7PWvS3SOUIrH8EHk28MtzwFuY0kzpEPMxEz/xbEzbIxnUzRPKOBjbXMmRlIra2iOpwVOK/omAxPwHdzsBOOfzf0XpI4jzH4GjjJvCAPAEp+yxDYDMTNMKsMFKdpsChmvA1kukUGVv5W3aMwTb+hT7QREJxAx8hRJ5ngg472q1Gmvrjy3uI217QZOGOMCABrvWeCOH20WSLIoI3eiXRGR16/1SZbScu/5gsf2lhpTjXao9Y9mVQQekCOoJk3IjSeEu8a63On3wHfpuCkS/a1OQYh3dA0uEROHhcU+SeZF+1XYYzuukBguS7hO4Y9ixItLnMytsBfhxm12wGZnPXtGwwVXKzRejEWtKeF9uJJ0fzSlmVDRaiSdJcY4ioQdyC2OYOYLoUFdykeSYuedn0k0rsb/oM7m4b5+LuZHpmb7tQys/Dvo2UW4N+GZdp5+NGxzAyWaOKb1Y5NfpfK+JY9VP5xQ3skPD8PJPM1kNhNX3E4KmwvsATWJtnLiD3kKie/rohmOd/0lSF20CMCtgBpLimEuBB6gGZ4PSX2Id11snsWDI5uLERELaO3QFMLsumgB+2ypTTIvvdKqQ86cQI2UQ5DlSFd5TvdQWcek0FA1YiW8aJSMEnvm5jwpFMLqqgstDE2lVZu3JJ9C5nmi9rLCnlXURtBmFmUDRVcdu22gm7J9ArGTWauCFlYi1ZVqVhjH9/Qxmh4ERqXhTPE6pCpVFINBeGzgEpyC+Nw8YTQtWT4KsLj6yOcRSW7wjzQADZJ8Vozhat+Ur3EhPeaRd3XfOry4FGXNcKrCZc1TcuHt5efDGOYj8/re8eVLx3ZLYBn4g76DphsJ6Z0mjJYclcpamtFTcskA6HhhbRrje5tusXZrUXKhohIkNXQovEvTu5nNxENYC6iPWj+geoMJ41DpqXKiUlfNTSfQMJism2iQiiQnO/MF4Rbj8UAHTxZqTha0xl87H48kufA3UBP/C+NjKObpFEiFBgZEBMFk4GQYRDKssO6Z8BH8Hrj19jNkxGAFnfDj0wGNubTd4IbPwPesbJjORTMAAsjhBmp1UwmlMp5+GA9Wl3rrmt12G7wi3Qj3XW/f09n/+z437/bhT/Jg53+8/H79/yfpI5O2/zS3AUECXQj/tr5DDqPBnfKp3FPtG5oCfoxOfrR6CRYSZhyxhivSUtQ0h4ESIEMQEAxy6vK/GkyxUyYIcokGvZfIFD+OhIEh+wojnaBkHUpS0EvVjoeFjqwWcRI1ew2Htxaqb/gMHMTj68r/ldxXFpsS/HjSjoL4xsmy3CQd+DlEvYtxP+PgqtWx35hzT9zVLzoRgh/8PLpwXFsSvcceNiPhQECi6MTdCiRplVo2hJAE9gQZ11CQh+Fjhp3lbtqqYSIsdDYakSssHczG6cJBQ0hMFvvpzCm9rQusoY8ifHv/0vRPUVl1eW1YBgiMiD0lni33oftxlftIOJX4fsOKbGF+6YIqAtUy7A1BT0ig4WGgMN5hcEyMiwwwyyv5cfbhPUY2G9k6HIWPoVCcRnjvwn6FiRObrleqToeOR6xQDrKGDge5LPJdj4LL7terG3+MfaOHs8vRvZb+u2quTZYpKrzUeMg8VDarp6Vnxbzavcuc306359mTluvD/bxU3Oy139YXxw2Mtepq43h0drT/O4yVS4N92Ze73rrqL/YGnTXd9fXS0/ZxMb6/mzYXd9Kjey53T/IXeYG/kX+NZdd2EdH1aPLg5PMdfv6Kl2Yde1KaX58+HCcvTzYvbzuLezXt4vG4OS5NGycbuTfGmOvOl5/SFzs7T9tX8wWw3Z9uuisee27o4tq/6Az2thrNdfHa97E27oqdR/ru62LWuLlYOeufDRtNLZ2z2uHOxe75fu7a3//qLLfL+Xe9isP20ep9Om88XD2MD+bXm1U29uT+1k920vnX3KN+5dxtXSYO88eVxLD6dvgbW17Y7Qx9HbHL429xcvRXju19jx9e9ndnjeGT7v984dGc2e+Vb24fhg1Hl+nTb+ee3k8Os50t7Z3zo4u3xIpu7F7eHncvj/Zbp1cDZoXd7P55OH+Ze80O7/OXR8V1uaD12xlf71V397p5dv1kXeynV7MHzf2Rov8cfls+LRTqF5UTk4LldfuQ7OTKDw05vZW5Xmt9ujPTzz/sbyzt++PTrb9VGV9sn3fuWtX293t66vCzG6W1juF/PPGxuuJvV9pvZXH69WT/Rf/cCvXufaz68+nZw/77dxk+pDbKmXWU48Xl+fe/Ul7cj8eHdrn43K3e9xrHqSz07v0JNdLTLsnxxf3pdpkfl/dWJtee52rab92kkrUH2pblemJd3eUadbmk+1J67owvZy0p+sH2d79zD7Pd1L39bXJrHw53G3Vm72J33zp359cDy+e93bWT2dHiVR5Z5jqHqXLg3S7ce49X56fPu4f1qqZV2/Dv85f7Qx2yBCfniuNvYN69So1eumnj+fNxPVGbdbrbL897vvt0sb+29NkXi3nq9Nh9vDqKTe72LefL/31q+fxdWr7tZ59OLzrpNp2NnH0XC7Xzrs7s2Zpbe5Vr55rhca41t86npaP+rmTl/led3Yx7ld3CGrYfhxcnu621xZPs7PBdLGonZ2dDI8O57V666128DDoHD8fHV8P7157r7OT08v6+fiqcLQ4bfiPjcF6N32ZXn/svb14+ycn/dFG+/RhMBkWvHr2cvhUOrwaveTf3lJrtYuznVl39rrn3VUO9+ovudPTu4bdbKZb03RhZ6Pb8uzT0VW1nNm7e9pYKzUnu6WXnUJ9Y2+r3zxMVaonj+ls9eSk2zrrNxavhHy7t6uNw91uaX8wrFzvP2x05ifVcXlSzhUqrfl66W1QOpz2rzL2xevuXv3QT10cFHaP+y97D7PGw7XX2Gi1t7bW7dZGaXs2OG/sH09fEoXzRP7ZH+z7J/n17aPJWaLkv3Uf+lul/ePK3cnuXbV0erDvpeb77b1ydfpW2spV6rvHk+aoc/JYqmX2Hx4ODw8PdsrPxy/Xa/XEydXDVX6xsfPQOd/L3u3VjsrN9bXGw1v+8jQ72juaNcvV8WCRLdkH6dPzu9Hjebdf79ovzcfp/PG++va27RO0tGhfvl2SY3Y4Xevvj69n4+u9+dvR42G6eXnQPyscpo6rhwfZ8fy60hg8npUqo6k/TuzvXb7adu/l/qQ+2XnaXiv3718rPb802e6vV7YeXl7r80Vhmupm+9uD6U6jXdiu5of+eLpemXprr7WHef3eKzw9rz+/5HuNtv/wvN6c784TV2u9rYuXTj773CnsXM+eO6eH3X5l7fBl2spMtg/eZue9k/TJbuO6+1R/rtxtNI4PS/nJyewpW706uz44fzi4H71s55qlo9FbwjusVxaN/sXLeWPtepDtZQt2YXej4Gdz8/PeQb3eTpWnT29Vz37cs/eG90/H7afz07uH11zlrj0cnRMEure/dTB5bG0dbNQvF/nFbvbgrpZtje/2httnOw8Du3w8OXxOHDbXeyeHzdblwWTjbPRmL7qJ2mje3k50nw5nG5nSTqbX8Pvzw+Phc7+5ftxtla8X+d7Jaf489do6fiJ334u/6LXTF+PTud0ZPDUG932COE+O7O3tw6eHRi1RqVROpi87mXHm5ehprTrcT+2VTjr3e/akcnjcr02z3cHF0CsMnqqZbDP7eN9KrVW2svVyttadZIaNrd50vJ9Iv+ZT+w+7jcPETu/+2S+X2rnedL1cPyUoPJHtjE4nR/N8bWP9sH668zasvJ4f+0/VtZ5du96f7Z89NMqJ1+ds4/my8rpd8M8XhUnvNXN6/pKtZTJbG/XnbOLytFVoPOxc3w13j8rpyt2LbV+V7k+2ti+7+WbzcWu31Cxc5Ov13vPZ9t5j4+qldHjRGG30+3271G+tjdOFytbd7Hl38vbcSmwf9drD01H+LV/fb2/n1vzjabYxfkn3rvonR6OTw8ndY/OOXEKTUrt6Pbm6qG48vGUW9mWrcPJylHrNXE4rg9Fo3Fmkx7NO/7x2ub2efWmmt+3d2mSR3zt9ey6UX+y9u9pj7Xy/6iWeXr3heuvw7LyTSOReEpnD7tX0vFa/6O7fVc5T1ye295RvJB4md4ej0sZg0Lx+tl9Op1vzhT1I2xcHvVZh+tY/Tj9cbtwnzp/a9uTiaev1sryz6CR2H6793uvx3cHJSa/pVc8u02uV57vd6vnx9uPZfsPPvhzlhy/XuUYzd3ddrxWeButXG94oN3sb+/ezjedC63jHP+itPR6tnT92H5vdibfxcrBPFnite9Epvd7bBIU9HJVHrXbu4eS5U6+trV8c5vffnjt3pRf7JT9IrLUPuw+X+aPU+c7p630vd9Uczs+HXmN/bzovP508b9n7zbPdnVrn7iS73a2fnex374fZ/iRxeZc/O59tF+Ze03vIPuwQNH/2eNJ97A8Pdjpb3d1pfz2TO8jvVo5y6d7zS+PweHFwmh62/bPsdD9XyT0Ojufbzc7B3WjRnxz2XlLN3VrvsLlxcjU63RhvnWSGWf/8sX5w2ZxlD0+aV9lJdtI89NN3/bf8vOBlerXt7uOo3vdPX+o7s+fs7mXr6u0wu7ieZw4b17PdxkvnYP3u7ax1dfdQPd7da5YeZs9XtaP20+6lvZ66e8ls37X740mnWpscdI8Wpcnp2cugmn+u3GcvtkZH2Ze3zvP2w9n14fm8kR5P7g4e+7sZMhmZ55Psw/rxaGOj0CF7urQ1PehNO6njo+Hz+GH3fi9fud4aVhfjXman7k9espWzcb0+uDhp300P+kfdfqqfJavU2j5unV1Nr/vDdq0/fChfv52nro5SzeP9/vogs9bpdyaT89L1Q/s0U7sY3b8+X87Pr9uJ67uHo8PM+qlHaKrL2lt2NFy7au/VzhZrifrowL5O1a9Ke7mJd3n2uP+0yGbvu/bj3STj5V7ns4vsQ+Y+u7HeejzZWsu8NfL+7Gg/UXvsbg02qqmT86OHQq87OTuebVUKzc7rwbQxm6ay6w/z1M75pX1Y3p2d5u7eNp4rdsfzj3oPrdF6Z3bhb2Tfsi/jw/582M8Phjvbb+snV9XWFbkqyq+Zl9OD7NnJSdu7OEl72ezOa63WfEqfD6vdixw5F7OH1OzS9jLHuf4oO8ltbXeG89x5dVRv1dNXlVlv/PjSzKfKdnnezM6yL+3FQaNav9h9ShwOr7Yfmt74/Pxo4N23vdeT48P63cvp9nD/6O5+Pztqrr/lM61TL7U7bRbSfj9fSZXzF8/54dnW6/n4oJ89Pyoc2GfN9mDW3Xi+JzT11mS9vHbdKDyukbM73n4ZHayf753MEufegqCOLiHCC9njpjfsd97uTu4Wk7tc5ZRQErPWweT0fHc0K/euEuO3vcPqYJRre1v+WeI6Xbm/7p8sdhbTg0zlJLVP9kKm2kzdJc63D/bXOt3p687eWX1vu+U/dOr18+kot95+PWzm7bVK6eWqcLebqlwdHW83p+XuQ7V3ur9t5y6ernaej0/e0lvNnbvtg5zd8xbzzkvm2n9uPb3u5b2nx9LkIGN31tf69wfDXj53Xi5tNE/8xfZLY9i/K7UHu5OWv9grbeUP7e7JfD7eO9tNb5c7x4mrfGlv8HhcvbSr94n1/J09W/TnT6+9+hm5z3cX7aujg9TOq51Y2/IOKrXsZFYhNNTo6Wnc6b2dkfu8Xhs/72Tz1f3UMHPefl2cn5cKW+c7le7ksT+/TxxlS4O9+sbj6K0/Pmtt7Tf37td2O+dXXu7sYf2u9rRzXPDvd/aPH99aF735sFY7LSXeHgb1+sP21fVWZWNrP5t4qAwfa2ud89b+4LL8cPDw1vVT6e3ZyV1j3pkVFnvT09TL+fHd+f7haX3R63d7l53OdaWTru0+nJ1dTqp24nWYf7iq9857r+UNcjkfPJVKs1F7i1xlicy8v+jcPzXsVr78Nnm5m3ezaxtlf2NjdnrqzzuX23sP1cNc1T6bTS/693b+cNSuV9Kvw+wzIbA7u7PXNe8gf5i5SKVO93yCBLLj/YtpqnT61F9Ll472d0dH9/cXnYp9cbTljVIHnZfOfWawO1vbKZG7yn49yxw8vOb99fvu3tnhydFiOkyvnVzPZ9XE89tlf5pqTvYTR4nu/27qSpYdxYHgvb9ibh0THEDsTEQf2MEYsM0DjC8v2EHsYNavH/r1LH1U1EEKVYUys1RS0U2ROcYYHDq/YipqQGNQ5AdzWHwssNAJ+xktuQYeZSBnkuFGlfzebuUAXguoeFa9B+9gfRfMfJsc0wSok4uekARvq86d3mzKsMaDNJ/K9qmfspowfKYIMGjsLTYH2Y3EW+tdKP6V04GB4ST9EQHODt2tme4g5zRnQhbb6CHo00HCvHC6jQi1Uv67kXzdN9WTUTe1LY6DhwS5Ex3HawxhVMweixa8IXgVpuhKFcfFtobky16EjaZ2aChVJ5H1FFCpbOMdGy5V83IOLlnZ3uryCWQDOT9zMZjyNYwZuQWTqT71grmrzEA8olfWkkDlKesDCKeSgE0CYF7j2nbd+ISbra6UU0VpaG5iukOVFlhNoL0Fl62T93tJGNqBPZG77IskHVy5gOtPgRsvvrSwxMq9bXM4rpEDyEfOCtb08HDrvtujtp8BERpZ+qQKrAKXzXpEacGlzCAepiDZtYcH7SCKybpplJ13E01RMeT7xLLephhYaShhfZR8PCXpo5mqgMQvwiIKCmjinIZFdeFtF2FOEhKPGoOqD0LpRQg0WHNenfDYKQ+PnIBrb+/ovTA1Gc+YC+nOlOUBxvbIUHRs5Ml7LkT1wlX76nRCwGehRl8V6mDD6LFeyQvK0cnbTmkHbFjcz1kyCxbG8fW46rk0xMuF3tFqXO572bD79TbI1V1XJE5KJKrkmAtO5Bs6ONLhX2tu00HpuO70xBTjqKFimUyATUTYsSj21OSKJeB+adyGXNKLpCqifTlXkvKzH71pCRjooToWEgdIzz1piriFR8wGlZjksV0q8iQkFePk28Et7/WmEtXx0W7P2wj9pZI4KqMsfjzhHaGn+OIk9qClkZuZDInTPsQUVjycmRB9GFeZAyRFJF42j6fojNIxYNNOF8J1VhlyMLtGXIeh0Dcgkw7S03zEaNl83+TJeYdyB88AjJ4j7dNz+miW2z0r0fyjUY0rU0YLLoWvlLo2RIvUvFzzLs2Cu4v3rdIDneUkoV7aE9TrVRUjed19+SBPv9SnIOyTOHs1SosVPBmevHJsdKR6zXogHDEQwossi/M4hUtQdPqtgbUadCaaHkWHks/7CdDQi3nHjVwZa555gPrRMbyM/XZA5sFMIq6NNhE4626ZaM4SyfXRNKXQhYjjjR1h0bjG3KgtGXYybsnX9D606+rSHZZZdO4bExI8rqSaSKSJevZ9CymHs5TpY85HT+ScNoOegpvaUgPMN7W25aXo3KuYE9NjRk66YOvW6+CgjqzvnogBSsGbw4VEWwNZgT3djEsY6BnMmAOjB7ZwuRPSy91JB7e5Vi3X9iDlWHvv2vPoFZJxGjsVmAiZQHg51CJ2yKbaD4atOft6PMrv/9YwfX79K/9bI7xf42+/5av/L+/6lff+rwU8/0f20/RH+mXr4q/6ruRXJjP97WEH/ue38ucVxM+E4+fnjx/fP79m/fz8/tc/0/8Niow16bb5AQA=" 4519 out = open(RT_PATH, "wb") 4520 out.write("#!/usr/bin/env python3\n\n".encode("utf-8")+gzip.decompress(base64.b64decode(recovery_esptool))) 4521 out.close() 4522 except Exception as e: 4523 RNS.log("Error: Could not extract recovery ESP-Tool. The contained exception was:") 4524 RNS.log(str(e)) 4525 graceful_exit(181) 4526 4527 if __name__ == "__main__": 4528 main()