diff --git a/.gitignore b/.gitignore index f12761f4be..e3cfd1232c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1,3 @@ -# Compiled Sources -################### -*.o -*.a -*.elf -*.bin -*.map -*.hex -*.dis -*.exe - # Packages ############ @@ -20,10 +9,13 @@ ###################### *.swp -# Build directory +# Build directories ###################### -build/ -build-*/ +esp32/build/* +esp32/build-PYBYTES/* +mpy-cross/build/* +# This map file is generated here instead of the build folder... +mpy-cross/*.map # Test failure outputs ###################### @@ -50,8 +42,9 @@ secure_boot_signing_key.pem signature_verification_key.bin secure-bootloader-key.bin flash_encryption_key.bin - + .DS_Store org.eclipse.cdt.ui.prefs org.eclipse.cdt.core.prefs language.settings.xml +.idea diff --git a/esp32/frozen/LTE/sqnsupgrade.py b/esp32/frozen/LTE/sqnsupgrade.py index df94d841a1..4ada7da510 100644 --- a/esp32/frozen/LTE/sqnsupgrade.py +++ b/esp32/frozen/LTE/sqnsupgrade.py @@ -129,6 +129,7 @@ def return_code(self, rsp, debug=False): def wait_for_modem(self, send=True, expected=b'OK', echo_char=None): + self.__serial.read() rsp = b'' start = time.time() while True: @@ -339,13 +340,14 @@ def __get_wait_msg(self, load_fff=True): - def __run(self, file_path=None, baudrate=921600, port=None, resume=False, load_ffh=False, mirror=False, switch_ffh=False, bootrom=False, rgbled=0x050505, debug=False, pkgdebug=False, atneg=True, max_try=10, direct=True, atneg_only=False, info_only=False, expected_smod=None, verbose=False, load_fff=False, mtools=False, fc=False): + def __run(self, file_path=None, baudrate=921600, port=None, resume=False, load_ffh=False, mirror=False, switch_ffh=False, bootrom=False, rgbled=0x050505, debug=False, pkgdebug=False, atneg=True, max_try=10, direct=True, atneg_only=False, info_only=False, expected_smod=None, verbose=False, load_fff=False, mtools=False, fc=False, force_fff=False): self.__wait_msg = False mirror = True if atneg_only else mirror recover = True if atneg_only else load_ffh resume = True if mirror or recover or atneg_only or info_only else resume verbose = True if debug else verbose load_fff = False if bootrom and switch_ffh else load_fff + load_fff = True if force_fff else load_fff target_baudrate = baudrate baudrate = self.__modem_speed if self.__speed_detected else baudrate if debug: print('mirror? {} recover? {} resume? {} direct? {} atneg_only? {} bootrom? {} load_fff? {}'.format(mirror, recover, resume, direct, atneg_only, bootrom, load_fff)) @@ -410,9 +412,9 @@ def __run(self, file_path=None, baudrate=921600, port=None, resume=False, load_f reconnect_uart() return False if blobsize > 4194304: - if load_fff: + if load_fff and not force_fff: print("Firmware file is too big to load via FFF method. Using ON_THE_FLY") - load_fff = False + load_fff = False blob = open(file_path, "rb") if not load_ffh: @@ -550,7 +552,8 @@ def __run(self, file_path=None, baudrate=921600, port=None, resume=False, load_f raise OSError("Invalid answer '%s' from the device" % response) try: blob.close() - except: + except Exception as ex: + if debug: print('Exception: {}'.format(ex)) pass self.__serial.read() @@ -595,7 +598,8 @@ def __run(self, file_path=None, baudrate=921600, port=None, resume=False, load_f if start == True: try: blob.close() - except: + except Exception as ex: + if debug: print('Exception: {}'.format(ex)) pass self.__serial.read() if switch_ffh: @@ -612,15 +616,18 @@ def __run(self, file_path=None, baudrate=921600, port=None, resume=False, load_f else: try: blob.close() - except: + except Exception as ex: + if debug: print('Exception: {}'.format(ex)) pass print('Code download failed[1], aborting!') return False except Exception as ex: try: blob.close() - except: + except Exception as ex: + if debug: print('Exception: {}'.format(ex)) pass + print('Exception: {}'.format(ex)) print('Code download failed [2], aborting!') abort = True @@ -870,7 +877,7 @@ def success_message(self, port=None, verbose=False, debug=False): print("Here is the current firmware version:\n") self.show_info(port=port, verbose=verbose, debug=debug) - def upgrade(self, ffile, mfile=None, baudrate=921600, retry=False, resume=False, debug=False, pkgdebug=False, verbose=False, load_fff=True, load_only=False, mtools=False): + def upgrade(self, ffile, mfile=None, baudrate=921600, retry=False, resume=False, debug=False, pkgdebug=False, verbose=False, load_fff=True, load_only=False, mtools=False, force_fff=False): success = True if not retry and mfile is not None: if resume or self.__check_br(br_only=True, verbose=verbose, debug=debug): @@ -895,7 +902,7 @@ def upgrade(self, ffile, mfile=None, baudrate=921600, retry=False, resume=False, print('Unable to upgrade bootrom.') if debug: print('Success2? {}'.format(success)) if success: - if self.__run(file_path=ffile, resume=True if mfile is not None else resume, baudrate=baudrate, direct=False, debug=debug, pkgdebug=pkgdebug, verbose=verbose, load_fff=False if mfile else load_fff, mtools=mtools): + if self.__run(file_path=ffile, resume=True if mfile is not None else resume, baudrate=baudrate, direct=False, debug=debug, pkgdebug=pkgdebug, verbose=verbose, load_fff=False if mfile else load_fff, mtools=mtools, force_fff=force_fff): if self.__check_br(verbose=verbose, debug=debug): success = self.__run(bootrom=True, debug=debug, direct=False, pkgdebug=pkgdebug, verbose=verbose, load_fff=True) self.success_message(verbose=verbose, debug=debug) @@ -942,13 +949,13 @@ def upgrade_uart(self, ffh_mode=False, mfile=None, retry=False, resume=False, co def show_info(self, port=None, debug=False, verbose=False, fc=False): self.__run(port=port, debug=debug, info_only=True, verbose=verbose, fc=fc) - def upgrade_ext(self, port, ffile, mfile, resume=False, debug=False, pkgdebug=False, verbose=False, load_fff=True, fc=False): + def upgrade_ext(self, port, ffile, mfile, resume=False, debug=False, pkgdebug=False, verbose=False, load_fff=True, fc=False, force_fff=False): success = True if mfile is not None: success = False success = self.__run(file_path=mfile, load_ffh=True, port=port, debug=debug, pkgdebug=pkgdebug, verbose=verbose, fc=fc) if success: - if self.__run(file_path=ffile, resume=True if mfile is not None else resume, direct=False, port=port, debug=debug, pkgdebug=pkgdebug, verbose=verbose, load_fff=load_fff, fc=fc): + if self.__run(file_path=ffile, resume=True if mfile is not None else resume, direct=False, port=port, debug=debug, pkgdebug=pkgdebug, verbose=verbose, load_fff=load_fff, fc=fc, force_fff=force_fff): self.success_message(port=port, verbose=verbose, debug=debug) else: print('Unable to load updater from {}'.format(mfile)) @@ -984,7 +991,7 @@ def load(mfile, baudrate=921600, verbose=False, debug=False, hangup=False): print('Modem must be in recovery mode!') reconnect_uart() - def run(ffile, mfile=None, baudrate=921600, verbose=False, debug=False, load_fff=True, hangup=True): + def run(ffile, mfile=None, baudrate=921600, verbose=False, debug=False, load_fff=True, hangup=True, force_fff=False): print_welcome() retry = False resume = False @@ -1008,7 +1015,7 @@ def run(ffile, mfile=None, baudrate=921600, verbose=False, debug=False, load_fff mtools = True elif state == -1: detect_error() - success = sqnup.upgrade(ffile=ffile, mfile=mfile, baudrate=baudrate, retry=retry, resume=resume, debug=debug, pkgdebug=False, verbose=verbose, load_fff=load_fff, mtools=mtools) + success = sqnup.upgrade(ffile=ffile, mfile=mfile, baudrate=baudrate, retry=retry, resume=resume, debug=debug, pkgdebug=False, verbose=verbose, load_fff=load_fff, mtools=mtools, force_fff=force_fff) reconnect_uart() return success @@ -1072,11 +1079,11 @@ def state(verbose=False, debug=False, retry=5, hangup=False): return sqnup.detect_modem_state(debug=debug, hangup=hangup, retry=retry) else: - def run(port, ffile, mfile=None, resume=False, debug=False, verbose=False, load_fff=True, fc=False): + def run(port, ffile, mfile=None, resume=False, debug=False, verbose=False, load_fff=True, fc=False, force_fff=False): print_welcome() sqnup = sqnsupgrade() if sqnup.check_files(ffile, mfile, debug): - sqnup.upgrade_ext(port=port, ffile=ffile, mfile=mfile, resume=resume, debug=debug, pkgdebug=False, verbose=verbose, load_fff=load_fff) + sqnup.upgrade_ext(port=port, ffile=ffile, mfile=mfile, resume=resume, debug=debug, pkgdebug=False, verbose=verbose, load_fff=load_fff, force_fff=force_fff) def version(port, verbose=False, debug=False, fc=False): sqnup = sqnsupgrade() diff --git a/esp32/frozen/Pybytes/_OTA.py b/esp32/frozen/Pybytes/_OTA.py index e9cd71e37c..ab4f1d0b02 100644 --- a/esp32/frozen/Pybytes/_OTA.py +++ b/esp32/frozen/Pybytes/_OTA.py @@ -5,6 +5,20 @@ see the Pycom Licence v1.0 document supplied with this file, or available at https://www.pycom.io/opensource/licensing ''' +try: + from pybytes_debug import print_debug +except: + from _pybytes_debug import print_debug + +try: + from pybytes_constants import constants +except: + from _pybytes_constants import constants + +try: + import urequest +except: + import _urequest as urequest import network import socket @@ -18,11 +32,6 @@ import os from binascii import hexlify -try: - from pybytes_debug import print_debug -except: - from _pybytes_debug import print_debug - # Try to get version number # try: # from OTA_VERSION import VERSION @@ -40,6 +49,9 @@ def connect(self): def get_data(self, req, dest_path=None, hash=False): raise NotImplementedError() + def update_device_network_config(self, fcota, config): + raise NotImplementedError() + # OTA methods def get_current_version(self): @@ -56,9 +68,9 @@ def get_update_manifest(self): gc.collect() return manifest - def update(self): + def update(self, customManifest=None): try: - manifest = self.get_update_manifest() + manifest = self.get_update_manifest() if not customManifest else customManifest except Exception as e: print('Error reading the manifest, aborting: {}'.format(e)) return 0 @@ -68,36 +80,38 @@ def update(self): return 1 # Download new files and verify hashes - for f in manifest['new'] + manifest['update']: - # Upto 5 retries - for _ in range(5): - try: - self.get_file(f) - break - except Exception as e: - print(e) - msg = "Error downloading `{}` retrying..." - print(msg.format(f['URL'])) - return 0 - else: - raise Exception("Failed to download `{}`".format(f['URL'])) - - # Backup old files - # only once all files have been successfully downloaded - for f in manifest['update']: - self.backup_file(f) - - # Rename new files to proper name - for f in manifest['new'] + manifest['update']: - new_path = "{}.new".format(f['dst_path']) - dest_path = "{}".format(f['dst_path']) - - os.rename(new_path, dest_path) - - # `Delete` files no longer required - # This actually makes a backup of the files incase we need to roll back - for f in manifest['delete']: - self.delete_file(f) + if "new" in manifest and "update" in manifest: + for f in manifest['new'] + manifest['update']: + # Upto 5 retries + for _ in range(5): + try: + self.get_file(f) + break + except Exception as e: + print(e) + msg = "Error downloading `{}` retrying..." + print(msg.format(f['URL'])) + return 0 + else: + raise Exception("Failed to download `{}`".format(f['URL'])) + + # Backup old files + # only once all files have been successfully downloaded + for f in manifest['update']: + self.backup_file(f) + + # Rename new files to proper name + for f in manifest['new'] + manifest['update']: + new_path = "{}.new".format(f['dst_path']) + dest_path = "{}".format(f['dst_path']) + + os.rename(new_path, dest_path) + + if "delete" in manifest: + # `Delete` files no longer required + # This actually makes a backup of the files incase we need to roll back + for f in manifest['delete']: + self.delete_file(f) # Flash firmware if "firmware" in manifest: @@ -162,9 +176,12 @@ def delete_file(self, f): os.rename(dest_path, bak_path) def write_firmware(self, f): - hash = self.get_data(f['URL'].split("/", 3)[-1], - hash=True, - firmware=True) + # hash = + self.get_data( + f['URL'].split("/", 3)[-1], + hash=True, + firmware=True + ) # TODO: Add verification when released in future firmware @@ -258,6 +275,7 @@ def get_data(self, req, dest_path=None, hash=False, firmware=False): if fp is not None: fp.close() if firmware: + print_debug(6, 'ota_finish') pycom.ota_finish() except Exception as e: @@ -277,3 +295,47 @@ def get_data(self, req, dest_path=None, hash=False, firmware=False): return bytes(content) elif hash: return hash_val + + def update_device_network_config(self, fcota, config): + targetURL = '{}://{}/device/networks/{}'.format( + constants.__DEFAULT_PYCONFIG_PROTOCOL, constants.__DEFAULT_PYCONFIG_DOMAIN, config['device_id'] + ) + print_debug(6, "request device update URL: {}".format(targetURL)) + try: + pybytes_activation = urequest.get(targetURL, headers={'content-type': 'application/json'}) + responseDetails = pybytes_activation.json() + pybytes_activation.close() + print_debug(6, "Response Details: {}".format(responseDetails)) + self.update_network_config(responseDetails, fcota, config) + machine.reset() + except Exception as ex: + print_debug(1, "error while calling {}!: {}".format(targetURL, ex)) + + def update_network_config(self, letResp, fcota, config): + try: + if 'networkConfig' in letResp: + netConf = letResp['networkConfig'] + config['network_preferences'] = netConf['networkPreferences'] + if 'wifi' in netConf: + config['wifi'] = netConf['wifi'] + elif 'wifi' in config: + del config['wifi'] + + if 'lte' in netConf: + config['lte'] = netConf['lte'] + elif 'lte' in config: + del config['lte'] + + if 'lora' in netConf: + config['lora'] = { + 'otaa': netConf['lora']['otaa'], + 'abp': netConf['lora']['abp'] + } + elif 'lora' in config: + del config['lora'] + + json_string = ujson.dumps(config) + print_debug(1, "update_network_config : {}".format(json_string)) + fcota.update_file_content('/flash/pybytes_config.json', json_string) + except Exception as e: + print_debug(1, "error while updating network config pybytes_config.json! {}".format(e)) diff --git a/esp32/frozen/Pybytes/_pybytes.py b/esp32/frozen/Pybytes/_pybytes.py index 5ffb9dbb34..607e3a6d70 100644 --- a/esp32/frozen/Pybytes/_pybytes.py +++ b/esp32/frozen/Pybytes/_pybytes.py @@ -99,7 +99,7 @@ def connect_lora_abp(self, timeout, nanogateway=False): self.__check_init() return self.__pybytes_connection.connect_lora_abp(timeout, nanogateway) - def connect_lora_otta(self, timeout=15, nanogateway=False): + def connect_lora_otta(self, timeout=120, nanogateway=False): self.__check_init() return self.__pybytes_connection.connect_lora_otta(timeout, nanogateway) @@ -213,7 +213,7 @@ def isconnected(self): def connect(self): try: - lora_joining_timeout = 15 # seconds to wait for LoRa joining + lora_joining_timeout = 120 # seconds to wait for LoRa joining if self.__config_updated: if self.__check_config(): try: @@ -228,6 +228,7 @@ def connect(self): print("network_preferences are empty, set it up in /flash/pybytes_config.json first") # noqa for net in self.__conf['network_preferences']: + print_debug(3,'Attempting to connect with network {}'.format(net)) if net == 'lte' or net == 'nbiot': if self.connect_lte(): break diff --git a/esp32/frozen/Pybytes/_pybytes_config.py b/esp32/frozen/Pybytes/_pybytes_config.py index f3f392e7d6..4fffe655ec 100644 --- a/esp32/frozen/Pybytes/_pybytes_config.py +++ b/esp32/frozen/Pybytes/_pybytes_config.py @@ -6,7 +6,7 @@ available at https://www.pycom.io/opensource/licensing ''' -import pycom, time +import pycom, time, os, json, binascii, machine try: from pybytes_debug import print_debug except: @@ -24,11 +24,11 @@ def __init__(self): self.__pybytes_config = {} self.__pybytes_activation = None self.__pybytes_cli_activation = None + self.__pybytes_sigfox_registration = None self.__force_update = False def __write_config(self, filename='/flash/pybytes_config.json'): try: - import json cf = open(filename, 'w') cf.write(json.dumps(self.__pybytes_config)) cf.close() @@ -84,7 +84,6 @@ def __read_activation(self): except: import _urequest as urequest - import binascii, machine, os from uhashlib import sha512 print('Wifi connection established... activating device!') self.__pybytes_activation = None @@ -110,7 +109,6 @@ def __read_cli_activation(self, activation_token): except: import _urequest as urequest - import binascii, machine, os from uhashlib import sha512 print('Wifi connection established... activating device!') self.__pybytes_cli_activation = None @@ -124,8 +122,44 @@ def __read_cli_activation(self, activation_token): print('Failed to send activation request!') print_debug(2, ex) - def __process_cli_activation(self, filename): - import json + def __process_sigfox_registration(self, activation_token): + try: + import urequest + except: + import _urequest as urequest + + if hasattr(pycom, 'sigfox_info'): + if pycom.sigfox_info()[0] is None or pycom.sigfox_info()[1] is None or pycom.sigfox_info()[2] is None or pycom.sigfox_info()[3] is None: + try: + from network import LoRa + data = { "activationToken": activation_token['a'], "wmac": binascii.hexlify(machine.unique_id()).upper(), "smac": binascii.hexlify(LoRa(region=LoRa.EU868).mac())} + print_debug(99,'sigfox_registration: {}'.format(data)) + self.__pybytes_sigfox_registration = urequest.post('https://api.{}/v2/register-sigfox'.format(constants.__DEFAULT_DOMAIN), json=data, headers={'content-type': 'application/json'}) + start_time = time.time() + while (self.__pybytes_sigfox_registration is None or self.__pybytes_sigfox_registration.status_code != 200) and time.time() - start_time < 600: + time.sleep(30) + self.__pybytes_sigfox_registration = urequest.post('https://api.{}/v2/register-sigfox'.format(constants.__DEFAULT_DOMAIN), json=data, headers={'content-type': 'application/json'}) + if self.__pybytes_sigfox_registration is not None and self.__pybytes_sigfox_registration.status_code == 200: + jsigfox = self.__pybytes_sigfox_registration.json() + try: + self.__pybytes_sigfox_registration.close() + except: + pass + print_debug(99, 'Sigfox regisgtration response:\n{}'.format(jsigfox)) + return pycom.sigfox_info(id=jsigfox.get('sigfoxId'), pac=jsigfox.get('sigfoxPac'), public_key=jsigfox.get('sigfoxPubKey'), private_key=jsigfox.get('sigfoxPrivKey'), force=True) + else: + try: + self.__pybytes_sigfox_registration.close() + except: + pass + return False + except Exception as ex: + print('Failed to retrieve/program Sigfox credentials!') + print_debug(2, ex) + return False + return True + + def __process_cli_activation(self, filename, activation_token): try: if not self.__pybytes_cli_activation.status_code == 200: print_debug(3, 'Activation request returned {}.'.format(self.__pybytes_cli_activation.status_code)) @@ -134,8 +168,11 @@ def __process_cli_activation(self, filename): print_debug(99, 'Activation response:\n{}'.format(self.__pybytes_cli_activation.json())) self.__process_config(filename, self.__generate_cli_config()) self.__pybytes_cli_activation.close() - if self.__check_config() and self.__write_config(filename): - return self.__pybytes_config + if self.__process_sigfox_registration(activation_token): + if self.__check_config() and self.__write_config(filename): + return self.__pybytes_config + else: + print('Unable to provision Sigfox! Please try again.') return None except Exception as e: @@ -366,7 +403,7 @@ def cli_config(self, filename='/flash/pybytes_config.json', activation_info=None known_nets = [((activation_info['s'], activation_info['p']))] # noqa print_debug(3,'WLAN connected? {}'.format(wlan.isconnected())) - while not wlan.isconnected() and attempt < 3: + while not wlan.isconnected() and attempt < 10: attempt += 1 print_debug(3, "Wifi connection attempt: {}".format(attempt)) print_debug(3,'WLAN connected? {}'.format(wlan.isconnected())) @@ -376,7 +413,7 @@ def cli_config(self, filename='/flash/pybytes_config.json', activation_info=None available_nets = wlan.scan() for x in available_nets: print_debug(5, x) - time.sleep(1) + time.sleep(3) except: pass @@ -403,7 +440,7 @@ def cli_config(self, filename='/flash/pybytes_config.json', activation_info=None print("Error connecting using WIFI: %s" % e) return None self.__read_cli_activation(activation_info) - return self.__process_cli_activation(filename) + return self.__process_cli_activation(filename, activation_info) def read_config(self, filename='/flash/pybytes_config.json'): @@ -414,7 +451,6 @@ def read_config(self, filename='/flash/pybytes_config.json'): if not self.__force_update: try: - import json f = open(filename, 'r') jfile = f.read() f.close() @@ -444,7 +480,6 @@ def read_config(self, filename='/flash/pybytes_config.json'): pjfile = pf.read() pf.close() try: - import json pybytes_project = json.loads(pjfile.strip()) self.__pybytes_config.update(pybytes_project) print("Custom project configuration applied successfully") diff --git a/esp32/frozen/Pybytes/_pybytes_connection.py b/esp32/frozen/Pybytes/_pybytes_connection.py index fed930a49d..bc86eb6c59 100644 --- a/esp32/frozen/Pybytes/_pybytes_connection.py +++ b/esp32/frozen/Pybytes/_pybytes_connection.py @@ -32,13 +32,17 @@ import socket import struct import binascii +import pycom from machine import WDT class PybytesConnection: def __init__(self, config, message_callback): self.__conf = config - self.__host = config.get('server') + try: + self.__host = pycom.nvs_get('pybytes_server') + except: + self.__host = config.get('server') self.__ssl = config.get('ssl', False) self.__ssl_params = config.get('ssl_params', {}) self.__user_name = config.get('username') @@ -181,9 +185,13 @@ def connect_lte(self, reconnect=True, check_interval=0.5): try: from network import LTE time.sleep(3) - print_debug(1, 'LTE init(carrier={}, cid={})'.format(lte_cfg.get('carrier'), lte_cfg.get('cid', 1))) # noqa + if lte_cfg.get('carrier', 'standard') == 'standard': + carrier = None + else: + carrier = lte_cfg.get('carrier') + print_debug(1, 'LTE init(carrier={}, cid={})'.format(carrier, lte_cfg.get('cid', 1))) # noqa # instantiate the LTE object - self.lte = LTE(carrier=lte_cfg.get('carrier'), cid=lte_cfg.get('cid', 1)) + self.lte = LTE(carrier=carrier, cid=lte_cfg.get('cid', 1)) try: lte_type = lte_cfg.get('type') if len(lte_cfg.get('type')) > 0 else None except: @@ -265,9 +273,14 @@ def connect_lora_abp(self, lora_timeout, nanogateway): self.lora = LoRa(mode=LoRa.LORAWAN) self.lora.nvram_restore() - dev_addr = self.__conf['lora']['abp']['dev_addr'] - nwk_swkey = self.__conf['lora']['abp']['nwk_skey'] - app_swkey = self.__conf['lora']['abp']['app_skey'] + try: + dev_addr = self.__conf['lora']['abp']['dev_addr'] + nwk_swkey = self.__conf['lora']['abp']['nwk_skey'] + app_swkey = self.__conf['lora']['abp']['app_skey'] + except Exception as ex: + print("Invalid LoRaWAN ABP configuration!") + print_debug(1, ex) + return False timeout_ms = self.__conf.get('lora_timeout', lora_timeout) * 1000 dev_addr = struct.unpack(">l", binascii.unhexlify(dev_addr.replace(' ', '')))[0] # noqa @@ -275,13 +288,12 @@ def connect_lora_abp(self, lora_timeout, nanogateway): app_swkey = binascii.unhexlify(app_swkey.replace(' ', '')) try: - if not self.lora.has_joined: - print("Trying to join LoRa.ABP for %d seconds..." % lora_timeout) - self.lora.join( - activation=LoRa.ABP, - auth=(dev_addr, nwk_swkey, app_swkey), - timeout=timeout_ms - ) + print("Trying to join LoRa.ABP for %d seconds..." % self.__conf.get('lora_timeout', lora_timeout)) + self.lora.join( + activation=LoRa.ABP, + auth=(dev_addr, nwk_swkey, app_swkey), + timeout=timeout_ms + ) # if you want, uncomment this code, but timeout must be 0 # while not self.lora.has_joined(): @@ -296,7 +308,7 @@ def connect_lora_abp(self, lora_timeout, nanogateway): except Exception as e: message = str(e) if message == 'timed out': - print("LoRa connection timeout: %d seconds" % lora_timeout) + print("LoRa connection timeout: %d seconds" % self.__conf.get('lora_timeout', lora_timeout)) else: print_debug(3, 'Exception in LoRa connect: {}'.format(e)) return False @@ -312,9 +324,15 @@ def connect_lora_otta(self, lora_timeout, nanogateway): print("This device does not support LoRa connections: %s" % ex) return False - dev_eui = self.__conf['lora']['otaa']['app_device_eui'] - app_eui = self.__conf['lora']['otaa']['app_eui'] - app_key = self.__conf['lora']['otaa']['app_key'] + try: + dev_eui = self.__conf['lora']['otaa']['app_device_eui'] + app_eui = self.__conf['lora']['otaa']['app_eui'] + app_key = self.__conf['lora']['otaa']['app_key'] + except Exception as ex: + print("Invalid LoRaWAN OTAA configuration!") + print_debug(1, ex) + return False + timeout_ms = self.__conf.get('lora_timeout', lora_timeout) * 1000 if self.__conf.get('lora', {}).get('region') is not None: @@ -328,7 +346,7 @@ def connect_lora_otta(self, lora_timeout, nanogateway): app_key = binascii.unhexlify(app_key.replace(' ', '')) try: if not self.lora.has_joined(): - print("Trying to join LoRa.OTAA for %d seconds..." % lora_timeout) + print("Trying to join LoRa.OTAA for %d seconds..." % self.__conf.get('lora_timeout', lora_timeout)) self.lora.join( activation=LoRa.OTAA, auth=(dev_eui, app_eui, app_key), @@ -348,7 +366,7 @@ def connect_lora_otta(self, lora_timeout, nanogateway): except Exception as e: message = str(e) if message == 'timed out': - print("LoRa connection timeout: %d seconds" % lora_timeout) + print("LoRa connection timeout: %d seconds" % self.__conf.get('lora_timeout', lora_timeout)) else: print_debug(3, 'Exception in LoRa connect: {}'.format(e)) return False diff --git a/esp32/frozen/Pybytes/_pybytes_constants.py b/esp32/frozen/Pybytes/_pybytes_constants.py index 1b51ca8f23..f7efbce88c 100644 --- a/esp32/frozen/Pybytes/_pybytes_constants.py +++ b/esp32/frozen/Pybytes/_pybytes_constants.py @@ -70,6 +70,7 @@ class constants: __TYPE_PYBYTES = 0x0E __TYPE_RELEASE_INFO = 0x0B __TYPE_RELEASE_DEPLOY = 0x0A + __TYPE_DEVICE_NETWORK_DEPLOY = 0x0C __PYBYTES_PROTOCOL = ">B%ds" __PYBYTES_PROTOCOL_PING = ">B" __PYBYTES_INTERNAL_PROTOCOL = ">BBH" @@ -129,3 +130,7 @@ class constants: __DEFAULT_PYCONFIG_DOMAIN = pycom.nvs_get('pyconfig_host', 'pyconfig.eu-central-1.elasticbeanstalk.com') except: __DEFAULT_PYCONFIG_DOMAIN = 'pyconfig.eu-central-1.elasticbeanstalk.com' + try: + __DEFAULT_PYCONFIG_PROTOCOL = pycom.nvs_get('pyconfig_protocol', 'https') + except: + __DEFAULT_PYCONFIG_PROTOCOL = "https" diff --git a/esp32/frozen/Pybytes/_pybytes_protocol.py b/esp32/frozen/Pybytes/_pybytes_protocol.py index d71fc33392..ba11c509e3 100644 --- a/esp32/frozen/Pybytes/_pybytes_protocol.py +++ b/esp32/frozen/Pybytes/_pybytes_protocol.py @@ -159,6 +159,15 @@ def __process_recv_message(self, message): elif (message_type == constants.__TYPE_RELEASE_DEPLOY): self.deploy_new_release(body) + elif (message_type == constants.__TYPE_DEVICE_NETWORK_DEPLOY): + ota = WiFiOTA( + self.__conf['wifi']['ssid'], + self.__conf['wifi']['password'], + self.__conf['ota_server']['domain'], + self.__conf['ota_server']['port'] + ) + ota.update_device_network_config(self.__FCOTA, self.__conf) + elif (message_type == constants.__TYPE_OTA): ota = WiFiOTA( self.__conf['wifi']['ssid'], @@ -496,7 +505,7 @@ def __send_terminal_message(self, data): def enable_terminal(self): self.__terminal_enabled = True - #os.dupterm(self.__terminal) + # os.dupterm(self.__terminal) def __send_pybytes_message(self, command, pin_number, value): self.__send_message( @@ -522,14 +531,28 @@ def __send_pybytes_message_variable( def set_battery_level(self, battery_level): self.__battery_level = battery_level - def deploy_new_release(self, body): - application = self.__conf.get('application') - try: - body = ujson.loads(body.decode()) - except Exception as e: - print_debug(0, "error while loading body {}".format(e)) - return + def write_firmware(self, customManifest=None): + ota = WiFiOTA( + self.__conf['wifi']['ssid'], + self.__conf['wifi']['password'], + self.__conf['ota_server']['domain'], + self.__conf['ota_server']['port'] + ) + + if (self.__pybytes_connection.__connection_status == constants.__CONNECTION_STATUS_DISCONNECTED): # noqa + print_debug(5, 'Connecting to WiFi') + ota.connect() + print_debug(5, "Performing OTA") + result = ota.update(customManifest) + self.send_ota_response(result) + time.sleep(1.5) + if (result == 2): + # Reboot the device to run the new decode + machine.reset() + + def get_application_details(self, body): + application = self.__conf.get('application') if application is not None: if 'id' in application and application['id']: applicationID = application['id'] @@ -542,16 +565,24 @@ def deploy_new_release(self, body): else: applicationID = body['applicationId'] currentReleaseID = None + self.__conf['application'] = { + "id": "", + "release": { + "id": "", + "codeFilename": "", + "version": 0 + } + } + return (applicationID, currentReleaseID) - baseWebConfigUrl = 'https://{}'.format(constants.__DEFAULT_PYCONFIG_DOMAIN) - manifestURL = '{}/manifest.json?'.format(baseWebConfigUrl) - fileUrl = '{}/files?'.format(baseWebConfigUrl) - newReleaseID = body["releaseId"] - targetURL = '{}app_id={}&target_ver={}¤t_ver={}'.format( + def get_update_manifest(self, applicationID, newReleaseID, currentReleaseID): + manifestURL = '{}://{}/manifest.json?'.format(constants.__DEFAULT_PYCONFIG_PROTOCOL, constants.__DEFAULT_PYCONFIG_DOMAIN) + targetURL = '{}app_id={}&target_ver={}¤t_ver={}&device_id={}'.format( manifestURL, applicationID, newReleaseID, - currentReleaseID + currentReleaseID, + self.__conf['device_id'] ) print_debug(6, "manifest URL: {}".format(targetURL)) try: @@ -559,6 +590,7 @@ def deploy_new_release(self, body): letResp = pybytes_activation.json() pybytes_activation.close() print_debug(6, "letResp: {}".format(letResp)) + return letResp except Exception as ex: print_debug(1, "error while calling {}!: {}".format(targetURL, ex)) return @@ -567,6 +599,8 @@ def deploy_new_release(self, body): print_debug(1, letResp['errorMessage']) return + def update_files(self, letResp, applicationID, newReleaseID): + fileUrl = '{}://{}/files?'.format(constants.__DEFAULT_PYCONFIG_PROTOCOL, constants.__DEFAULT_PYCONFIG_DOMAIN) try: newFiles = letResp['newFiles'] updatedFiles = letResp['updatedFiles'] @@ -591,22 +625,14 @@ def deploy_new_release(self, body): fileContent = getFile.content self.__FCOTA.update_file_content(file['fileName'], fileContent) + def delete_files(self, letResp): if 'deletedFiles' in letResp: deletedFiles = letResp['deletedFiles'] for file in deletedFiles: self.__FCOTA.delete_file(file['fileName']) + def update_application_config(self, letResp, applicationID): try: - if application is None: - self.__conf['application'] = { - "id": "", - "release": { - "id": "", - "codeFilename": "", - "version": 0 - } - } - self.__conf['application']["id"] = applicationID self.__conf['application']['release']['id'] = letResp['target_version']['id'] self.__conf['application']['release']['codeFilename'] = letResp['target_version']['codeFileName'] @@ -621,4 +647,69 @@ def deploy_new_release(self, body): except Exception as e: print_debug(1, "error while updating pybytes_config.json! {}".format(e)) + def update_network_config(self, letResp): + try: + if 'networkConfig' in letResp: + netConf = letResp['networkConfig'] + self.__conf['network_preferences'] = netConf['networkPreferences'] + if 'wifi' in netConf: + self.__conf['wifi'] = netConf['wifi'] + elif 'wifi' in self.__conf: + del self.__conf['wifi'] + + if 'lte' in netConf: + self.__conf['lte'] = netConf['lte'] + elif 'lte' in self.__conf: + del self.__conf['lte'] + + if 'lora' in netConf: + self.__conf['lora'] = { + 'otaa': netConf['lora']['otaa'], + 'abp': netConf['lora']['abp'] + } + elif 'lora' in self.__conf: + del self.__conf['lora'] + + json_string = ujson.dumps(self.__conf) + print_debug(1, "update_network_config : {}".format(json_string)) + self.__FCOTA.update_file_content('/flash/pybytes_config.json', json_string) + except Exception as e: + print_debug(1, "error while updating network config pybytes_config.json! {}".format(e)) + + def update_firmware(self, body): + if "firmware" not in body: + print_debug(0, "no firmware to update") + return + version = body['firmware']["version"] + print_debug(0, "updating firmware to {}".format(version)) + customManifest = { + "firmware": { + "URL": "https://{}/downloads/appimg/firmware_{}_{}.bin".format( + constants.__DEFAULT_SW_HOST, + os.uname().sysname, + version), + } + } + self.write_firmware(customManifest) + + def deploy_new_release(self, body): + try: + body = ujson.loads(body.decode()) + print_debug(6, "body {}".format(body)) + except Exception as e: + print_debug(0, "error while loading body {}".format(e)) + return + + newReleaseID = body["releaseId"] + applicationID, currentReleaseID = self.get_application_details(body) + + letResp = self.get_update_manifest(applicationID, newReleaseID, currentReleaseID) + if not letResp: + return + + self.update_files(letResp, applicationID, newReleaseID) + self.delete_files(letResp) + self.update_application_config(letResp, applicationID) + self.update_network_config(letResp) + self.update_firmware(letResp) machine.reset() diff --git a/esp32/mods/modpycom.c b/esp32/mods/modpycom.c index 3616f0be8b..7b1f976659 100644 --- a/esp32/mods/modpycom.c +++ b/esp32/mods/modpycom.c @@ -66,6 +66,16 @@ void modpycom_init0(void) { } } +static bool is_empty(uint8_t* value, uint8_t size) { + bool ret_val = true; + for (int i=0; i < size; i++) { + if (value[i] != 0xFF) { + ret_val = false; + } + } + return ret_val; +} + static void modpycom_bootmgr(uint8_t boot_partition, uint8_t fs_type, uint8_t safeboot, bool reset) { bool update_part = false; bool update_fstype = false; @@ -135,7 +145,7 @@ STATIC mp_obj_t mod_pycom_heartbeat (mp_uint_t n_args, const mp_obj_t *args) { do { mp_hal_delay_ms(2); } while (!mperror_heartbeat_disable_done()); - } + } } else { return mp_obj_new_bool(mperror_is_heartbeat_enabled()); } @@ -149,7 +159,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_pycom_heartbeat_obj, 0, 1, mod_py STATIC mp_obj_t mod_pycom_rgb_led (mp_obj_t o_color) { #ifndef RGB_LED_DISABLE if (mperror_is_heartbeat_enabled()) { - nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, mpexception_os_request_not_possible)); + nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, mpexception_os_request_not_possible)); } uint32_t color = mp_obj_get_int(o_color); @@ -365,34 +375,34 @@ STATIC mp_obj_t mod_pycom_wifi_mode (mp_uint_t n_args, const mp_obj_t *args) { mode = mp_obj_get_int(args[0]); switch(mode) { - case 0: - config_set_wifi_mode ((uint8_t)PYCOM_WIFI_CONF_MODE_NONE, true); - break; - case 1: - config_set_wifi_mode ((uint8_t)PYCOM_WIFI_CONF_MODE_STA, true); - break; - case 2: - config_set_wifi_mode ((uint8_t)PYCOM_WIFI_CONF_MODE_AP, true); - break; - case 3: - config_set_wifi_mode ((uint8_t)PYCOM_WIFI_CONF_MODE_APSTA, true); - break; - default: - break; + case 0: + config_set_wifi_mode ((uint8_t)PYCOM_WIFI_CONF_MODE_NONE, true); + break; + case 1: + config_set_wifi_mode ((uint8_t)PYCOM_WIFI_CONF_MODE_STA, true); + break; + case 2: + config_set_wifi_mode ((uint8_t)PYCOM_WIFI_CONF_MODE_AP, true); + break; + case 3: + config_set_wifi_mode ((uint8_t)PYCOM_WIFI_CONF_MODE_APSTA, true); + break; + default: + break; } } else { mode = config_get_wifi_mode(); switch(mode) { - case PYCOM_WIFI_CONF_MODE_STA: - return MP_OBJ_NEW_SMALL_INT(1); - case PYCOM_WIFI_CONF_MODE_AP: - return MP_OBJ_NEW_SMALL_INT(2); - case PYCOM_WIFI_CONF_MODE_APSTA: - return MP_OBJ_NEW_SMALL_INT(3); - case PYCOM_WIFI_CONF_MODE_NONE: - default: - return MP_OBJ_NEW_SMALL_INT(0); + case PYCOM_WIFI_CONF_MODE_STA: + return MP_OBJ_NEW_SMALL_INT(1); + case PYCOM_WIFI_CONF_MODE_AP: + return MP_OBJ_NEW_SMALL_INT(2); + case PYCOM_WIFI_CONF_MODE_APSTA: + return MP_OBJ_NEW_SMALL_INT(3); + case PYCOM_WIFI_CONF_MODE_NONE: + default: + return MP_OBJ_NEW_SMALL_INT(0); } } return mp_const_none; @@ -564,12 +574,12 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_pycom_lte_modem_on_boot_obj, 0, 1 STATIC mp_obj_t mod_pycom_pybytes_lte_config (size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { enum { ARG_carrier, ARG_apn, ARG_cid, ARG_band, ARG_type, ARG_reset }; STATIC const mp_arg_t allowed_args[] = { - { MP_QSTR_carrier, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, - { MP_QSTR_apn, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, - { MP_QSTR_cid, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, - { MP_QSTR_band, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, - { MP_QSTR_type, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, - { MP_QSTR_reset, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_carrier, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_apn, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_cid, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_band, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_type, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_reset, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, }; @@ -633,7 +643,7 @@ STATIC mp_obj_t mod_pycom_pybytes_lte_config (size_t n_args, const mp_obj_t *pos return MP_OBJ_FROM_PTR(t); } else { - nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "Error this functionality is not yet supported!")); + nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "Error this functionality is not yet supported!")); } return mp_const_none; @@ -644,10 +654,10 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_KW(mod_pycom_pybytes_lte_config_obj, 0, mod_pycom STATIC mp_obj_t mod_pycom_bootmgr (size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { enum { ARG_boot_partition, ARG_fs_type, ARG_safeboot, ARG_status }; STATIC const mp_arg_t allowed_args[] = { - { MP_QSTR_boot_partition, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 255} }, - { MP_QSTR_fs_type, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 255} }, - { MP_QSTR_safeboot, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 255} }, - { MP_QSTR_reset, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} }, + { MP_QSTR_boot_partition, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 255} }, + { MP_QSTR_fs_type, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 255} }, + { MP_QSTR_safeboot, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 255} }, + { MP_QSTR_reset, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} }, }; mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; @@ -694,7 +704,7 @@ STATIC mp_obj_t mod_pycom_bootmgr (size_t n_args, const mp_obj_t *pos_args, mp_m return MP_OBJ_FROM_PTR(t); } else { - modpycom_bootmgr(args[ARG_boot_partition].u_int, args[ARG_fs_type].u_int, args[ARG_safeboot].u_int, args[3].u_bool); + modpycom_bootmgr(args[ARG_boot_partition].u_int, args[ARG_fs_type].u_int, args[ARG_safeboot].u_int, args[3].u_bool); } return mp_const_none; @@ -727,38 +737,38 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_pycom_get_free_heap_obj, mod_pycom_get_free #if (VARIANT == PYBYTES) STATIC mp_obj_t mod_pycom_pybytes_device_token (void) { - uint8_t pybytes_device_token[39]; - config_get_pybytes_device_token(pybytes_device_token); - return mp_obj_new_str((const char*)pybytes_device_token,strlen((const char*)pybytes_device_token)); + uint8_t pybytes_device_token[39]; + config_get_pybytes_device_token(pybytes_device_token); + return mp_obj_new_str((const char*)pybytes_device_token,strlen((const char*)pybytes_device_token)); } STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_pycom_pybytes_device_token_obj, mod_pycom_pybytes_device_token); STATIC mp_obj_t mod_pycom_pybytes_mqttServiceAddress (void) { - uint8_t pybytes_mqttServiceAddress[39]; - config_get_pybytes_mqttServiceAddress(pybytes_mqttServiceAddress); - return mp_obj_new_str((const char*)pybytes_mqttServiceAddress,strlen((const char*)pybytes_mqttServiceAddress)); + uint8_t pybytes_mqttServiceAddress[39]; + config_get_pybytes_mqttServiceAddress(pybytes_mqttServiceAddress); + return mp_obj_new_str((const char*)pybytes_mqttServiceAddress,strlen((const char*)pybytes_mqttServiceAddress)); } STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_pycom_pybytes_mqttServiceAddress_obj, mod_pycom_pybytes_mqttServiceAddress); STATIC mp_obj_t mod_pycom_pybytes_userId (void) { - uint8_t pybytes_userId[254]; - config_get_pybytes_userId(pybytes_userId); - return mp_obj_new_str((const char*)pybytes_userId,strlen((const char*)pybytes_userId)); + uint8_t pybytes_userId[254]; + config_get_pybytes_userId(pybytes_userId); + return mp_obj_new_str((const char*)pybytes_userId,strlen((const char*)pybytes_userId)); } STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_pycom_pybytes_userId_obj, mod_pycom_pybytes_userId); STATIC mp_obj_t mod_pycom_pybytes_network_preferences (void) { - uint8_t pybytes_network_preferences[54]; - config_get_pybytes_network_preferences(pybytes_network_preferences); - return mp_obj_new_str((const char*)pybytes_network_preferences,strlen((const char*)pybytes_network_preferences)); + uint8_t pybytes_network_preferences[54]; + config_get_pybytes_network_preferences(pybytes_network_preferences); + return mp_obj_new_str((const char*)pybytes_network_preferences,strlen((const char*)pybytes_network_preferences)); } STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_pycom_pybytes_network_preferences_obj, mod_pycom_pybytes_network_preferences); STATIC mp_obj_t mod_pycom_pybytes_extra_preferences (void) { - uint8_t pybytes_extra_preferences[99]; - config_get_pybytes_extra_preferences(pybytes_extra_preferences); - return mp_obj_new_str((const char*)pybytes_extra_preferences,strlen((const char*)pybytes_extra_preferences)); + uint8_t pybytes_extra_preferences[99]; + config_get_pybytes_extra_preferences(pybytes_extra_preferences); + return mp_obj_new_str((const char*)pybytes_extra_preferences,strlen((const char*)pybytes_extra_preferences)); } STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_pycom_pybytes_extra_preferences_obj, mod_pycom_pybytes_extra_preferences); @@ -784,54 +794,252 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_pycom_smartConfig_obj, 0, 1, mod_ #endif //(VARIANT == PYBYTES) + +// Helper function to return decimal value of a hexadecimal character coded in ASCII +STATIC uint8_t hex_from_char(const char c) { + + if((uint8_t)c >= '0' && (uint8_t)c <= '9') { + return c - '0'; + } + else if((uint8_t)c >= 'A' && (uint8_t)c <= 'F') { + return c - ('A' - 10); + } + else if((uint8_t)c >= 'a' && (uint8_t)c <= 'f') { + return c - ('a' - 10); + } + else { + // 16 is invalid, because in hexa allowed range is 0 - 15 + return 16; + } + +} + + +STATIC mp_obj_t mod_pycom_sigfox_info (size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_id, ARG_pac, ARG_public_key, ARG_private_key, ARG_force }; + STATIC const mp_arg_t allowed_args[] = { + { MP_QSTR_id, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_pac, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_public_key, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_private_key, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_force, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} } + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + uint8_t id[4]; + uint8_t pac[8]; + uint8_t public_key[16]; + uint8_t private_key[16]; + + config_get_sigfox_id(id); + config_get_sigfox_pac(pac); + config_get_sigfox_public_key(public_key); + config_get_sigfox_private_key(private_key); + + + if (args[ARG_id].u_obj == mp_const_none && args[ARG_pac].u_obj == mp_const_none && args[ARG_public_key].u_obj == mp_const_none && args[ARG_private_key].u_obj == mp_const_none) { + + mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(4, NULL)); + + t->items[ARG_id] = is_empty(id, sizeof(id)) ? mp_const_none:mp_obj_new_str((const char*)id, sizeof(id)); + t->items[ARG_pac] = is_empty(pac, sizeof(pac)) ? mp_const_none:mp_obj_new_str((const char*)pac, sizeof(pac)); + t->items[ARG_public_key] = is_empty(public_key, sizeof(public_key)) ? mp_const_none:mp_obj_new_str((const char*)public_key, sizeof(public_key)); + t->items[ARG_private_key] = is_empty(private_key, sizeof(private_key)) ? mp_const_none:mp_obj_new_str((const char*)private_key, sizeof(private_key)); + + return MP_OBJ_FROM_PTR(t); + + } else { + + // temporary array to store even the longest value from id, pac, public_key and private_key + uint8_t tmp_array[16]; + size_t length; + bool ret_val = false; + + if (args[ARG_id].u_obj != mp_const_none) { + + if ( args[ARG_force].u_bool == true || is_empty(id, sizeof(id)) ) { + + const char* id = mp_obj_str_get_data(args[ARG_id].u_obj, &length); + + if(length != 8) { + nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "ID must have length of 8!")); + } + + // Put together every 2 characters of the string (which are digits from 0 - 9 and a/A - f/F) into 1 bytes because the available space is half of the required one + for(int i = 0, j = 0; i < length; i = i+2) { + uint8_t lower_nibble = hex_from_char(id[i+1]); + uint8_t upper_nibble = hex_from_char(id[i]); + + if(lower_nibble == 16 || upper_nibble == 16) { + nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "ID must only contain hexadecimal digits!")); + } + + tmp_array[j] = lower_nibble | (upper_nibble << 4); + j++; + } + + ret_val = config_set_sigfox_id(tmp_array); + if (ret_val == false) { + return mp_const_false; + } + } else { + nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "Use force option to overwrite existing id!")); + } + } + if (args[ARG_pac].u_obj != mp_const_none) { + + if (args[ARG_force].u_bool == true || is_empty(pac, sizeof(pac))) { + + const char* pac = mp_obj_str_get_data(args[ARG_pac].u_obj, &length); + + if(length != 16) { + nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "PAC must have length of 16!")); + } + + // Put together every 2 characters of the string (which are digits from 0 - 9 and a/A - f/F) into 1 bytes because the available space is half of the required one + for(int i = 0, j = 0; i < length; i = i+2) { + uint8_t lower_nibble = hex_from_char(pac[i+1]); + uint8_t upper_nibble = hex_from_char(pac[i]); + + if(lower_nibble == 16 || upper_nibble == 16) { + nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "PAC must only contain hexadecimal digits!")); + } + + tmp_array[j] = lower_nibble | (upper_nibble << 4); + j++; + } + + ret_val = config_set_sigfox_pac(tmp_array); + if (ret_val == false) { + return mp_const_false; + } + } else { + nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "Use force option to overwrite existing pac!")); + } + } + if (args[ARG_public_key].u_obj != mp_const_none) { + + if (args[ARG_force].u_bool == true || is_empty(public_key, sizeof(public_key))) { + + const char* public_key = mp_obj_str_get_data(args[ARG_public_key].u_obj, &length); + + if(length != 32) { + nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "Public Key must have length of 32!")); + } + + // Put together every 2 characters of the string (which are digits from 0 - 9 and a/A - f/F) into 1 bytes because the available space is half of the required one + for(int i = 0, j = 0; i < length; i = i+2) { + uint8_t lower_nibble = hex_from_char(public_key[i+1]); + uint8_t upper_nibble = hex_from_char(public_key[i]); + + if(lower_nibble == 16 || upper_nibble == 16) { + nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "Public Key must only contain hexadecimal digits!")); + } + + tmp_array[j] = lower_nibble | (upper_nibble << 4); + j++; + } + + ret_val = config_set_sigfox_public_key(tmp_array); + if (ret_val == false) { + return mp_const_false; + } + } else { + nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "Use force option to overwrite existing public key!")); + } + + } + if (args[ARG_private_key].u_obj != mp_const_none) { + + if (args[ARG_force].u_bool == true || is_empty(private_key, sizeof(private_key))) { + + const char* private_key = mp_obj_str_get_data(args[ARG_private_key].u_obj, &length); + + if(length != 32) { + nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "Private Key must have length of 32")); + } + + // Put together every 2 characters of the string (which are digits from 0 - 9 and a/A - f/F) into 1 bytes because the available space is half of the required one + for(int i = 0, j = 0; i < length; i = i+2) { + uint8_t lower_nibble = hex_from_char(private_key[i+1]); + uint8_t upper_nibble = hex_from_char(private_key[i]); + + if(lower_nibble == 16 || upper_nibble == 16) { + nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "Private Key must only contain hexadecimal digits!")); + } + + tmp_array[j] = lower_nibble | (upper_nibble << 4); + j++; + } + + ret_val = config_set_sigfox_private_key(tmp_array); + if (ret_val == false) { + return mp_const_false; + } + } else { + nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "Use force option to overwrite existing private key!")); + } + } + return mp_const_true; + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(mod_pycom_sigfox_info_obj, 0, mod_pycom_sigfox_info); + STATIC const mp_map_elem_t pycom_module_globals_table[] = { - { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_pycom) }, - { MP_OBJ_NEW_QSTR(MP_QSTR_heartbeat), (mp_obj_t)&mod_pycom_heartbeat_obj }, - { MP_OBJ_NEW_QSTR(MP_QSTR_rgbled), (mp_obj_t)&mod_pycom_rgb_led_obj }, - { MP_OBJ_NEW_QSTR(MP_QSTR_ota_start), (mp_obj_t)&mod_pycom_ota_start_obj }, - { MP_OBJ_NEW_QSTR(MP_QSTR_ota_write), (mp_obj_t)&mod_pycom_ota_write_obj }, - { MP_OBJ_NEW_QSTR(MP_QSTR_ota_finish), (mp_obj_t)&mod_pycom_ota_finish_obj }, - { MP_OBJ_NEW_QSTR(MP_QSTR_ota_verify), (mp_obj_t)&mod_pycom_ota_verify_obj }, - { MP_OBJ_NEW_QSTR(MP_QSTR_ota_slot), (mp_obj_t)&mod_pycom_ota_slot_obj }, - { MP_OBJ_NEW_QSTR(MP_QSTR_pulses_get), (mp_obj_t)&mod_pycom_pulses_get_obj }, - { MP_OBJ_NEW_QSTR(MP_QSTR_nvs_set), (mp_obj_t)&mod_pycom_nvs_set_obj }, - { MP_OBJ_NEW_QSTR(MP_QSTR_nvs_get), (mp_obj_t)&mod_pycom_nvs_get_obj }, - { MP_OBJ_NEW_QSTR(MP_QSTR_nvs_erase), (mp_obj_t)&mod_pycom_nvs_erase_obj }, - { MP_OBJ_NEW_QSTR(MP_QSTR_nvs_erase_all), (mp_obj_t)&mod_pycom_nvs_erase_all_obj }, - { MP_OBJ_NEW_QSTR(MP_QSTR_wifi_on_boot), (mp_obj_t)&mod_pycom_wifi_on_boot_obj }, - { MP_OBJ_NEW_QSTR(MP_QSTR_wdt_on_boot), (mp_obj_t)&mod_pycom_wdt_on_boot_obj }, - { MP_OBJ_NEW_QSTR(MP_QSTR_wdt_on_boot_timeout), (mp_obj_t)&mod_pycom_wdt_on_boot_timeout_obj }, - { MP_OBJ_NEW_QSTR(MP_QSTR_heartbeat_on_boot), (mp_obj_t)&mod_pycom_heartbeat_on_boot_obj }, - { MP_OBJ_NEW_QSTR(MP_QSTR_lte_modem_en_on_boot), (mp_obj_t)&mod_pycom_lte_modem_on_boot_obj }, - { MP_OBJ_NEW_QSTR(MP_QSTR_get_free_heap), (mp_obj_t)&mod_pycom_get_free_heap_obj }, - { MP_OBJ_NEW_QSTR(MP_QSTR_wifi_ssid_sta), (mp_obj_t)&mod_pycom_wifi_ssid_sta_obj }, - { MP_OBJ_NEW_QSTR(MP_QSTR_wifi_ssid_ap), (mp_obj_t)&mod_pycom_wifi_ssid_ap_obj }, - { MP_OBJ_NEW_QSTR(MP_QSTR_wifi_pwd_sta), (mp_obj_t)&mod_pycom_wifi_pwd_sta_obj }, - { MP_OBJ_NEW_QSTR(MP_QSTR_wifi_pwd_ap), (mp_obj_t)&mod_pycom_wifi_pwd_ap_obj }, - { MP_OBJ_NEW_QSTR(MP_QSTR_wifi_mode_on_boot), (mp_obj_t)&mod_pycom_wifi_mode_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_pycom) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_heartbeat), (mp_obj_t)&mod_pycom_heartbeat_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_rgbled), (mp_obj_t)&mod_pycom_rgb_led_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_ota_start), (mp_obj_t)&mod_pycom_ota_start_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_ota_write), (mp_obj_t)&mod_pycom_ota_write_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_ota_finish), (mp_obj_t)&mod_pycom_ota_finish_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_ota_verify), (mp_obj_t)&mod_pycom_ota_verify_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_ota_slot), (mp_obj_t)&mod_pycom_ota_slot_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_pulses_get), (mp_obj_t)&mod_pycom_pulses_get_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_nvs_set), (mp_obj_t)&mod_pycom_nvs_set_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_nvs_get), (mp_obj_t)&mod_pycom_nvs_get_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_nvs_erase), (mp_obj_t)&mod_pycom_nvs_erase_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_nvs_erase_all), (mp_obj_t)&mod_pycom_nvs_erase_all_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_wifi_on_boot), (mp_obj_t)&mod_pycom_wifi_on_boot_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_wdt_on_boot), (mp_obj_t)&mod_pycom_wdt_on_boot_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_wdt_on_boot_timeout), (mp_obj_t)&mod_pycom_wdt_on_boot_timeout_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_heartbeat_on_boot), (mp_obj_t)&mod_pycom_heartbeat_on_boot_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_lte_modem_en_on_boot), (mp_obj_t)&mod_pycom_lte_modem_on_boot_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_get_free_heap), (mp_obj_t)&mod_pycom_get_free_heap_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_wifi_ssid_sta), (mp_obj_t)&mod_pycom_wifi_ssid_sta_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_wifi_ssid_ap), (mp_obj_t)&mod_pycom_wifi_ssid_ap_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_wifi_pwd_sta), (mp_obj_t)&mod_pycom_wifi_pwd_sta_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_wifi_pwd_ap), (mp_obj_t)&mod_pycom_wifi_pwd_ap_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_wifi_mode_on_boot), (mp_obj_t)&mod_pycom_wifi_mode_obj }, + +#if defined(FIPY) || defined(LOPY4) || defined(SIPY) + { MP_OBJ_NEW_QSTR(MP_QSTR_sigfox_info), (mp_obj_t)&mod_pycom_sigfox_info_obj }, +#endif + #if (VARIANT == PYBYTES) - { MP_OBJ_NEW_QSTR(MP_QSTR_pybytes_device_token), (mp_obj_t)&mod_pycom_pybytes_device_token_obj }, - { MP_OBJ_NEW_QSTR(MP_QSTR_pybytes_mqttServiceAddress), (mp_obj_t)&mod_pycom_pybytes_mqttServiceAddress_obj }, - { MP_OBJ_NEW_QSTR(MP_QSTR_pybytes_userId), (mp_obj_t)&mod_pycom_pybytes_userId_obj }, - { MP_OBJ_NEW_QSTR(MP_QSTR_pybytes_network_preferences), (mp_obj_t)&mod_pycom_pybytes_network_preferences_obj }, - { MP_OBJ_NEW_QSTR(MP_QSTR_pybytes_extra_preferences), (mp_obj_t)&mod_pycom_pybytes_extra_preferences_obj }, - { MP_OBJ_NEW_QSTR(MP_QSTR_pybytes_force_update), (mp_obj_t)&mod_pycom_pybytes_force_update_obj }, - { MP_OBJ_NEW_QSTR(MP_QSTR_smart_config_on_boot), (mp_obj_t)&mod_pycom_smartConfig_obj }, - { MP_OBJ_NEW_QSTR(MP_QSTR_pybytes_lte_config), (mp_obj_t)&mod_pycom_pybytes_lte_config_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_pybytes_device_token), (mp_obj_t)&mod_pycom_pybytes_device_token_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_pybytes_mqttServiceAddress), (mp_obj_t)&mod_pycom_pybytes_mqttServiceAddress_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_pybytes_userId), (mp_obj_t)&mod_pycom_pybytes_userId_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_pybytes_network_preferences), (mp_obj_t)&mod_pycom_pybytes_network_preferences_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_pybytes_extra_preferences), (mp_obj_t)&mod_pycom_pybytes_extra_preferences_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_pybytes_force_update), (mp_obj_t)&mod_pycom_pybytes_force_update_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_smart_config_on_boot), (mp_obj_t)&mod_pycom_smartConfig_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_pybytes_lte_config), (mp_obj_t)&mod_pycom_pybytes_lte_config_obj }, #endif //(VARIANT == PYBYTES) - { MP_OBJ_NEW_QSTR(MP_QSTR_bootmgr), (mp_obj_t)&mod_pycom_bootmgr_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_bootmgr), (mp_obj_t)&mod_pycom_bootmgr_obj }, - // class constants - { MP_OBJ_NEW_QSTR(MP_QSTR_FACTORY), MP_OBJ_NEW_SMALL_INT(0) }, - { MP_OBJ_NEW_QSTR(MP_QSTR_OTA_0), MP_OBJ_NEW_SMALL_INT(1) }, - { MP_OBJ_NEW_QSTR(MP_QSTR_FAT), MP_OBJ_NEW_SMALL_INT(0) }, - { MP_OBJ_NEW_QSTR(MP_QSTR_LittleFS), MP_OBJ_NEW_SMALL_INT(1) }, + // class constants + { MP_OBJ_NEW_QSTR(MP_QSTR_FACTORY), MP_OBJ_NEW_SMALL_INT(0) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_OTA_0), MP_OBJ_NEW_SMALL_INT(1) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_FAT), MP_OBJ_NEW_SMALL_INT(0) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_LittleFS), MP_OBJ_NEW_SMALL_INT(1) }, }; STATIC MP_DEFINE_CONST_DICT(pycom_module_globals, pycom_module_globals_table); const mp_obj_module_t pycom_module = { - .base = { &mp_type_module }, - .globals = (mp_obj_dict_t*)&pycom_module_globals, + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&pycom_module_globals, }; diff --git a/esp32/pycom_version.h b/esp32/pycom_version.h index 2f9f66bfe2..ffcc8015fb 100644 --- a/esp32/pycom_version.h +++ b/esp32/pycom_version.h @@ -10,14 +10,14 @@ #ifndef VERSION_H_ #define VERSION_H_ -#define SW_VERSION_NUMBER "1.20.1.r1" +#define SW_VERSION_NUMBER "1.20.1.r2" #define LORAWAN_VERSION_NUMBER "1.0.2" #define SIGFOX_VERSION_NUMBER "1.0.1" #if (VARIANT == PYBYTES) -#define PYBYTES_VERSION_NUMBER "1.1.3" +#define PYBYTES_VERSION_NUMBER "1.3.0" #endif #endif /* VERSION_H_ */