diff --git a/esp32/Makefile b/esp32/Makefile index a92ab5d53a..616720ccd9 100644 --- a/esp32/Makefile +++ b/esp32/Makefile @@ -14,7 +14,7 @@ ifeq ($(wildcard boards/$(BOARD)/.),) $(error Invalid BOARD specified) endif -IDF_VERSION=3.3.1 +IDF_HASH=d072c55 TARGET ?= boot_app diff --git a/esp32/application.mk b/esp32/application.mk index 70c0f7d96d..941fd19c57 100644 --- a/esp32/application.mk +++ b/esp32/application.mk @@ -186,6 +186,7 @@ APP_UTIL_SRC_C = $(addprefix util/,\ timeutils.c \ esp32chipinfo.c \ pycom_general_util.c \ + str_utils.c \ ) APP_FATFS_SRC_C = $(addprefix fatfs/src/,\ @@ -819,7 +820,7 @@ $(OBJ): | $(GEN_PINS_HDR) # Check Dependencies (IDF version, Frozen code and IDF LIBS) CHECK_DEP: - $(Q) bash tools/idfVerCheck.sh $(IDF_PATH) "$(IDF_VERSION)" + $(Q) bash tools/idfVerCheck.sh $(IDF_PATH) "$(IDF_HASH)" $(Q) bash tools/mpy-build-check.sh $(BOARD) $(BTYPE) $(VARIANT) $(Q) $(PYTHON) check_secure_boot.py --SECURE $(SECURE) ifeq ($(COPY_IDF_LIB), 1) diff --git a/esp32/frozen/Pybytes/_OTA.py b/esp32/frozen/Pybytes/_OTA.py index 662a961e5f..12cf5aee58 100644 --- a/esp32/frozen/Pybytes/_OTA.py +++ b/esp32/frozen/Pybytes/_OTA.py @@ -57,20 +57,24 @@ def update_device_network_config(self, fcota, config): def get_current_version(self): return os.uname().release - def get_update_manifest(self): + def get_update_manifest(self, fwtype=None, token=None): current_version = self.get_current_version() sysname = os.uname().sysname wmac = hexlify(machine.unique_id()).decode('ascii') - request_template = "manifest.json?current_ver={}&sysname={}&wmac={}&ota_slot={}" - req = request_template.format(current_version, sysname, wmac, hex(pycom.ota_slot())) + if fwtype == 'pymesh': + request_template = "manifest.json?current_ver={}&sysname={}&token={}&ota_slot={}&wmac={}&fwtype={}" + req = request_template.format(current_version, sysname, token, hex(pycom.ota_slot()), wmac.upper(), fwtype) + else: + request_template = "manifest.json?current_ver={}&sysname={}&wmac={}&ota_slot={}" + req = request_template.format(current_version, sysname, wmac, hex(pycom.ota_slot())) manifest_data = self.get_data(req).decode() manifest = ujson.loads(manifest_data) gc.collect() return manifest - def update(self, customManifest=None): + def update(self, customManifest=None, fwtype=None, token=None): try: - manifest = self.get_update_manifest() if not customManifest else customManifest + manifest = self.get_update_manifest(fwtype, token) if not customManifest else customManifest except Exception as e: print('Error reading the manifest, aborting: {}'.format(e)) return 0 @@ -240,7 +244,6 @@ def get_data(self, req, dest_path=None, hash=False, firmware=False): fp = open(dest_path, 'wb') if firmware: - print('start') pycom.ota_start() h = uhashlib.sha1() diff --git a/esp32/frozen/Pybytes/_coap.py b/esp32/frozen/Pybytes/_coap.py new file mode 100644 index 0000000000..234fccd3ac --- /dev/null +++ b/esp32/frozen/Pybytes/_coap.py @@ -0,0 +1,36 @@ +''' +Copyright (c) 2020, Pycom Limited. +This software is licensed under the GNU GPL version 3 or any +later version, with permitted additional terms. For more information +see the Pycom Licence v1.0 document supplied with this file, or +available at https://www.pycom.io/opensource/licensing +''' + +from network import WLAN +from network import Coap +# import uselect +# import _thread +# import machine + + +class COAPClient(): + def __init__(self, target_server, port): + self.__coap_server = target_server + self.__coap_server_port = port + + wlan = WLAN(mode=WLAN.STA) + self.__device_ip = wlan.ifconfig()[0] + + Coap.init(str(wlan.ifconfig()[0]), service_discovery=False) + + def send_coap_message(self, message, method, uri_path, token, include_options=True): + message_id = Coap.send_request( + self.__coap_server, + method, + uri_port=int(self.__coap_server_port), + uri_path=uri_path, + payload=message, + token=token, + include_options=include_options + ) + return message_id diff --git a/esp32/frozen/Pybytes/_periodical_pin.py b/esp32/frozen/Pybytes/_periodical_pin.py new file mode 100644 index 0000000000..df2463e552 --- /dev/null +++ b/esp32/frozen/Pybytes/_periodical_pin.py @@ -0,0 +1,20 @@ +''' +Copyright (c) 2020, Pycom Limited. +This software is licensed under the GNU GPL version 3 or any +later version, with permitted additional terms. For more information +see the Pycom Licence v1.0 document supplied with this file, or +available at https://www.pycom.io/opensource/licensing +''' + + +class PeriodicalPin: + + TYPE_DIGITAL = 0 + TYPE_ANALOG = 1 + TYPE_VIRTUAL = 2 + + def __init__(self, persistent, pin_number, message_type, message, pin_type): + self.pin_number = pin_number + self.message_type = message_type + self.message = message + self.pin_type = pin_type diff --git a/esp32/frozen/Pybytes/_pybytes.py b/esp32/frozen/Pybytes/_pybytes.py index 6f109c931d..ba191c21db 100644 --- a/esp32/frozen/Pybytes/_pybytes.py +++ b/esp32/frozen/Pybytes/_pybytes.py @@ -6,36 +6,35 @@ available at https://www.pycom.io/opensource/licensing ''' -import os, json, binascii -import time, pycom +import os +import json +import time +import pycom import sys from network import WLAN -from machine import Timer +from binascii import hexlify, a2b_base64 +from machine import Timer, deepsleep, pin_sleep_wakeup, unique_id + +try: + from periodical_pin import PeriodicalPin +except: + from _periodical_pin import PeriodicalPin try: from pybytes_debug import print_debug except: from _pybytes_debug import print_debug - -class __PERIODICAL_PIN: - TYPE_DIGITAL = 0 - TYPE_ANALOG = 1 - TYPE_VIRTUAL = 2 - - def __init__( - self, persistent, pin_number, message_type, message, pin_type - ): - self.pin_number = pin_number - self.message_type = message_type - self.message = message - self.pin_type = pin_type +try: + from pybytes_config_reader import PybytesConfigReader +except: + from _pybytes_config_reader import PybytesConfigReader class Pybytes: - WAKEUP_ALL_LOW = const(0) - WAKEUP_ANY_HIGH = const(1) + WAKEUP_ALL_LOW = const(0) # noqa: F821 + WAKEUP_ANY_HIGH = const(1) # noqa: F821 def __init__(self, config, activation=False, autoconnect=False): self.__frozen = globals().get('__name__') == '_pybytes' @@ -45,9 +44,11 @@ def __init__(self, config, activation=False, autoconnect=False): self.__pybytes_connection = None self.__smart_config = False self.__conf = {} + self.__pymesh = None if not self.__activation: self.__conf = config + self.__conf_reader = PybytesConfigReader(config) pycom.wifi_on_boot(False, True) self.__check_dump_ca() @@ -64,12 +65,13 @@ def __create_pybytes_connection(self, conf): from pybytes_connection import PybytesConnection except: from _pybytes_connection import PybytesConnection + self.__pybytes_connection = PybytesConnection(conf, self.__recv_message) def __check_config(self): try: print_debug(99, self.__conf) - return (len(self.__conf.get('username','')) > 4 and len(self.__conf.get('device_id', '')) >= 36 and len(self.__conf.get('server', '')) > 4) + return (len(self.__conf.get('username', '')) > 4 and len(self.__conf.get('device_id', '')) >= 36 and len(self.__conf.get('server', '')) > 4) except Exception as e: print_debug(4, 'Exception in __check_config!\n{}'.format(e)) return False @@ -86,17 +88,23 @@ def __check_dump_ca(self): print_debug(4, ' ssl_params={} '.format(ssl_params)) if self.__conf.get('dump_ca', False): try: - stat = os.stat(ssl_params.get('ca_certs')) # noqa + os.stat(ssl_params.get('ca_certs')) except: self.dump_ca(ssl_params.get('ca_certs')) def connect_wifi(self, reconnect=True, check_interval=0.5): self.__check_init() - return self.__pybytes_connection.connect_wifi(reconnect, check_interval) + if self.__pybytes_connection.connect_wifi(reconnect, check_interval): + self.__pybytes_connection.communication_protocol('wifi') + return True + return False def connect_lte(self): self.__check_init() - return self.__pybytes_connection.connect_lte() + if self.__pybytes_connection.connect_lte(): + self.__pybytes_connection.communication_protocol('lte') + return True + return False def connect_lora_abp(self, timeout, nanogateway=False): self.__check_init() @@ -142,27 +150,33 @@ def send_analog_pin_value(self, persistent, pin): self.__check_init() self.__pybytes_connection.__pybytes_protocol.send_pybytes_analog_value(pin) - def send_signal(self, signal_number, value): + def send_node_signal(self, signal_number, value, token): self.__check_init() - self.__pybytes_connection.__pybytes_protocol.send_pybytes_custom_method_values(signal_number, [value]) + topic = 'br/{}'.format(token) + self.__pybytes_connection.__pybytes_protocol.send_pybytes_custom_method_values(signal_number, [value], topic) - def send_virtual_pin_value(self, persistent, pin, value): + def send_signal(self, signal_number, value): self.__check_init() - print("This function is deprecated and will be removed in the future. Use send_signal(signalNumber, value)") - self.send_signal(pin, value) + if self.__pymesh: + self.__pymesh.unpack_pymesh_message(signal_number, value) + else: + self.__pybytes_connection.__pybytes_protocol.send_pybytes_custom_method_values(signal_number, [value]) def __periodical_pin_callback(self, periodical_pin): - self.__check_init() - if (periodical_pin.pin_type == __PERIODICAL_PIN.TYPE_DIGITAL): - self.send_digital_pin_value(periodical_pin.persistent, periodical_pin.pin_number, None) - elif (periodical_pin.pin_type == __PERIODICAL_PIN.TYPE_ANALOG): - self.send_analog_pin_value(periodical_pin.persistent, periodical_pin.pin_number) + self.__check_init() + if (periodical_pin.pin_type == PeriodicalPin.TYPE_DIGITAL): + self.send_digital_pin_value( + periodical_pin.persistent, periodical_pin.pin_number, None + ) + elif (periodical_pin.pin_type == PeriodicalPin.TYPE_ANALOG): + self.send_analog_pin_value( + periodical_pin.persistent, periodical_pin.pin_number + ) def register_periodical_digital_pin_publish(self, persistent, pin_number, pull_mode, period): self.__check_init() self.send_digital_pin_value(pin_number, pull_mode) - periodical_pin = __PERIODICAL_PIN(pin_number, None, None, - __PERIODICAL_PIN.TYPE_DIGITAL) + periodical_pin = PeriodicalPin(pin_number, None, None, PeriodicalPin.TYPE_DIGITAL) Timer.Alarm( self.__periodical_pin_callback, period, arg=periodical_pin, periodic=True @@ -171,8 +185,8 @@ def register_periodical_digital_pin_publish(self, persistent, pin_number, pull_m def register_periodical_analog_pin_publish(self, pin_number, period): self.__check_init() self.send_analog_pin_value(pin_number) - periodical_pin = __PERIODICAL_PIN( - pin_number, None, None, __PERIODICAL_PIN.TYPE_ANALOG + periodical_pin = PeriodicalPin( + pin_number, None, None, PeriodicalPin.TYPE_ANALOG ) Timer.Alarm( self.__periodical_pin_callback, period, arg=periodical_pin, @@ -213,7 +227,6 @@ def isconnected(self): except: return False - def connect(self): try: lora_joining_timeout = 120 # seconds to wait for LoRa joining @@ -224,10 +237,10 @@ def connect(self): self.__check_init() if not self.__conf.get('network_preferences'): - print("network_preferences are empty, set it up in /flash/pybytes_config.json first") # noqa + print("network_preferences are empty, set it up in /flash/pybytes_config.json first") for net in self.__conf['network_preferences']: - print_debug(3,'Attempting to connect with network {}'.format(net)) + print_debug(3, 'Attempting to connect with network {}'.format(net)) if net == 'lte' or net == 'nbiot': if self.connect_lte(): break @@ -244,19 +257,32 @@ def connect(self): if self.connect_sigfox(): break - import time time.sleep(.1) if self.is_connected(): if self.__frozen: - print('Pybytes connected successfully (using the built-in pybytes library)') # noqa + print('Pybytes connected successfully (using the built-in pybytes library)') else: - print('Pybytes connected successfully (using a local pybytes library)') # noqa + print('Pybytes connected successfully (using a local pybytes library)') # SEND DEVICE'S INFORMATION - self.send_info_message() + if self.__conf_reader.send_info(): + self.send_info_message() # ENABLE TERMINAL - self.enable_terminal() + if self.__conf_reader.enable_terminal(): + self.enable_terminal() + + # CHECK PYMESH FIRMWARE VERSION + try: + if hasattr(os.uname(),'pymesh'): + try: + from pybytes_pymesh_config import PybytesPymeshConfig + except: + from _pybytes_pymesh_config import PybytesPymeshConfig + self.__pymesh = PybytesPymeshConfig(self) + self.__pymesh.pymesh_init() + except Exception as e: + print("Exception: {}".format(e)) else: print('ERROR! Could not connect to Pybytes!') @@ -265,7 +291,6 @@ def connect(self): def write_config(self, file='/flash/pybytes_config.json', silent=False): try: - import json f = open(file, 'w') f.write(json.dumps(self.__conf)) f.close() @@ -277,7 +302,6 @@ def write_config(self, file='/flash/pybytes_config.json', silent=False): def print_cfg_msg(self): if self.__conf.get('cfg_msg') is not None: - import time time.sleep(.1) print(self.__conf['cfg_msg']) time.sleep(.1) @@ -302,6 +326,7 @@ def set_config( ): if key is None and value is not None: self.__conf = value + self.__conf_reader = PybytesConfigReader(value) elif key is not None: self.__conf[key] = value else: @@ -326,28 +351,29 @@ def update_config(self, key, value=None, permanent=True, silent=False, reconnect else: self.__conf[key] = value self.__config_updated = True - if permanent: self.write_config(silent=silent) + if permanent: + self.write_config(silent=silent) if reconnect: self.reconnect() except Exception as ex: print('Error updating configuration!') sys.print_exception(ex) - def read_config(self, file='/flash/pybytes_config.json', reconnect=False): try: - import json f = open(file, 'r') jfile = f.read() f.close() try: - self.__conf = json.loads(jfile.strip()) + config_from_file = json.loads(jfile.strip()) + self.__conf = config_from_file + self.__conf_reader = PybytesConfigReader(config_from_file) self.__config_updated = True print("Pybytes configuration read from {}".format(file)) if reconnect: self.reconnect() except Exception as ex: - print("JSON error in configuration file {}!\n Exception: {}".format(file, ex)) # noqa + print("JSON error in configuration file {}!\n Exception: {}".format(file, ex)) except Exception as ex: print("Cannot open file {}\nException: {}".format(file, ex)) @@ -355,7 +381,6 @@ def reconnect(self): self.__check_init() try: self.disconnect() - import time time.sleep(1) self.connect() except Exception as ex: @@ -364,7 +389,6 @@ def reconnect(self): def export_config(self, file='/flash/pybytes_config.json'): self.__check_init() try: - import json f = open(file, 'w') f.write(json.dumps(self.__conf)) f.close() @@ -372,7 +396,7 @@ def export_config(self, file='/flash/pybytes_config.json'): except Exception as e: print("Error writing to file {}\nException: {}".format(file, e)) - def enable_ssl(self, ca_file='/flash/cert/pycom-ca.pem', dump_ca = True): + def enable_ssl(self, ca_file='/flash/cert/pycom-ca.pem', dump_ca=True): self.__check_init() self.set_config('dump_ca', dump_ca, permanent=False) if ca_file is not None: @@ -387,7 +411,7 @@ def enable_ssl(self, ca_file='/flash/cert/pycom-ca.pem', dump_ca = True): def enable_lte(self, carrier=None, cid=None, band=None, apn=None, type=None, reset=None, fallback=False): nwpref = None self.__check_init() - self.set_config('lte', {"carrier": carrier, "cid": cid, "band": band, "apn": apn, "type": type, "reset": reset }, permanent=False) + self.set_config('lte', {"carrier": carrier, "cid": cid, "band": band, "apn": apn, "type": type, "reset": reset}, permanent=False) if fallback: nwpref = self.__conf.get('network_preferences', []) nwpref.extend(['lte']) @@ -400,20 +424,20 @@ def enable_lte(self, carrier=None, cid=None, band=None, apn=None, type=None, res def deepsleep(self, ms, pins=None, mode=None, enable_pull=None): self.__check_init() - import machine if pins is not None: if mode is None or type(mode) != int: raise ValueError('You must specify a mode as integer!') - machine.pin_sleep_wakeup(pins, mode, enable_pull) + pin_sleep_wakeup(pins, mode, enable_pull) self.disconnect() - machine.deepsleep(ms) + deepsleep(ms) def dump_ca(self, ca_file='/flash/cert/pycom-ca.pem'): try: - try: - from _pybytes_ca import PYBYTES_CA - except: - from pybytes_ca import PYBYTES_CA + from _pybytes_ca import PYBYTES_CA + except: + from pybytes_ca import PYBYTES_CA + + try: f = open(ca_file, 'w') f.write(PYBYTES_CA) f.close() @@ -421,35 +445,26 @@ def dump_ca(self, ca_file='/flash/cert/pycom-ca.pem'): except Exception as e: print("Error creating {}\nException: {}".format(file, e)) - def start(self, autoconnect=True): if self.__conf is not None: self.__check_dump_ca() self.__config_updated = True - # START code from the old boot.py - import machine - import micropython - from binascii import hexlify - - wmac = hexlify(machine.unique_id()).decode('ascii') - print("WMAC: %s" % wmac.upper()) - try: + print("WMAC: {}".format(hexlify(unique_id()).decode('ascii').upper())) + if hasattr(os.uname(), 'pybytes'): print("Firmware: %s\nPybytes: %s" % (os.uname().release, os.uname().pybytes)) - except: + else: print("Firmware: %s" % os.uname().release) if autoconnect: self.connect() - - def activate(self, activation_string): self.__smart_config = False if self.__pybytes_connection is not None: print('Disconnecting current connection!') self.__pybytes_connection.disconnect(keep_wifi=True, force=True) try: - jstring = json.loads(binascii.a2b_base64(activation_string)) + jstring = json.loads(a2b_base64(activation_string)) except Exception as ex: print('Error decoding activation string!') print(ex) @@ -459,7 +474,9 @@ def activate(self, activation_string): from _pybytes_config import PybytesConfig try: self.__create_pybytes_connection(None) - self.__conf = PybytesConfig().cli_config(activation_info=jstring, pybytes_connection=self.__pybytes_connection) + conf_from_pybytes_conf = PybytesConfig().cli_config(activation_info=jstring, pybytes_connection=self.__pybytes_connection) + self.__conf = conf_from_pybytes_conf + self.__conf_reader = PybytesConfigReader(conf_from_pybytes_conf) if self.__conf is not None: self.start() else: @@ -502,17 +519,20 @@ def __smart_config_callback(self, wl): from _pybytes_config import PybytesConfig print_debug(99, 'smartConfig done... activating') try: - self.__conf = PybytesConfig().smart_config() + conf_smart = PybytesConfig().smart_config() + self.__conf = conf_smart + self.__conf_reader = PybytesConfigReader(conf_smart) self.__smart_config = False self.start() except Exception as ex: + print_debug(99, ex) print('Smart Config failed... restarting!') self.__smart_config = True self.__smart_config_setup() def __smart_config_setup(self): wl = WLAN(mode=WLAN.STA) - wl.callback(trigger= WLAN.SMART_CONF_DONE | WLAN.SMART_CONF_TIMEOUT, handler=self.__smart_config_callback) + wl.callback(trigger=WLAN.SMART_CONF_DONE | WLAN.SMART_CONF_TIMEOUT, handler=self.__smart_config_callback) if pycom.wifi_ssid_sta() is not None and len(pycom.wifi_ssid_sta()) > 0: print('Trying previous AP details for 60 seconds...') pycom.wifi_on_boot(True, True) @@ -527,7 +547,9 @@ def __smart_config_setup(self): from pybytes_config import PybytesConfig except: from _pybytes_config import PybytesConfig - self.__conf = PybytesConfig().smart_config() + conf_smart = PybytesConfig().smart_config() + self.__conf = conf_smart + self.__conf_reader = PybytesConfigReader(conf_smart) self.__smart_config = False self.start() if self.__smart_config: diff --git a/esp32/frozen/Pybytes/_pybytes_config.py b/esp32/frozen/Pybytes/_pybytes_config.py index 8d49419c8c..3b980a02a9 100644 --- a/esp32/frozen/Pybytes/_pybytes_config.py +++ b/esp32/frozen/Pybytes/_pybytes_config.py @@ -6,17 +6,23 @@ available at https://www.pycom.io/opensource/licensing ''' -import pycom, time, os, json, binascii, machine -try: - from pybytes_debug import print_debug -except: - from _pybytes_debug import print_debug +import pycom +import time +import os +import json +import binascii +import machine try: from pybytes_constants import constants except: from _pybytes_constants import constants +try: + from pybytes_debug import print_debug +except: + from _pybytes_debug import print_debug + class PybytesConfig: @@ -78,7 +84,6 @@ def __check_cb_config(self, config): print_debug(4, 'Exception in __check_cb_config!\n{}'.format(e)) return False - def __read_activation(self): try: import urequest @@ -88,14 +93,19 @@ def __read_activation(self): from uhashlib import sha512 print('Wifi connection established... activating device!') self.__pybytes_activation = None - data = { "deviceType": os.uname().sysname.lower(), "wirelessMac": binascii.hexlify(machine.unique_id()).upper() } + data = {"deviceType": os.uname().sysname.lower(), "wirelessMac": binascii.hexlify(machine.unique_id()).upper()} try: - data.update({"activation_hash" : binascii.b2a_base64(sha512(data.get("wirelessMac") + '-' + '{}'.format(pycom.wifi_ssid_sta()) + '-' + '{}'.format(pycom.wifi_pwd_sta())).digest()).decode('UTF-8').strip()}) + data.update({"activation_hash": binascii.b2a_base64(sha512(data.get("wirelessMac") + '-' + '{}'.format(pycom.wifi_ssid_sta()) + '-' + '{}'.format( + pycom.wifi_pwd_sta())).digest()).decode('UTF-8').strip()} + ) except: pass time.sleep(1) try: - self.__pybytes_activation = urequest.post('https://api.{}/esp-touch/register-device'.format(constants.__DEFAULT_DOMAIN), json=data, headers={'content-type': 'application/json'}) + self.__pybytes_activation = urequest.post( + 'https://api.{}/esp-touch/register-device'.format(constants.__DEFAULT_DOMAIN), + json=data, headers={'content-type': 'application/json'} + ) return True except Exception as ex: if self.__pybytes_activation is not None: @@ -110,12 +120,13 @@ def __read_cli_activation(self, activation_token): except: import _urequest as urequest - from uhashlib import sha512 self.__pybytes_cli_activation = None - data = { "activationToken": activation_token['a'], "deviceMacAddress": binascii.hexlify(machine.unique_id()).upper()} + data = {"activationToken": activation_token['a'], "deviceMacAddress": binascii.hexlify(machine.unique_id()).upper()} time.sleep(1) try: - self.__pybytes_cli_activation = urequest.post('https://api.{}/v2/quick-device-activation'.format(constants.__DEFAULT_DOMAIN), json=data, headers={'content-type': 'application/json'}) + self.__pybytes_cli_activation = urequest.post( + 'https://api.{}/v2/quick-device-activation'.format(constants.__DEFAULT_DOMAIN), json=data, headers={'content-type': 'application/json'} + ) except Exception as ex: if self.__pybytes_cli_activation is not None: self.__pybytes_cli_activation.close() @@ -133,10 +144,16 @@ def __process_sigfox_registration(self, activation_token): try: jsigfox = None 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)) + 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)) try: - self.__pybytes_sigfox_registration = urequest.post('https://api.{}/v2/register-sigfox'.format(constants.__DEFAULT_DOMAIN), json=data, headers={'content-type': 'application/json'}) + self.__pybytes_sigfox_registration = urequest.post( + 'https://api.{}/v2/register-sigfox'.format(constants.__DEFAULT_DOMAIN), json=data, headers={'content-type': 'application/json'} + ) jsigfox = self.__pybytes_sigfox_registration.json() except: jsigfox = None @@ -144,7 +161,9 @@ def __process_sigfox_registration(self, activation_token): while jsigfox is None and time.time() - start_time < 300: time.sleep(15) try: - self.__pybytes_sigfox_registration = urequest.post('https://api.{}/v2/register-sigfox'.format(constants.__DEFAULT_DOMAIN), json=data, headers={'content-type': 'application/json'}) + self.__pybytes_sigfox_registration = urequest.post( + 'https://api.{}/v2/register-sigfox'.format(constants.__DEFAULT_DOMAIN), json=data, headers={'content-type': 'application/json'} + ) print_debug(2, '/v2/register-sigfox returned response: {}'.format(self.__pybytes_sigfox_registration.text)) jsigfox = self.__pybytes_sigfox_registration.json() except: @@ -155,7 +174,13 @@ def __process_sigfox_registration(self, activation_token): 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) + 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() @@ -171,7 +196,9 @@ def __process_sigfox_registration(self, activation_token): def __process_cli_activation(self, filename, activation_token): try: if not self.__pybytes_cli_activation.status_code == 200: - print_debug(3, 'Activation request returned {} with text: "{}".'.format(self.__pybytes_cli_activation.status_code, self.__pybytes_cli_activation.text)) + print_debug(3, 'Activation request returned {} with text: "{}".'.format( + self.__pybytes_cli_activation.status_code, self.__pybytes_cli_activation.text) + ) else: print_debug(99, 'Activation response:\n{}'.format(self.__pybytes_cli_activation.json())) if self.__process_sigfox_registration(activation_token): @@ -194,7 +221,6 @@ def __process_cli_activation(self, filename, activation_token): print('{}'.format(e)) return None - def __process_activation(self, filename): try: if not self.__pybytes_activation.status_code == 200: @@ -218,19 +244,19 @@ def __read_cb_config(self): config_block = {} try: config_block = { - 'userId' : pycom.pybytes_userId(), - 'device_token' : pycom.pybytes_device_token(), - 'mqttServiceAddress' : pycom.pybytes_mqttServiceAddress(), - 'network_preferences' : pycom.pybytes_network_preferences().split(), - 'wifi_ssid' : pycom.wifi_ssid_sta() if hasattr(pycom, 'wifi_ssid_sta') else pycom.wifi_ssid(), + 'userId': pycom.pybytes_userId(), + 'device_token': pycom.pybytes_device_token(), + 'mqttServiceAddress': pycom.pybytes_mqttServiceAddress(), + 'network_preferences': pycom.pybytes_network_preferences().split(), + 'wifi_ssid': pycom.wifi_ssid_sta() if hasattr(pycom, 'wifi_ssid_sta') else pycom.wifi_ssid(), 'wifi_pwd': pycom.wifi_pwd_sta() if hasattr(pycom, 'wifi_pwd_sta') else pycom.wifi_pwd(), - 'extra_preferences' : pycom.pybytes_extra_preferences(), - 'carrier' : pycom.pybytes_lte_config()[0], - 'apn' : pycom.pybytes_lte_config()[1], - 'cid' : pycom.pybytes_lte_config()[2], - 'band' : pycom.pybytes_lte_config()[3], - 'protocol' : pycom.pybytes_lte_config()[4], - 'reset' : pycom.pybytes_lte_config()[5] + 'extra_preferences': pycom.pybytes_extra_preferences(), + 'carrier': pycom.pybytes_lte_config()[0], + 'apn': pycom.pybytes_lte_config()[1], + 'cid': pycom.pybytes_lte_config()[2], + 'band': pycom.pybytes_lte_config()[3], + 'protocol': pycom.pybytes_lte_config()[4], + 'reset': pycom.pybytes_lte_config()[5] } except: pass @@ -239,15 +265,15 @@ def __read_cb_config(self): def __generate_cli_config(self): pybytes_config = self.__pybytes_cli_activation.json() cli_config = { - 'userId' : pybytes_config.get('userId'), - 'device_token' : pybytes_config.get('deviceToken'), - 'mqttServiceAddress' : pybytes_config.get('mqttServiceAddress'), - 'network_preferences' : pybytes_config.get('network_preferences'), + 'userId': pybytes_config.get('userId'), + 'device_token': pybytes_config.get('deviceToken'), + 'mqttServiceAddress': pybytes_config.get('mqttServiceAddress'), + 'network_preferences': pybytes_config.get('network_preferences'), 'wifi_ssid': '', 'wifi_pwd': '', } try: - cli_config.update({'wifi_ssid' : pybytes_config.get('wifi').get('ssid')}) + cli_config.update({'wifi_ssid': pybytes_config.get('wifi').get('ssid')}) except: print_debug(3, '__generate_cli_config: config does not contain wifi_ssid') try: @@ -256,17 +282,17 @@ def __generate_cli_config(self): print_debug(3, '__generate_cli_config: config does not contain wifi_password') try: cli_config.update({ - 'carrier' : pybytes_config.get('lte').get('carrier').lower(), - 'apn' : pybytes_config.get('lte').get('apn'), - 'cid' : pybytes_config.get('lte').get('cid'), - 'band' : pybytes_config.get('lte').get('band'), - 'reset' : pybytes_config.get('lte').get('reset'), - 'protocol' : pybytes_config.get('lte').get('protocol') + 'carrier': pybytes_config.get('lte').get('carrier').lower(), + 'apn': pybytes_config.get('lte').get('apn'), + 'cid': pybytes_config.get('lte').get('cid'), + 'band': pybytes_config.get('lte').get('band'), + 'reset': pybytes_config.get('lte').get('reset'), + 'protocol': pybytes_config.get('lte').get('protocol') }) except: print_debug(3, '__generate_cli_config: config does not contain LTE configuration') try: - cli_config.update({'extra_preferences' :pybytes_config.get('extra_preferences', '')}) + cli_config.update({'extra_preferences': pybytes_config.get('extra_preferences', '')}) except: print_debug(3, '__generate_cli_config: config does not contain extra_preferences') return cli_config @@ -315,32 +341,35 @@ def __process_config(self, filename, configuration): elif extra_preference == "ca_certs": index = extra_preferences.index(extra_preference) if len(extra_preferences[index + 1]) > 0: - ssl_params = { "ssl_params": {"ca_certs": extra_preferences[index + 1] }} + ssl_params = {"ssl_params": {"ca_certs": extra_preferences[index + 1]}} else: - ssl_params = { "ssl_params": {"ca_certs": '/flash/cert/pycom-ca.pem' }} + ssl_params = {"ssl_params": {"ca_certs": '/flash/cert/pycom-ca.pem'}} elif extra_preference == "dump_ca": dump_ca = True elif extra_preference == "sigfox": index = extra_preferences.index(extra_preference) if len(extra_preferences[index + 1]) > 0 and extra_preferences[index + 1].isdigit(): rcz = int(extra_preferences[index + 1]) - sigfox_config = { 'sigfox' : - { "RCZ" : rcz } - } + sigfox_config = { + 'sigfox': { + "RCZ": rcz + } + } except Exception as e: print_debug(2, 'Exception __process_config[extra]\n{}'.format(e)) try: - lte_config = { 'lte': - { 'carrier': configuration.get('carrier'), - 'cid': configuration.get('cid'), - 'band': configuration.get('band'), - 'apn': configuration.get('apn'), - 'reset': configuration.get('reset', 'false') == 'true', - 'type': configuration.get('protocol') - } - } + lte_config = { + 'lte': { + 'carrier': configuration.get('carrier'), + 'cid': configuration.get('cid'), + 'band': configuration.get('band'), + 'apn': configuration.get('apn'), + 'reset': configuration.get('reset', 'false') == 'true', + 'type': configuration.get('protocol') + } + } except Exception as e: print_debug(2, 'Exception __process_config[lte]\n{}'.format(e)) @@ -351,7 +380,7 @@ def __process_config(self, filename, configuration): 'server': configuration['mqttServiceAddress'], 'network_preferences': configuration['network_preferences'] \ if type(configuration['network_preferences']) is list \ - else configuration['network_preferences'].split(), # ordered list, first working network used + else configuration['network_preferences'].split(), # noqa # ordered list, first working network used 'wifi': { 'ssid': configuration['wifi_ssid'], 'password': configuration['wifi_pwd'] @@ -374,18 +403,18 @@ def __process_config(self, filename, configuration): if ssl_params is not None: self.__pybytes_config.update(ssl_params) print_debug(2, 'Checking and writing configuration in __process_config') - if (len(self.__pybytes_config['username']) > 4 and len(self.__pybytes_config['device_id']) >= 36 and len(self.__pybytes_config['server']) > 4) and self.__write_config(filename): + if (len(self.__pybytes_config['username']) > 4 and len(self.__pybytes_config['device_id']) >= 36 and len(self.__pybytes_config['server']) > 4) and self.__write_config(filename): # noqa self.__pybytes_config['cfg_msg'] = "Configuration successfully converted to pybytes_config.json" return True return False except Exception as e: - print_debug(2 , 'Exception __process_config[generic]\n{}'.format(e)) + print_debug(2, 'Exception __process_config[generic]\n{}'.format(e)) return False def __convert_legacy_config(self, filename): try: from config import config as pybytes_legacy_config - if pybytes_legacy_config.get('username') is None or pybytes_legacy_config.get('device_id') is None or pybytes_legacy_config.get('server') is None or pybytes_legacy_config.get('network_preferences') is None: + if pybytes_legacy_config.get('username') is None or pybytes_legacy_config.get('device_id') is None or pybytes_legacy_config.get('server') is None or pybytes_legacy_config.get('network_preferences') is None: # noqa print("This config.py does not look like a Pybytes configuration... skipping...") del pybytes_legacy_config raise ValueError() @@ -428,11 +457,11 @@ def __cli_activation_over_wifi(self, activation_info): attempt = 0 known_nets = [((activation_info['s'], activation_info['p']))] # noqa - print_debug(3,'WLAN connected? {}'.format(wlan.isconnected())) + print_debug(3, 'WLAN connected? {}'.format(wlan.isconnected())) 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())) + print_debug(3, 'WLAN connected? {}'.format(wlan.isconnected())) available_nets = None while available_nets is None: try: @@ -457,8 +486,8 @@ def __cli_activation_over_wifi(self, activation_info): wlan.connect(net_to_use, (sec, pwd), timeout=10000) start_time = time.time() while not wlan.isconnected(): - if time.time() - start_time > timeout: - raise TimeoutError('Timeout trying to connect via WiFi') + if time.time() - start_time > 300: + raise TimeoutError('Timeout trying to connect via WiFi') # noqa: F821 time.sleep(0.1) except Exception as e: if str(e) == "list index out of range" and attempt == 3: @@ -488,9 +517,6 @@ def read_config(self, filename='/flash/pybytes_config.json'): except Exception as ex: self.__force_update = True -# if self.__force_update: -# self.__convert_legacy_config(filename) - if self.__force_update: if not self.__process_config(filename, self.__read_cb_config()): self.__pybytes_config['pybytes_autostart'] = False @@ -517,6 +543,6 @@ def read_config(self, filename='/flash/pybytes_config.json'): except: pass if self.__pybytes_config.get('pybytes_autostart', False): - self.__pybytes_config['pybytes_autostart'] = self.__check_config() + self.__pybytes_config['pybytes_autostart'] = self.__check_config() return self.__pybytes_config diff --git a/esp32/frozen/Pybytes/_pybytes_config_reader.py b/esp32/frozen/Pybytes/_pybytes_config_reader.py new file mode 100644 index 0000000000..bb13ab1842 --- /dev/null +++ b/esp32/frozen/Pybytes/_pybytes_config_reader.py @@ -0,0 +1,55 @@ +''' +Copyright (c) 2020, Pycom Limited. +This software is licensed under the GNU GPL version 3 or any +later version, with permitted additional terms. For more information +see the Pycom Licence v1.0 document supplied with this file, or +available at https://www.pycom.io/opensource/licensing +''' + + +class PybytesConfigReader: + + def __init__(self, config): + self.__pybytes_config = config + + def get_pybytes(self): + return self.__pybytes_config.get( + 'pybytes', {} + ) + + def send_info(self): + return self.get_pybytes().get( + 'send_info', True + ) + + def enable_terminal(self): + return self.get_pybytes().get( + 'enable_terminal', True + ) + + def get_communication_obj(self): + return self.get_pybytes().get( + 'communication', {} + ) + + def get_communication_type(self): + return self.get_communication_obj().get( + 'type', 'mqtt' + ).lower() + + def get_coap(self): + return self.get_communication_obj().get( + 'servers', {} + ).get( + 'coap', {} + ) + + def get_coap_host(self): + return self.get_coap().get( + 'host', None + ) + + def get_coap_port(self): + return self.get_coap().get( + 'port', None + ) diff --git a/esp32/frozen/Pybytes/_pybytes_connection.py b/esp32/frozen/Pybytes/_pybytes_connection.py index ab9a22bb2e..e0b98aaa34 100644 --- a/esp32/frozen/Pybytes/_pybytes_connection.py +++ b/esp32/frozen/Pybytes/_pybytes_connection.py @@ -6,10 +6,24 @@ available at https://www.pycom.io/opensource/licensing ''' +import os +import sys +import _thread +import time +import socket +import struct +import binascii +import pycom +from machine import WDT + try: from mqtt import MQTTClient except: from _mqtt import MQTTClient +try: + from pybytes_config_reader import PybytesConfigReader +except: + from _pybytes_config_reader import PybytesConfigReader try: from pybytes_protocol import PybytesProtocol @@ -25,22 +39,17 @@ from pybytes_debug import print_debug except: from _pybytes_debug import print_debug - -import os -import sys -import _thread -import time -import socket -import struct -import binascii -import pycom -from machine import WDT +try: + from coap import COAPClient +except: + from _coap import COAPClient class PybytesConnection: def __init__(self, config, message_callback): if config is not None: self.__conf = config + self.__conf_reader = PybytesConfigReader(config) try: self.__host = pycom.nvs_get('pybytes_server') except: @@ -131,14 +140,14 @@ def connect_wifi(self, reconnect=True, check_interval=0.5, timeout=120): pwd = dict(known_nets)[net_to_use] sec = [e.sec for e in available_nets if e.ssid == net_to_use][0] # noqa print_debug(99, "Connecting with {} and {}".format(net_to_use, pwd)) - if sec == 0: - self.wlan.connect(net_to_use, timeout=10000) + if sec == 0: + self.wlan.connect(net_to_use, timeout=self.__conf.get('wifi', {}).get('timeout', 10000)) else: - self.wlan.connect(net_to_use, (sec, pwd), timeout=10000) + self.wlan.connect(net_to_use, (sec, pwd), timeout=self.__conf.get('wifi', {}).get('timeout', 10000)) start_time = time.time() while not self.wlan.isconnected(): if time.time() - start_time > timeout: - raise TimeoutError('Timeout trying to connect via WiFi') + raise TimeoutError('Timeout trying to connect via WiFi') # noqa: F821 time.sleep(0.1) except Exception as e: if str(e) == "list index out of range" and attempt == 3: @@ -148,31 +157,9 @@ def connect_wifi(self, reconnect=True, check_interval=0.5, timeout=120): elif attempt == 3: print("Error connecting using WIFI: %s" % e) return False - self.__network_type = constants.__NETWORK_TYPE_WIFI print("WiFi connection established") - try: - self.__connection = MQTTClient( - self.__device_id, - self.__host, - self.__mqtt_download_topic, - self.__pybytes_protocol, - user=self.__user_name, - password=self.__device_id - ) - self.__connection.connect() - self.__connection_status = constants.__CONNECTION_STATUS_CONNECTED_MQTT_WIFI # noqa - self.__pybytes_protocol.start_MQTT( - self, - constants.__NETWORK_TYPE_WIFI - ) - return True - except Exception as ex: - if '{}'.format(ex) == '4': - print('MQTT ERROR! Bad credentials when connecting to server: "{}"'.format(self.__host)) # noqa - else: - print("MQTT ERROR! {}".format(ex)) - return False + return True except Exception as ex: print("Exception connect_wifi: {}".format(ex)) return False @@ -230,33 +217,11 @@ def connect_lte(self, activation_info=False, start_mqtt=True): print_debug(1, 'LTE is_connected()') while not self.lte.isconnected(): time.sleep(0.25) - print("LTE connection established") self.__network_type = constants.__NETWORK_TYPE_LTE - + print("LTE connection established") if start_mqtt: - try: - self.__connection = MQTTClient( - self.__device_id, - self.__host, - self.__mqtt_download_topic, - self.__pybytes_protocol, - user=self.__user_name, - password=self.__device_id - ) - self.__connection.connect() - self.__connection_status = constants.__CONNECTION_STATUS_CONNECTED_MQTT_LTE # noqa - self.__pybytes_protocol.start_MQTT( - self, - constants.__NETWORK_TYPE_LTE - ) - print("Connected to MQTT {}".format(self.__host)) - return True - except Exception as ex: - if '{}'.format(ex) == '4': - print('MQTT ERROR! Bad credentials when connecting to server: "{}"'.format(self.__host)) # noqa - else: - print("MQTT ERROR! {}".format(ex)) - return False + print("connect_lte with start_mqtt is now removed please call communication_protocol or start_mqtt directly") + return True except Exception as ex: print("Exception connect_lte: {}".format(ex)) sys.print_exception(ex) @@ -267,7 +232,7 @@ def connect_lte(self, activation_info=False, start_mqtt=True): # LORA def connect_lora_abp(self, lora_timeout, nanogateway): - print_debug(1,'Attempting to connect via LoRa') + print_debug(1, 'Attempting to connect via LoRa') if (self.__connection_status != constants.__CONNECTION_STATUS_DISCONNECTED): # noqa print("Error connect_lora_abp: Connection already exists. Disconnect First") # noqa return False @@ -325,7 +290,7 @@ def connect_lora_abp(self, lora_timeout, nanogateway): return False def connect_lora_otaa(self, lora_timeout, nanogateway): - print_debug(1,'Attempting to connect via LoRa') + print_debug(1, 'Attempting to connect via LoRa') if (self.__connection_status != constants.__CONNECTION_STATUS_DISCONNECTED): # noqa print("Error connect_lora_otaa: Connection already exists. Disconnect First") # noqa return False @@ -501,3 +466,61 @@ def is_connected(self): # Added for convention with other connectivity classes def isconnected(self): return not (self.__connection_status == constants.__CONNECTION_STATUS_DISCONNECTED) # noqa + + def communication_protocol(self, connection_type): + if self.__conf_reader.get_communication_type() == constants.__SIMPLE_COAP: + + if connection_type == 'wifi': + target_connection_type = constants.__CONNECTION_STATUS_CONNECTED_COAP_WIFI + elif connection_type == 'lte': + target_connection_type = constants.__CONNECTION_STATUS_CONNECTED_COAP_LTE + + return self.start_coap(target_connection_type) + + else: + + if connection_type == 'wifi': + target_connection_type = constants.__CONNECTION_STATUS_CONNECTED_MQTT_WIFI + elif connection_type == 'lte': + target_connection_type = constants.__CONNECTION_STATUS_CONNECTED_MQTT_LTE + + return self.start_mqtt(target_connection_type) + + def start_coap(self, connection_type): + print_debug(1, 'CoAP Protocol') + self.__connection = COAPClient( + self.__conf_reader.get_coap_host(), + self.__conf_reader.get_coap_port(), + ) + self.__connection_status = connection_type + self.__pybytes_protocol.start_coap( + self, + self.__network_type + ) + return True + + def start_mqtt(self, connection_type): + print_debug(1, 'MQTT Protocol') + try: + self.__connection = MQTTClient( + self.__device_id, + self.__host, + self.__mqtt_download_topic, + self.__pybytes_protocol, + user=self.__user_name, + password=self.__device_id + ) + self.__connection.connect() + self.__connection_status = connection_type + self.__pybytes_protocol.start_MQTT( + self, + self.__network_type + ) + print("Connected to MQTT {}".format(self.__host)) + return True + except Exception as ex: + if '{}'.format(ex) == '4': + print('MQTT ERROR! Bad credentials when connecting to server: "{}"'.format(self.__host)) + else: + print("MQTT ERROR! {}".format(ex)) + return False diff --git a/esp32/frozen/Pybytes/_pybytes_constants.py b/esp32/frozen/Pybytes/_pybytes_constants.py index 704e4f0a73..8b68cffa9c 100644 --- a/esp32/frozen/Pybytes/_pybytes_constants.py +++ b/esp32/frozen/Pybytes/_pybytes_constants.py @@ -58,6 +58,8 @@ class constants: __CONNECTION_STATUS_CONNECTED_MQTT_LTE = 2 __CONNECTION_STATUS_CONNECTED_LORA = 3 __CONNECTION_STATUS_CONNECTED_SIGFOX = 4 + __CONNECTION_STATUS_CONNECTED_COAP_WIFI = 5 + __CONNECTION_STATUS_CONNECTED_COAP_LTE = 6 __TYPE_PING = 0x00 __TYPE_INFO = 0x01 @@ -67,6 +69,7 @@ class constants: __TYPE_OTA = 0x05 __TYPE_FCOTA = 0x06 __TYPE_PONG = 0x07 + __TYPE_PYMESH = 0x0D __TYPE_PYBYTES = 0x0E __TYPE_RELEASE_INFO = 0x0B __TYPE_RELEASE_DEPLOY = 0x0A @@ -118,6 +121,8 @@ class constants: __WDT_MAX_TIMEOUT_MILLISECONDS = sys.maxsize + __SIMPLE_COAP = 'simple_coap' + try: __DEFAULT_DOMAIN = pycom.nvs_get('pybytes_domain', 'pybytes.pycom.io') except: diff --git a/esp32/frozen/Pybytes/_pybytes_library.py b/esp32/frozen/Pybytes/_pybytes_library.py index 5dc4aee415..e3ac7898ff 100644 --- a/esp32/frozen/Pybytes/_pybytes_library.py +++ b/esp32/frozen/Pybytes/_pybytes_library.py @@ -49,11 +49,11 @@ def pack_release_info_message(self, releaseId): print("This is pack_release_info_message") return self.__pack_message(constants.__TYPE_RELEASE_INFO, releaseId) - def pack_pybytes_message_variable(self, command, pin, parameters): + def pack_pybytes_message_variable(self, command, pin, parameters, message_type): print_debug(5, "This is pack_pybytes_message_variable({}, {}, {})".format(command, pin, parameters)) body = struct.pack(constants.__PYBYTES_INTERNAL_PROTOCOL_VARIABLE % len(parameters), command, pin, parameters) - return self.__pack_message(constants.__TYPE_PYBYTES, body) + return self.__pack_message(getattr(constants, message_type), body) def pack_ping_message(self): return self.__pack_message(constants.__TYPE_PING, None) @@ -150,12 +150,6 @@ def pack_scan_info_message(self, lora): body = bytearray() - # max_networks = 5 - # if (len(wifi_networks) < 5): - # max_networks = len(wifi_networks) - # - # for x in range(0, max_networks): - for x in range(0, len(wifi_networks)): wifi_pack = struct.pack(constants.__WIFI_NETWORK_FORMAT, wifi_networks[x][1], wifi_networks[x][3], wifi_networks[x][4]) diff --git a/esp32/frozen/Pybytes/_pybytes_protocol.py b/esp32/frozen/Pybytes/_pybytes_protocol.py index 52ff0d0deb..484b242add 100644 --- a/esp32/frozen/Pybytes/_pybytes_protocol.py +++ b/esp32/frozen/Pybytes/_pybytes_protocol.py @@ -26,6 +26,16 @@ except: from _OTA import WiFiOTA +try: + from pybytes_pymesh_config import PybytesPymeshConfig +except: + from _pybytes_pymesh_config import PybytesPymeshConfig + +try: + from pybytes_config_reader import PybytesConfigReader +except: + from _pybytes_config_reader import PybytesConfigReader + try: from flash_control_OTA import FCOTA except: @@ -45,6 +55,8 @@ from machine import PWM from machine import Timer from machine import reset +from binascii import hexlify +from network import Coap import os import _thread @@ -57,6 +69,7 @@ class PybytesProtocol: def __init__(self, config, message_callback, pybytes_connection): self.__conf = config + self.__conf_reader = PybytesConfigReader(config) self.__thread_stack_size = 8192 self.__device_id = config['device_id'] self.__mqtt_download_topic = "d" + self.__device_id @@ -99,6 +112,11 @@ def start_Sigfox(self, pybytes_connection): constants.__NETWORK_TYPE_SIGFOX) self.__pybytes_connection = pybytes_connection + def start_coap(self, pybytes_connection, networkType): + print_debug(5, "This is PybytesProtocol.start_coap()") + self.__pybytes_connection = pybytes_connection + self.__pybytes_library.set_network_type(networkType) + def __wifi_or_lte_connection(self): return self.__pybytes_connection.__connection_status == constants.__CONNECTION_STATUS_CONNECTED_MQTT_WIFI or self.__pybytes_connection.__connection_status == constants.__CONNECTION_STATUS_CONNECTED_MQTT_LTE # noqa @@ -119,7 +137,7 @@ def __check_lora_messages(self): self.__pybytes_connection.__lora_socket.setblocking(False) message = self.__pybytes_connection.__lora_socket.recv(256) except Exception as ex: - print_debug(5, "Exception in PybytesProtocol.__check_lora_messages: {}".format(ex)) # noqa + print_debug(5, "Exception in PybytesProtocol.__check_lora_messages: {}".format(ex)) if (message): self.__process_recv_message(message) time.sleep(0.5) @@ -128,16 +146,16 @@ def __process_recv_message(self, message): print_debug(5, "This is PybytesProtocol.__process_recv_message()") if message.payload: - network_type, message_type, body = self.__pybytes_library.unpack_message(message.payload) # noqa + network_type, message_type, body = self.__pybytes_library.unpack_message(message.payload) else: # for lora messages - network_type, message_type, body = self.__pybytes_library.unpack_message(message) # noqa + network_type, message_type, body = self.__pybytes_library.unpack_message(message) if self.__user_message_callback is not None: if (message_type == constants.__TYPE_PING): self.send_ping_message() - elif message_type == constants.__TYPE_PONG and self.__conf.get('connection_watchdog', True): # noqa + elif message_type == constants.__TYPE_PONG and self.__conf.get('connection_watchdog', True): self.__pybytes_connection.__wifi_lte_watchdog.feed() elif (message_type == constants.__TYPE_INFO): @@ -168,6 +186,32 @@ def __process_recv_message(self, message): ) ota.update_device_network_config(self.__FCOTA, self.__conf) + elif (message_type == constants.__TYPE_PYMESH): + # create pymesh config file + wmac = hexlify(machine.unique_id()).decode('ascii') + pymeshConfig = PybytesPymeshConfig() + pymeshConfig.write_config(wmac=wmac.upper(), pymeshSettings=pymeshConfig.get_config(token=self.__conf['device_id'])) + + # start OTA update + 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): + print_debug(5, 'Connecting to WiFi') + ota.connect() + + print_debug(5, "Performing OTA") + result = ota.update(fwtype="pymesh", token=self.__conf['device_id']) + self.send_ota_response(result=result, topic='mesh') + time.sleep(1.5) + if (result == 2): + # Reboot the device to run the new decode + machine.reset() + elif (message_type == constants.__TYPE_OTA): ota = WiFiOTA( self.__conf['wifi']['ssid'], @@ -176,13 +220,13 @@ def __process_recv_message(self, message): self.__conf['ota_server']['port'] ) - if (self.__pybytes_connection.__connection_status == constants.__CONNECTION_STATUS_DISCONNECTED): # noqa + if (self.__pybytes_connection.__connection_status == constants.__CONNECTION_STATUS_DISCONNECTED): print_debug(5, 'Connecting to WiFi') ota.connect() print_debug(5, "Performing OTA") result = ota.update() - self.send_ota_response(result) + self.send_ota_response(result=result, topic='ota') time.sleep(1.5) if (result == 2): # Reboot the device to run the new decode @@ -190,19 +234,19 @@ def __process_recv_message(self, message): elif (message_type == constants.__TYPE_FCOTA): print_debug(2, 'receiving FCOTA request') - if (self.__pybytes_connection.__connection_status == constants.__CONNECTION_STATUS_DISCONNECTED): # noqa + if (self.__pybytes_connection.__connection_status == constants.__CONNECTION_STATUS_DISCONNECTED): print_debug(5, 'Not connected, Re-Connecting ...') ota.connect() command = body[0] - if (command == constants.__FCOTA_COMMAND_HIERARCHY_ACQUISITION): # noqa + if (command == constants.__FCOTA_COMMAND_HIERARCHY_ACQUISITION): self.send_fcota_ping('acquiring hierarchy...') hierarchy = self.__FCOTA.get_flash_hierarchy() self.send_fcota_hierarchy(hierarchy) elif (command == constants.__FCOTA_COMMAND_FILE_ACQUISITION): path = body[1:len(body)].decode() - if (path[len(path)-2:len(path)] == '.py'): + if (path[len(path) - 2:len(path)] == '.py'): self.send_fcota_ping('acquiring file...') content = self.__FCOTA.get_file_content(path) size = self.__FCOTA.get_file_size(path) @@ -213,14 +257,14 @@ def __process_recv_message(self, message): splittedBody = bodyString.split(',') if (len(splittedBody) >= 2): path = splittedBody[0] - print_debug(2, path[len(path)-7:len(path)]) - if (path[len(path)-7:len(path)] != '.pymakr'): + print_debug(2, path[len(path) - 7:len(path)]) + if (path[len(path) - 7:len(path)] != '.pymakr'): self.send_fcota_ping('updating file...') - newContent = bodyString[len(path)+1:len(body)] - if (self.__FCOTA.update_file_content(path, newContent) is True): # noqa + newContent = bodyString[len(path) + 1:len(body)] + if (self.__FCOTA.update_file_content(path, newContent) is True): size = self.__FCOTA.get_file_size(path) self.send_fcota_file(newContent, path, size) - if (path[len(path)-7:len(path)] != '.pymakr'): + if (path[len(path) - 7:len(path)] != '.pymakr'): time.sleep(2) self.send_fcota_ping('board restarting...') time.sleep(2) @@ -289,7 +333,7 @@ def __process_recv_message(self, message): pin.duty_cycle(value * 100) elif (command == constants.__COMMAND_CUSTOM_METHOD): - if (pin_number == constants.__TERMINAL_PIN and self.__terminal_enabled): # noqa + if (pin_number == constants.__TERMINAL_PIN and self.__terminal_enabled): original_dupterm = os.dupterm() os.dupterm(self.__terminal) self.__terminal.message_sent_from_pybytes_start() @@ -319,15 +363,15 @@ def __process_recv_message(self, message): value = body[i: i + 2] parameters[i / 3] = (value[0] << 8) | value[1] - method_return = self.__custom_methods[pin_number](parameters) # noqa + method_return = self.__custom_methods[pin_number](parameters) - if (method_return is not None and len(method_return) > 0): # noqa + if (method_return is not None and len(method_return) > 0): self.send_pybytes_custom_method_values( signal_number, method_return ) else: - print("WARNING: Trying to write to an unregistered Virtual Pin") # noqa + print("WARNING: Trying to write to an unregistered Virtual Pin") else: try: @@ -372,28 +416,39 @@ def __configure_pwm_pin(self, pin_number): ) def __send_message(self, message, topic=None, priority=True): - try: - finalTopic = self.__mqtt_upload_topic if topic is None else self.__mqtt_upload_topic + "/" + topic # noqa - if self.__wifi_or_lte_connection(): - self.__pybytes_connection.__connection.publish( - finalTopic, message, priority=priority - ) - elif (self.__pybytes_connection.__connection_status == constants.__CONNECTION_STATUS_CONNECTED_LORA): # noqa - with self.__pybytes_connection.lora_lock: - self.__pybytes_connection.__lora_socket.setblocking(True) - self.__pybytes_connection.__lora_socket.send(message) - self.__pybytes_connection.__lora_socket.setblocking(False) - elif (self.__pybytes_connection.__connection_status == constants.__CONNECTION_STATUS_CONNECTED_SIGFOX): # noqa - if (len(message) > 12): - print("WARNING: Message not sent, Sigfox only supports 12 Bytes messages") # noqa - return - self.__pybytes_connection.__sigfox_socket.send(message) + finalTopic = self.__mqtt_upload_topic if topic is None else self.__mqtt_upload_topic + "/" + topic + if self.__conf_reader.get_communication_type() == constants.__SIMPLE_COAP: + print_debug(1, 'CoAP Protocol') + owner = self.__conf.get('username') + self.__pybytes_connection.__connection.send_coap_message( + message, + Coap.REQUEST_GET, + '{}/{}'.format(owner, finalTopic), + "t{}".format(time.time()), + ) + else: + print_debug(1, 'MQTT Protocol') + try: + if self.__wifi_or_lte_connection(): + self.__pybytes_connection.__connection.publish( + finalTopic, message, priority=priority + ) + elif (self.__pybytes_connection.__connection_status == constants.__CONNECTION_STATUS_CONNECTED_LORA): + with self.__pybytes_connection.lora_lock: + self.__pybytes_connection.__lora_socket.setblocking(True) + self.__pybytes_connection.__lora_socket.send(message) + self.__pybytes_connection.__lora_socket.setblocking(False) + elif (self.__pybytes_connection.__connection_status == constants.__CONNECTION_STATUS_CONNECTED_SIGFOX): + if (len(message) > 12): + print("WARNING: Message not sent, Sigfox only supports 12 Bytes messages") + return + self.__pybytes_connection.__sigfox_socket.send(message) - else: - print_debug(2, "Warning: Sending without a connection") - pass - except Exception as ex: - print(ex) + else: + print_debug(2, "Warning: Sending without a connection") + pass + except Exception as ex: + print(ex) def send_user_message(self, message_type, body): self.__send_message( @@ -416,7 +471,7 @@ def send_network_info_message(self): self.__send_message(self.__pybytes_library.pack_network_info_message()) def send_scan_info_message(self, lora): - print('WARNING! send_scan_info_message is deprecated and should be called only from Pybytes.') # noqa + print('WARNING! send_scan_info_message is deprecated and should be called only from Pybytes.') def send_battery_info(self): self.__send_message( @@ -425,11 +480,11 @@ def send_battery_info(self): ) ) - def send_ota_response(self, result): + def send_ota_response(self, result, topic): print_debug(2, 'Sending OTA result back {}'.format(result)) self.__send_message( self.__pybytes_library.pack_ota_message(result), - 'ota' + topic ) def send_fcota_hierarchy(self, hierarchy): @@ -467,29 +522,29 @@ def send_pybytes_analog_value(self, pin_number): pin = self.__pins[pin_number] self.send_pybytes_custom_method_values(signal_number, [pin()]) - def send_pybytes_custom_method_values(self, method_id, parameters): + def send_pybytes_custom_method_values(self, method_id, parameters, topic=None): if(isinstance(parameters[0], int)): values = bytearray(struct.pack(">i", parameters[0])) values.append(constants.__INTEGER) self.__send_pybytes_message_variable( - constants.__COMMAND_CUSTOM_METHOD, method_id, values + constants.__COMMAND_CUSTOM_METHOD, method_id, values, topic ) elif(isinstance(parameters[0], float)): values = bytearray(struct.pack(" 2: + token = x[1] + rcv_data = rcv_data[len(self.__pack_tocken_prefix) + len(token) + len(self.__pack_tocken_sep):] + pkt = 'BR %d B from %s (%s), to %s ( %d): %s' % (len(rcv_data), token, rcv_ip, dest_ip, dest_port, str(rcv_data)) + print_debug(99, 'Pymesh node packet: {} '.format(pkt)) + self.__pybytes.send_node_signal(1, str(rcv_data.decode()).replace("#", ""), token.decode()) + return + + def get_config(self, token, silent=False): + url = '{}://{}/pymesh/{}'.format( + constants.__DEFAULT_PYCONFIG_PROTOCOL, + constants.__DEFAULT_PYCONFIG_DOMAIN, + token + ) + try: + pymesh = urequest.get(url, headers={'content-type': 'application/json'}) + except Exception as e: + if not silent: + print("Exception: {}".format(e)) + return pymesh.content + + def write_config(self, wmac, file='/flash/pymesh_config.json', pymeshSettings={}, silent=False): + try: + customSettings = json.loads(pymeshSettings.decode()) + default = { + "LoRa": customSettings["LoRa"], + "Pymesh": customSettings["Pymesh"], + "debug": 5, + "ble_api": False, + "ble_name_prefix": "Device-{}".format(wmac), + "br_prio": 0, + "br_ena": False, + "autostart": True + } + + f = open(file, 'w') + f.write(json.dumps(default).encode('utf-8')) + f.close() + if not silent: + print("Pymesh configuration written to {}".format(file)) + except Exception as e: + if not silent: + print("Exception: {}".format(e)) diff --git a/esp32/get_idf_libs.py b/esp32/get_idf_libs.py index c37c78ad26..b2bd8129b0 100755 --- a/esp32/get_idf_libs.py +++ b/esp32/get_idf_libs.py @@ -87,8 +87,11 @@ def main(): shutil.copy(src + '/esp32/esp32.project.ld', ".") # copy the generated sdkconfig.h - shutil.copy(src + '/include/sdkconfig.h', ".") - + with open(src + '/include/sdkconfig.h', 'r') as input: + content = input.read() + with open(os.path.dirname(os.path.realpath(__file__)) + '/sdkconfig.h', 'w') as output: + output.write(content.replace('#define CONFIG_SECURE_BOOT_ENABLED 1','')) + shutil.rmtree(dsttmpbl) shutil.rmtree(dsttmpapp) diff --git a/esp32/mods/modlte.c b/esp32/mods/modlte.c index 589c9b336a..b3b39d7629 100644 --- a/esp32/mods/modlte.c +++ b/esp32/mods/modlte.c @@ -68,6 +68,8 @@ #include "modmachine.h" #include "mpirq.h" +#include "str_utils.h" + /****************************************************************************** DEFINE TYPES ******************************************************************************/ @@ -83,6 +85,23 @@ #define SQNS_SW_FULL_BAND_SUPPORT 41000 #define SQNS_SW_5_8_BAND_SUPPORT 39000 + +// PSM Power saving mode +// PERIOD, aka Requested Periodic TAU (T3412), in GPRS Timer 3 format +#define PSM_PERIOD_2S 0b011 +#define PSM_PERIOD_30S 0b100 +#define PSM_PERIOD_1M 0b101 +#define PSM_PERIOD_10M 0b000 +#define PSM_PERIOD_1H 0b001 +#define PSM_PERIOD_10H 0b010 +#define PSM_PERIOD_320H 0b110 +#define PSM_PERIOD_DISABLED 0b111 +// ACTIVE, aka Requested Active Time (T3324), in GPRS Timer 2 format +#define PSM_ACTIVE_2S 0b000 +#define PSM_ACTIVE_1M 0b001 +#define PSM_ACTIVE_6M 0b010 +#define PSM_ACTIVE_DISABLED 0b111 + /****************************************************************************** DECLARE PRIVATE DATA ******************************************************************************/ @@ -95,6 +114,7 @@ uart_config_t lte_uart_config0; uart_config_t lte_uart_config1; static bool lte_legacyattach_flag = true; +static bool lte_debug = false; static bool lte_ue_is_out_of_coverage = false; @@ -175,13 +195,17 @@ static void lte_callback_handler(void* arg) static bool lte_push_at_command_ext(char *cmd_str, uint32_t timeout, const char *expected_rsp, size_t len) { lte_task_cmd_data_t cmd = { .timeout = timeout, .dataLen = len}; memcpy(cmd.data, cmd_str, len); - //printf("[CMD] %s\n", cmd_str); + uint32_t start = mp_hal_ticks_ms(); + if (lte_debug) + printf("[AT] %u %s\n", start, cmd_str); lteppp_send_at_command(&cmd, &modlte_rsp); if ((expected_rsp == NULL) || (strstr(modlte_rsp.data, expected_rsp) != NULL)) { - //printf("[OK] %s\n", modlte_rsp.data); + if (lte_debug) + printf("[AT-OK] +%u %s\n", mp_hal_ticks_ms()-start, modlte_rsp.data); return true; } - //printf("[FAIL] %s\n", modlte_rsp.data); + if (lte_debug) + printf("[AT-FAIL] +%u %s\n", mp_hal_ticks_ms()-start, modlte_rsp.data); return false; } @@ -404,7 +428,8 @@ static void TASK_LTE_UPGRADE(void *pvParameters){ // Micro Python bindings; LTE class static mp_obj_t lte_init_helper(lte_obj_t *self, const mp_arg_val_t *args) { - char at_cmd[LTE_AT_CMD_SIZE_MAX - 4]; + const size_t at_cmd_len = LTE_AT_CMD_SIZE_MAX - 4; + char at_cmd[at_cmd_len]; lte_modem_conn_state_t modem_state; if (lte_obj.init) { @@ -422,7 +447,7 @@ static mp_obj_t lte_init_helper(lte_obj_t *self, const mp_arg_val_t *args) { MP_THREAD_GIL_ENTER(); if (E_LTE_MODEM_DISCONNECTED == lteppp_modem_state()) { xSemaphoreGive(xLTE_modem_Conn_Sem); - nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, "Couldn't connect to Modem!")); + nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, "Couldn't connect to Modem (modem_state=disconnected)")); } break; case E_LTE_MODEM_CONNECTING: @@ -430,16 +455,15 @@ static mp_obj_t lte_init_helper(lte_obj_t *self, const mp_arg_val_t *args) { xSemaphoreTake(xLTE_modem_Conn_Sem, portMAX_DELAY); if (E_LTE_MODEM_DISCONNECTED == lteppp_modem_state()) { xSemaphoreGive(xLTE_modem_Conn_Sem); - nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, "Couldn't connect to Modem!")); + nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, "Couldn't connect to Modem (modem_state=connecting)")); } break; case E_LTE_MODEM_CONNECTED: //continue break; default: - nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, "Couldn't connect to Modem!")); + nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, "Couldn't connect to Modem (modem_state=default)")); break; - } lte_obj.cid = args[1].u_int; @@ -486,15 +510,102 @@ static mp_obj_t lte_init_helper(lte_obj_t *self, const mp_arg_val_t *args) { lteppp_set_state(E_LTE_IDLE); mod_network_register_nic(<e_obj); lte_obj.init = true; + + // configure PSM + u8_t psm_period_value = args[4].u_int; + u8_t psm_period_unit = args[5].u_int; + u8_t psm_active_value = args[6].u_int; + u8_t psm_active_unit = args[7].u_int; + if ( psm_period_unit != PSM_PERIOD_DISABLED && psm_active_unit != PSM_ACTIVE_DISABLED ) { + u8_t psm_period = ( psm_period_unit << 5 ) | psm_period_value; + u8_t psm_active = ( psm_active_unit << 5 ) | psm_active_value; + char p[9]; + char a[9]; + sprint_binary_u8(p, psm_period); + sprint_binary_u8(a, psm_active); + snprintf(at_cmd, at_cmd_len, "AT+CPSMS=1,,,\"%s\",\"%s\"", p, a); + lte_push_at_command(at_cmd, LTE_RX_TIMEOUT_MAX_MS); + } + xSemaphoreGive(xLTE_modem_Conn_Sem); return mp_const_none; } +STATIC mp_obj_t lte_psm(mp_uint_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + lte_check_init(); + lte_check_inppp(); + mp_obj_t tuple[5]; + static const qstr psm_info_fields[] = { + MP_QSTR_enabled, + MP_QSTR_period_value, + MP_QSTR_period_unit, + MP_QSTR_active_value, + MP_QSTR_active_unit, + }; + + lte_push_at_command("AT+CPSMS?", LTE_RX_TIMEOUT_MAX_MS); + const char *resp = modlte_rsp.data; + char *pos; + if ( ( pos = strstr(resp, "+CPSMS: ") ) ) { + // decode the resp: + // +CPSMS: ,[],[],[],[] + + // go to + pos += strlen_const("+CPSMS: "); + tuple[0] = mp_obj_new_bool(*pos == '1'); + + // go to + pos += strlen_const("1,"); + + // find + pos = strstr(pos, ","); + pos++; + + // find + pos = strstr(pos, ","); + pos++; // , + pos++; // " + + // get three digit TAU unit + char* oldpos = pos; + tuple[2] = mp_obj_new_int_from_str_len( (const char**) &pos, 3, false, 2); + assert( pos == oldpos + 3); // mp_obj_new_int_from_str_len is supposed to consume exactly 3 characters + + // get five digit TAU value + tuple[1] = mp_obj_new_int_from_str_len( (const char**) &pos, 5, false, 2); + + // find + pos = strstr(pos, ","); + pos++; // , + pos++; // " + + // get three digit ActiveTime unit + oldpos = pos; + tuple[4] = mp_obj_new_int_from_str_len( (const char**) &pos, 3, false, 2); + assert( pos == oldpos + 3); // mp_obj_new_int_from_str_len is supposed to consume exactly 3 characters + + // get five digit ActiveTime value + tuple[3] = mp_obj_new_int_from_str_len( (const char**) &pos, 5, false, 2); + + } else { + nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, "Failed to read PSM setting")); + } + + return mp_obj_new_attrtuple(psm_info_fields, 5, tuple); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(lte_psm_obj, 1, lte_psm); + + static const mp_arg_t lte_init_args[] = { { MP_QSTR_id, MP_ARG_INT, {.u_int = 0} }, { MP_QSTR_carrier, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, { MP_QSTR_cid, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 1} }, - { MP_QSTR_legacyattach, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = true} } + { MP_QSTR_legacyattach, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = true} }, + { MP_QSTR_debug, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} }, + { MP_QSTR_psm_period_value, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0 } }, + { MP_QSTR_psm_period_unit, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = PSM_PERIOD_DISABLED } }, + { MP_QSTR_psm_active_value, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0 } }, + { MP_QSTR_psm_active_unit, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = PSM_ACTIVE_DISABLED } }, }; static mp_obj_t lte_make_new(const mp_obj_type_t *type, mp_uint_t n_args, mp_uint_t n_kw, const mp_obj_t *all_args) { @@ -504,6 +615,8 @@ static mp_obj_t lte_make_new(const mp_obj_type_t *type, mp_uint_t n_args, mp_uin // parse args mp_arg_val_t args[MP_ARRAY_SIZE(lte_init_args)]; mp_arg_parse_all(n_args, all_args, &kw_args, MP_ARRAY_SIZE(args), lte_init_args, args); + if (args[4].u_bool) + lte_debug = true; // setup the object lte_obj_t *self = <e_obj; @@ -515,6 +628,30 @@ static mp_obj_t lte_make_new(const mp_obj_type_t *type, mp_uint_t n_args, mp_uin nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, mpexception_os_resource_not_avaliable)); } } + + // check psm args + u8_t psm_period_value = args[5].u_int; + u8_t psm_period_unit = args[6].u_int; + u8_t psm_active_value = args[7].u_int; + u8_t psm_active_unit = args[8].u_int; + if (psm_period_unit > 7) + nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, "Invalid psm_period_unit")); + if (psm_period_value > 31) + nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, "Invalid psm_period_value")); + switch(psm_active_unit){ + case PSM_ACTIVE_2S: + case PSM_ACTIVE_1M: + case PSM_ACTIVE_6M: + case PSM_ACTIVE_DISABLED: + // ok, nothing to do + break; + default: + nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, "Invalid psm_active_unit")); + break; + } + if (psm_active_value > 31) + nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, "Invalid psm_active_value")); + // start the peripheral lte_init_helper(self, &args[1]); return (mp_obj_t)self; @@ -524,6 +661,7 @@ STATIC mp_obj_t lte_init(mp_uint_t n_args, const mp_obj_t *pos_args, mp_map_t *k // parse args mp_arg_val_t args[MP_ARRAY_SIZE(lte_init_args) - 1]; mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(args), <e_init_args[1], args); + lte_debug = args[3].u_bool; return lte_init_helper(pos_args[0], args); } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(lte_init_obj, 1, lte_init); @@ -1049,12 +1187,12 @@ STATIC mp_obj_t lte_disconnect(mp_obj_t self_in) { lte_pause_ppp(); } lteppp_set_state(E_LTE_ATTACHED); - lte_push_at_command("ATH", LTE_RX_TIMEOUT_MIN_MS); + lte_push_at_command("ATH", LTE_RX_TIMEOUT_MAX_MS); while (true) { - mp_hal_delay_ms(LTE_RX_TIMEOUT_MIN_MS); if (lte_push_at_command("AT", LTE_RX_TIMEOUT_MAX_MS)) { break; } + mp_hal_delay_ms(LTE_RX_TIMEOUT_MIN_MS); } lte_check_attached(lte_legacyattach_flag); mod_network_deregister_nic(<e_obj); @@ -1398,6 +1536,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(lte_events_obj, lte_events); STATIC const mp_map_elem_t lte_locals_dict_table[] = { { MP_OBJ_NEW_QSTR(MP_QSTR_init), (mp_obj_t)<e_init_obj }, { MP_OBJ_NEW_QSTR(MP_QSTR_deinit), (mp_obj_t)<e_deinit_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_psm), (mp_obj_t)<e_psm_obj }, { MP_OBJ_NEW_QSTR(MP_QSTR_attach), (mp_obj_t)<e_attach_obj }, { MP_OBJ_NEW_QSTR(MP_QSTR_dettach), (mp_obj_t)<e_detach_obj }, { MP_OBJ_NEW_QSTR(MP_QSTR_detach), (mp_obj_t)<e_detach_obj }, /* backward compatibility for dettach method FIXME */ @@ -1426,6 +1565,20 @@ STATIC const mp_map_elem_t lte_locals_dict_table[] = { { MP_OBJ_NEW_QSTR(MP_QSTR_IP), MP_OBJ_NEW_QSTR(MP_QSTR_IP) }, { MP_OBJ_NEW_QSTR(MP_QSTR_IPV4V6), MP_OBJ_NEW_QSTR(MP_QSTR_IPV4V6) }, { MP_OBJ_NEW_QSTR(MP_QSTR_EVENT_COVERAGE_LOSS), MP_OBJ_NEW_SMALL_INT(LTE_TRIGGER_SIG_LOST) }, + // PSM Power Saving Mode + { MP_OBJ_NEW_QSTR(MP_QSTR_PSM_PERIOD_2S), MP_OBJ_NEW_SMALL_INT(PSM_PERIOD_2S) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_PSM_PERIOD_30S), MP_OBJ_NEW_SMALL_INT(PSM_PERIOD_30S) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_PSM_PERIOD_1M), MP_OBJ_NEW_SMALL_INT(PSM_PERIOD_1M) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_PSM_PERIOD_10M), MP_OBJ_NEW_SMALL_INT(PSM_PERIOD_10M) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_PSM_PERIOD_1H), MP_OBJ_NEW_SMALL_INT(PSM_PERIOD_1H) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_PSM_PERIOD_10H), MP_OBJ_NEW_SMALL_INT(PSM_PERIOD_10H) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_PSM_PERIOD_320H), MP_OBJ_NEW_SMALL_INT(PSM_PERIOD_320H) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_PSM_PERIOD_DISABLED), MP_OBJ_NEW_SMALL_INT(PSM_PERIOD_DISABLED) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_PSM_ACTIVE_2S), MP_OBJ_NEW_SMALL_INT(PSM_ACTIVE_2S) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_PSM_ACTIVE_1M), MP_OBJ_NEW_SMALL_INT(PSM_ACTIVE_1M) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_PSM_ACTIVE_6M), MP_OBJ_NEW_SMALL_INT(PSM_ACTIVE_6M) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_PSM_ACTIVE_DISABLED), MP_OBJ_NEW_SMALL_INT(PSM_ACTIVE_DISABLED) }, + }; STATIC MP_DEFINE_CONST_DICT(lte_locals_dict, lte_locals_dict_table); diff --git a/esp32/pycom_version.h b/esp32/pycom_version.h index 11e7bc4182..e3cd562a1c 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.2.rc6" +#define SW_VERSION_NUMBER "1.20.2.rc7" #define LORAWAN_VERSION_NUMBER "1.0.2" #define SIGFOX_VERSION_NUMBER "1.0.1" #if (VARIANT == PYBYTES) -#define PYBYTES_VERSION_NUMBER "1.3.1" +#define PYBYTES_VERSION_NUMBER "1.4.0" #endif #endif /* VERSION_H_ */ diff --git a/esp32/tools/idfVerCheck.sh b/esp32/tools/idfVerCheck.sh index 385beeb1c8..3323cb8e0e 100644 --- a/esp32/tools/idfVerCheck.sh +++ b/esp32/tools/idfVerCheck.sh @@ -8,26 +8,26 @@ # available at https://www.pycom.io/opensource/licensing # -IDF_VER="idf_v"$2 -CURR_VER="$(git --git-dir=$1/.git branch | grep \* | cut -d ' ' -f2)" +IDF_HASH=$2 +IDF_PATH=$1 +CURR_HASH=$(git -c core.abbrev=7 --git-dir=$IDF_PATH/.git rev-parse --short HEAD) -if [ "${CURR_VER}" = "${IDF_VER}" ]; then - echo "IDF Version OK!" +if [ "${CURR_HASH}" = "${IDF_HASH}" ]; then + echo "IDF Version OK! $IDF_HASH" exit 0 else - echo "Incompatible IDF version...Checking out IDF version $2!" - if ! git --git-dir=$1/.git --work-tree=$1 checkout ${IDF_VER} ; then - echo "Cannot checkout IDF version ${IDF_VER}!...Please make sure latest idf_v${IDF_VER} branch is fetched" >&2 - exit 1 - fi - cd ${IDF_PATH} - if ! git submodule sync ; then - echo "Cannot checkout IDF version ${IDF_VER}!...Please make sure latest idf_v${IDF_VER} branch is fetched" >&2 - exit 1 - fi - if ! git submodule update --init ; then - echo "Cannot checkout IDF version ${IDF_VER}!...Please make sure latest idf_v${IDF_VER} branch is fetched" >&2 - exit 1 - fi - exit 0 -fi \ No newline at end of file + echo " +Incompatible IDF git hash: + +$IDF_HASH is expected from IDF_HASH from Makefile, but +$CURR_HASH is what IDF_PATH=$IDF_PATH is pointing at. + +You should probably update one (or multiple) of: + * IDF_PATH environment variable + * IDF_HASH variable in esp32/Makefile + * IDF commit, e.g. +cd \$IDF_PATH && git checkout $IDF_HASH && git submodule sync && git submodule update --init --recursive && cd - + +" + exit 1 +fi diff --git a/esp32/util/str_utils.c b/esp32/util/str_utils.c new file mode 100644 index 0000000000..8702e75610 --- /dev/null +++ b/esp32/util/str_utils.c @@ -0,0 +1,20 @@ +#include "str_utils.h" +#include + +/** + * Create a string representation of a uint8 + */ +void sprint_binary_u8(char* s, uint8_t v){ + size_t len = 9; // eight bits plus '\0' + snprintf(s, len, "%u%u%u%u%u%u%u%u", + (v & 0b10000000) >> 7, + (v & 0b01000000) >> 6, + (v & 0b00100000) >> 5, + (v & 0b00010000) >> 4, + (v & 0b00001000) >> 3, + (v & 0b00000100) >> 2, + (v & 0b00000010) >> 1, + (v & 0b00000001) >> 0 + ); +} + diff --git a/esp32/util/str_utils.h b/esp32/util/str_utils.h new file mode 100644 index 0000000000..258b6d1dc0 --- /dev/null +++ b/esp32/util/str_utils.h @@ -0,0 +1,17 @@ +#ifndef STR_UTILS_H +#define STR_UTILS_H + +#include +#include + +/** + * Determine the length of str at compile time + * + * The length is excluding the terminating \0 just as strlen() + * make sure you pass a compile time constant + */ +#define strlen_const(string) (sizeof(string)-1) + +void sprint_binary_u8(char* s, uint8_t v); + +#endif