diff --git a/.issuetracker b/.issuetracker new file mode 100644 index 0000000000..21c2846606 --- /dev/null +++ b/.issuetracker @@ -0,0 +1,7 @@ +# Integration with Issue Tracker +# +# (note that '\' need to be escaped). + +[issuetracker "PYFW jira rule"] + regex = "PYFW-(\\d+)" + url = "https://pycomiot.atlassian.net/browse/PYFW-$1" diff --git a/esp32/Makefile b/esp32/Makefile index 4f2efa8b89..d15d8fef76 100644 --- a/esp32/Makefile +++ b/esp32/Makefile @@ -14,6 +14,8 @@ ifeq ($(wildcard boards/$(BOARD)/.),) $(error Invalid BOARD specified) endif +IDF_VERSION=3.1 + TARGET ?= boot_app OEM ?= 0 @@ -70,9 +72,6 @@ ifndef PROJECT_PATH PROJECT_PATH := $(abspath $(dir $(firstword $(MAKEFILE_LIST)))) endif -#check Frozen files changes -$(shell bash tools/mpy-build-check.sh $(BOARD) $(BTYPE) $(VARIANT)) - FROZEN_MPY_DIR = frozen include ../py/mkenv.mk @@ -130,7 +129,7 @@ endif # Enable or Disable LTE_LOG_BUFF ifeq ($(BOARD), $(filter $(BOARD), GPY FIPY)) -ifeq ($(LTE_DEBUG_BUFF),1) +ifeq ($(LTE_LOG_BUFF),1) CFLAGS += -DLTE_DEBUG_BUFF endif #ifeq ($(LTE_LOG_BUFF),1) endif #ifeq ($(BOARD), $(filter $(BOARD), GPY FIPY)) diff --git a/esp32/application.mk b/esp32/application.mk index 0adb380ac0..cd37579e19 100644 --- a/esp32/application.mk +++ b/esp32/application.mk @@ -503,7 +503,7 @@ endif ifeq ($(TARGET), boot_app) all: $(BOOT_BIN) $(APP_BIN) endif -.PHONY: all +.PHONY: all CHECK_DEP $(info $(VARIANT) Variant) ifeq ($(SECURE), on) @@ -562,10 +562,6 @@ $(BUILD)/bootloader/bootloader.a: $(BOOT_OBJ) sdkconfig.h $(Q) $(AR) cru $@ $^ $(BUILD)/bootloader/bootloader.elf: $(BUILD)/bootloader/bootloader.a $(SECURE_BOOT_VERIFICATION_KEY) -ifeq ($(COPY_IDF_LIB), 1) - $(ECHO) "COPY IDF LIBRARIES $@" - $(Q) $(PYTHON) get_idf_libs.py --idflibs $(IDF_PATH)/examples/wifi/scan/build -endif ifeq ($(SECURE), on) # unpack libbootloader_support.a, and archive again using the right key for verifying signatures $(ECHO) "Inserting verification key $(SECURE_BOOT_VERIFICATION_KEY) in $@" @@ -627,10 +623,6 @@ $(BUILD)/application.a: $(OBJ) $(Q) rm -f $@ $(Q) $(AR) cru $@ $^ $(BUILD)/application.elf: $(BUILD)/application.a $(BUILD)/esp32_out.ld $(SECURE_BOOT_VERIFICATION_KEY) -ifeq ($(COPY_IDF_LIB), 1) - $(ECHO) "COPY IDF LIBRARIES $@" - $(Q) $(PYTHON) get_idf_libs.py --idflibs $(IDF_PATH)/examples/wifi/scan/build -endif ifeq ($(SECURE), on) # unpack libbootloader_support.a, and archive again using the right key for verifying signatures $(ECHO) "Inserting verification key $(SECURE_BOOT_VERIFICATION_KEY) in $@" @@ -750,6 +742,11 @@ GEN_PINS_SRC = $(BUILD)/pins.c GEN_PINS_HDR = $(HEADER_BUILD)/pins.h GEN_PINS_QSTR = $(BUILD)/pins_qstr.h +.NOTPARALLEL: CHECK_DEP $(OBJ) +.NOTPARALLEL: CHECK_DEP $(BOOT_OBJ) + +$(BOOT_OBJ) $(OBJ): | CHECK_DEP + # Making OBJ use an order-only dependency on the generated pins.h file # has the side effect of making the pins.h file before we actually compile # any of the objects. The normal dependency generation will deal with the @@ -757,6 +754,15 @@ GEN_PINS_QSTR = $(BUILD)/pins_qstr.h # which source files might need it. $(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/mpy-build-check.sh $(BOARD) $(BTYPE) $(VARIANT) +ifeq ($(COPY_IDF_LIB), 1) + $(ECHO) "COPY IDF LIBRARIES" + $(Q) $(PYTHON) get_idf_libs.py --idflibs $(IDF_PATH)/examples/wifi/scan/build +endif + # Call make-pins.py to generate both pins_gen.c and pins.h $(GEN_PINS_SRC) $(GEN_PINS_HDR) $(GEN_PINS_QSTR): $(BOARD_PINS) $(MAKE_PINS) $(AF_FILE) $(PREFIX_FILE) | $(HEADER_BUILD) $(ECHO) "Create $@" diff --git a/esp32/frozen/LTE/sqnsupgrade.py b/esp32/frozen/LTE/sqnsupgrade.py index ed4f3cffcf..87d1e72d0a 100644 --- a/esp32/frozen/LTE/sqnsupgrade.py +++ b/esp32/frozen/LTE/sqnsupgrade.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -VERSION = "1.2.5" +VERSION = "1.2.6" # Copyright (c) 2019, Pycom Limited. # @@ -339,7 +339,7 @@ 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): + 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): self.__wait_msg = False mirror = True if atneg_only else mirror recover = True if atneg_only else load_ffh @@ -365,7 +365,7 @@ def __run(self, file_path=None, baudrate=921600, port=None, resume=False, load_f external = True br = 115200 if recover and not direct else baudrate if debug: print('Setting baudrate to {}'.format(br)) - self.__serial = serial.Serial(port, br, bytesize=serial.EIGHTBITS, timeout=1 if info_only else 0.1) + self.__serial = serial.Serial(port, br, bytesize=serial.EIGHTBITS, timeout=1 if info_only else 0.1, rtscts=fc) self.__serial.reset_input_buffer() self.__serial.reset_output_buffer() @@ -447,7 +447,7 @@ def __run(self, file_path=None, baudrate=921600, port=None, resume=False, load_f if verbose: print('Sending AT+FSRDFILE="/fs/crashdump"') self.__serial.write(b'AT+FSRDFILE="/fs/crashdump"\r\n') - response = self.read_rsp(size=100) + response = self.read_rsp(size=1024) if verbose: print('AT+FSRDFILE="/fs/crashdump" returned {}'.format(response)) self.__serial.read() @@ -819,7 +819,7 @@ def at_negotiation(self, baudrate, port, max_try, mirror, atneg_only, debug, tar self.__serial = UART(1, baudrate=target_baudrate, pins=self.__pins, timeout_chars=100) else: self.__serial = None - self.__serial = serial.Serial(port, target_baudrate, bytesize=serial.EIGHTBITS, timeout=0.1) + self.__serial = serial.Serial(port, target_baudrate, bytesize=serial.EIGHTBITS, timeout=0.1, rtscts=fc) self.__serial.reset_input_buffer() self.__serial.reset_output_buffer() self.__serial.flush() @@ -882,10 +882,11 @@ def upgrade(self, ffile, mfile=None, baudrate=921600, retry=False, resume=False, 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.__check_br(verbose=verbose, debug=debug): - self.__run(bootrom=True, debug=debug, direct=False, pkgdebug=pkgdebug, verbose=verbose, load_fff=True) + success = self.__run(bootrom=True, debug=debug, direct=False, pkgdebug=pkgdebug, verbose=verbose, load_fff=True) self.success_message(verbose=verbose, debug=debug) else: print('Unable to load updater from {}'.format(mfile)) + return success def upgrade_uart(self, ffh_mode=False, mfile=None, retry=False, resume=False, color=0x050505, debug=False, pkgdebug=False, verbose=False, load_fff=True): success = False @@ -923,16 +924,16 @@ def upgrade_uart(self, ffh_mode=False, mfile=None, retry=False, resume=False, co else: print('Unable to upgrade bootrom.') - def show_info(self, port=None, debug=False, verbose=False): - self.__run(port=port, debug=debug, info_only=True, verbose=verbose) + 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): + def upgrade_ext(self, port, ffile, mfile, resume=False, debug=False, pkgdebug=False, verbose=False, load_fff=True, fc=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) + 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): + 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): self.success_message(port=port, verbose=verbose, debug=debug) else: print('Unable to load updater from {}'.format(mfile)) @@ -973,6 +974,7 @@ def run(ffile, mfile=None, baudrate=921600, verbose=False, debug=False, load_fff retry = False resume = False mtools = False + success = False sqnup = sqnsupgrade() if sqnup.check_files(ffile, mfile, debug): state = sqnup.detect_modem_state(debug=debug, hangup=hangup) @@ -991,8 +993,9 @@ def run(ffile, mfile=None, baudrate=921600, verbose=False, debug=False, load_fff mtools = True elif state == -1: detect_error() - 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) reconnect_uart() + return success def uart(ffh_mode=False, mfile=None, color=0x050505, verbose=False, debug=False, hangup=True): print_welcome() @@ -1054,12 +1057,12 @@ 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): + def run(port, ffile, mfile=None, resume=False, debug=False, verbose=False, load_fff=True, fc=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) - def version(port, verbose=False, debug=False): + def version(port, verbose=False, debug=False, fc=False): sqnup = sqnsupgrade() sqnup.show_info(port=port, debug=debug, verbose=verbose) diff --git a/esp32/frozen/Pybytes/_OTA.py b/esp32/frozen/Pybytes/_OTA.py index d772a680c9..532f3c6773 100644 --- a/esp32/frozen/Pybytes/_OTA.py +++ b/esp32/frozen/Pybytes/_OTA.py @@ -1,3 +1,11 @@ +''' +Copyright (c) 2019, 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 +''' + import network import socket import ssl diff --git a/esp32/frozen/Pybytes/_flash_control_OTA.py b/esp32/frozen/Pybytes/_flash_control_OTA.py index 1cacf71980..e1a70cd31e 100644 --- a/esp32/frozen/Pybytes/_flash_control_OTA.py +++ b/esp32/frozen/Pybytes/_flash_control_OTA.py @@ -1,9 +1,18 @@ +''' +Copyright (c) 2019, 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 +''' + import os try: from pybytes_debug import print_debug except: from _pybytes_debug import print_debug + class FCOTA: def __init__(self): pass diff --git a/esp32/frozen/Pybytes/_mqtt.py b/esp32/frozen/Pybytes/_mqtt.py index 395f021209..609a612be4 100644 --- a/esp32/frozen/Pybytes/_mqtt.py +++ b/esp32/frozen/Pybytes/_mqtt.py @@ -1,112 +1,142 @@ -import time +''' +Copyright (c) 2019, 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 +''' + +try: + from mqtt_core import MQTTCore as mqtt_core +except: + from _mqtt_core import MQTTCore as mqtt_core + +try: + from pybytes_constants import MQTTConstants as mqttConst +except: + from _pybytes_constants import MQTTConstants as mqttConst + try: - import mqtt_core + from pybytes_debug import print_debug except: - import _mqtt_core as mqtt_core + from _pybytes_debug import print_debug + +import time class MQTTClient: - def __init__(self, client_id, server, mqtt_download_topic, port=0, user=None, password=None, keepalive=0, ssl=False, - ssl_params={}, reconnect=True): + def __init__( + self, client_id, server, mqtt_download_topic, + pybytes_protocol, port=0, user=None, password=None, + keepalive=0, ssl=False, + ssl_params={}, reconnect=True + ): + if port == 0: + self.__port = 8883 if ssl else 1883 + else: + self.__port = port self.__reconnect = reconnect self.__reconnect_count = 0 self.__reconnecting = False self.__server = server self.__mqtt_download_topic = mqtt_download_topic - self.__mqtt = mqtt_core.MQTTClient(client_id, server, port, user, password, keepalive, - ssl, ssl_params) + self.__pybytes_protocol = pybytes_protocol + self.__clientId = client_id + self.__user = user + self.__password = password + self.init_mqtt_core() + + def init_mqtt_core(self): + self.__mqtt = mqtt_core( + self.__clientId, + True, + mqttConst.MQTTv3_1_1, + receive_timeout=500, + reconnectMethod=self.reconnect + ) + self.__mqtt.configEndpoint(self.__server, self.__port) + self.__mqtt._user = self.__user + self.__mqtt._password = self.__password def getError(self, x): """Return a human readable error instead of its code number""" - # This errors are thrown by connect function, I wouldn't be able to find + # This errors are thrown by connect function, + # I wouldn't be able to find # anywhere a complete list of these error codes ERRORS = { - '-1': 'MQTTClient: Can\'t connect to MQTT server: "{}"'.format(self.__server), - '-4': 'MQTTClient: Bad credentials when connecting to MQTT server: "{}"'.format(self.__server), - '-9984': 'MQTTClient: Invalid certificate validation when connecting to MQTT server: "{}"'.format(self.__server) + '-1': 'MQTTClient: Can\'t connect to MQTT server: "{}"'.format(self.__server), # noqa + '-4': 'MQTTClient: Bad credentials when connecting to MQTT server: "{}"'.format(self.__server), # noqa + '-9984': 'MQTTClient: Invalid certificate validation when connecting to MQTT server: "{}"'.format(self.__server) # noqa } message = str(x) - return ERRORS.get(str(x), 'Unknown error while connecting to MQTT server ' + message) + return ERRORS.get( + str(x), + 'Unknown error while connecting to MQTT server {}'.format(message) + ) def connect(self, clean_session=True): i = 0 while 1: try: - return self.__mqtt.connect(clean_session) - except OSError as e: - print(self.getError(e)) + return self.__mqtt.connect() + except OSError: if (not self.__reconnect): raise Exception('Reconnection Disabled.') i += 1 time.sleep(i) - def set_callback(self, f): - self.__mqtt.set_callback(f) + def set_callback(self, mqtt_client, message): + self.__pybytes_protocol.__process_recv_message(message) def subscribe(self, topic, qos=0): - self.__mqtt.subscribe(topic, qos) - - def check_msg(self): - while 1: - time_before_retry = 10 - if self.__reconnecting == False: - try: - return self.__mqtt.check_msg() - except OSError as e: - print("Error check_msg", e) - - if (not self.__reconnect): - raise Exception('Reconnection Disabled.') - self.reconnect() - break - else: - time.sleep(time_before_retry) + self.__mqtt.subscribe(topic, qos, self.set_callback) + def unsubscribe(self, topic): + return self.__mqtt.unsubscribe(topic) def reconnect(self): if self.__reconnecting: return + + self.init_mqtt_core() + while True: - self.__reconnect_count += 1 + self.__reconnect_count += 1 self.__reconnecting = True try: - self.__mqtt.connect() + if not self.__mqtt.connect(): + time.sleep(self.__reconnect_count) + continue self.subscribe(self.__mqtt_download_topic) - self.__reconnect_count=0 - print('Reconnected to MQTT server: "{}"'.format(self.__server)) + self.__reconnect_count = 0 self.__reconnecting = False break except OSError: - print("Reconnecting failed, will retry in {} seconds".format(self.__reconnect_count)) time.sleep(self.__reconnect_count) - def publish(self, topic, msg, retain=False, qos=0): + def publish(self, topic, msg, retain=False, qos=0, priority=False): while 1: - if self.__reconnecting == False: + if not self.__reconnecting: try: - return self.__mqtt.publish(topic, msg, retain, qos) + # Disable retain for publish by now + return self.__mqtt.publish( + topic, + msg, + qos, + False, + priority=priority + ) except OSError as e: - print("Error publish", e) + print_debug(2, "Error publish", e) if (not self.__reconnect): raise Exception('Reconnection Disabled.') self.reconnect() - break + raise Exception('Error publish.') else: time.sleep(10) - def wait_msg(self): - while 1: - try: - return self.__mqtt.wait_msg() - except OSError as e: - print("Error wait_msg {}".format(e)) - - if (not self.__reconnect): - raise Exception('Reconnection Disabled.') - self.reconnect() - def disconnect(self): self.__mqtt.disconnect() diff --git a/esp32/frozen/Pybytes/_mqtt_core.py b/esp32/frozen/Pybytes/_mqtt_core.py index d9fa9253e7..c71c2fcfc6 100644 --- a/esp32/frozen/Pybytes/_mqtt_core.py +++ b/esp32/frozen/Pybytes/_mqtt_core.py @@ -1,229 +1,401 @@ -'''umqtt is a simple MQTT client for MicroPython. - Original code: https://github.com/micropython/micropython-lib/tree/master/umqtt.simple''' +''' +umqtt is a simple MQTT client for MicroPython. +Original code: https://github.com/micropython/micropython-lib/tree/master/umqtt.simple + +Copyright (c) 2019, 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 +''' + +try: + from msg_handl import MsgHandler as msgHandler +except: + from _msg_handl import MsgHandler as msgHandler + +try: + from pybytes_constants import MQTTConstants as mqttConst +except: + from _pybytes_constants import MQTTConstants as mqttConst +try: + from pybytes_debug import print_debug +except: + from _pybytes_debug import print_debug + import time -import usocket as socket -import ustruct as struct -from ubinascii import hexlify - - -class MQTTException(Exception): - pass - - -class MQTTClient: - - def __init__(self, client_id, server, port=0, user=None, password=None, keepalive=0, - ssl=False, ssl_params={}): - if port == 0: - port = 8883 if ssl else 1883 - self.client_id = client_id - self.sock = None - self.addr = socket.getaddrinfo(server, port)[0][-1] - self.ssl = ssl - self.ssl_params = ssl_params - self.pid = 0 - self.cb = None - self.user = user - self.pswd = password - self.keepalive = keepalive - self.lastWill_topic = None - self.lastWill_msg = None - self.lastWill_qos = 0 - self.lastWill_retain = False - - def _send_str(self, s): - self.sock.write(struct.pack("!H", len(s))) - self.sock.write(s) - - def _recv_len(self): - n = 0 - sh = 0 +import struct +import _thread + + +class MQTTMessage: + def __init__(self): + self.timestamp = 0 + self.state = 0 + self.dup = False + self.mid = 0 + self.topic = "" + self.payload = None + self.qos = 0 + self.retain = False + + +class MQTTCore: + + def __init__( + self, + clientID, + cleanSession, + protocol, + receive_timeout=3000, + reconnectMethod=None + ): + self.client_id = clientID + self._cleanSession = cleanSession + self._protocol = protocol + self._user = "" + self._password = "" + self._connectdisconnectTimeout = 30 + self._mqttOperationTimeout = 5 + self._topic_callback_queue = [] + self._callback_mutex = _thread.allocate_lock() + self._pid = 0 + self._subscribeSent = False + self._unsubscribeSent = False + self._msgHandler = msgHandler( + self._recv_callback, + self.connect, + receive_timeout, + reconnectMethod + ) + + def configEndpoint(self, srcHost, srcPort): + self._msgHandler.setEndpoint(srcHost, srcPort) + + def connect(self): + if not self._msgHandler.createSocketConnection(): + return False + + self._send_connect(self._cleanSession) + + # delay to check the state + count_10ms = 0 + while(count_10ms <= self._connectdisconnectTimeout * 100 and not self._msgHandler.isConnected()): # noqa + count_10ms += 1 + time.sleep(0.01) + + return True if self._msgHandler.isConnected() else False + + def subscribe(self, topic, qos, callback): + if (topic is None or callback is None): + raise TypeError("Invalid subscribe values.") + topic = topic.encode('utf-8') + + header = mqttConst.MSG_SUBSCRIBE | (1 << 1) + pkt = bytearray([header]) + + # packet identifier + len of topic (16 bits) + topic len + QOS + pkt_len = 2 + 2 + len(topic) + 1 + pkt.extend(self._encode_varlen_length(pkt_len)) # len of the remaining + + self._pid += 1 + pkt.extend(self._encode_16(self._pid)) + pkt.extend(self._pascal_string(topic)) + pkt.append(qos) + + self._subscribeSent = False + self._msgHandler.push_on_send_queue(pkt) + + count_10ms = 0 + while(count_10ms <= self._mqttOperationTimeout * 100 and not self._subscribeSent): # noqa + count_10ms += 1 + time.sleep(0.01) + + if self._subscribeSent: + self._callback_mutex.acquire() + self._topic_callback_queue.append((topic, callback)) + self._callback_mutex.release() + return True + + return False + + def publish(self, topic, payload, qos, retain, dup=False, priority=False): + topic = topic.encode('utf-8') if hasattr(topic, 'encode') else topic + payload = payload.encode('utf-8') if hasattr(payload, 'encode') else payload # noqa + + header = mqttConst.MSG_PUBLISH | (dup << 3) | (qos << 1) | retain + pkt_len = (2 + len(topic) + (2 if qos else 0) + (len(payload))) + + pkt = bytearray([header]) + pkt.extend(self._encode_varlen_length(pkt_len)) # len of the remaining + pkt.extend(self._pascal_string(topic)) + if qos: + # todo: I don't think this is the way to deal with the packet id + self._pid += 1 + pkt.extend(self._encode_16(self._pid)) + + pkt = pkt + payload + if priority: + self._msgHandler.priority_send(pkt) + else: + self._msgHandler.push_on_send_queue(pkt) + + def _encode_16(self, x): + return struct.pack("!H", x) + + def _pascal_string(self, s): + return struct.pack("!H", len(s)) + s + + def _encode_varlen_length(self, length): + i = 0 + buff = bytearray() while 1: - b = self.sock.read(1)[0] - n |= (b & 0x7f) << sh - if not b & 0x80: - return n - sh += 7 - - def set_callback(self, f): - self.cb = f - - def set_last_will(self, topic, msg, retain=False, qos=0): - assert 0 <= qos <= 2 - assert topic - self.lastWill_topic = topic - self.lastWill_msg = msg - self.lastWill_qos = qos - self.lastWill_retain = retain - - def connect(self, clean_session=True): - if (self.sock): - # https://pycomiot.atlassian.net/browse/PB-358 - try: - self.sock.send('') # can send == closeable - self.sock.close() - except Exception as e: - self.sock = None # socket is not closable - import gc - gc.collect() - - self.sock = socket.socket() - self.sock.connect(self.addr) - if self.ssl == True: - import ussl - if self.ssl_params != None and self.ssl_params.get('ca_certs'): - self.ssl_params["cert_reqs"] = ussl.CERT_REQUIRED + buff.append(length % 128) + length = length // 128 + if length > 0: + buff[i] = buff[i] | 0x80 + i += 1 else: - print("WARNING: consider enabling certificate validation") - self.sock = ussl.wrap_socket(self.sock, **self.ssl_params) - print("Using MQTT over TLS") - else: - print("WARNING: consider enabling TLS by using pybytes.enable_ssl()") - premsg = bytearray(b"\x10\0\0\0\0\0") - msg = bytearray(b"\x04MQTT\x04\x02\0\0") - - size = 10 + 2 + len(self.client_id) - msg[6] = clean_session << 1 - if self.user is not None: - size += 2 + len(self.user) + 2 + len(self.pswd) - msg[6] |= 0xC0 - if self.keepalive: - assert self.keepalive < 65536 - msg[7] |= self.keepalive >> 8 - msg[8] |= self.keepalive & 0x00FF - if self.lastWill_topic: - size += 2 + len(self.lastWill_topic) + 2 + len(self.lastWill_msg) - msg[6] |= 0x4 | (self.lastWill_qos & 0x1) << 3 | (self.lastWill_qos & 0x2) << 3 - msg[6] |= self.lastWill_retain << 5 - - i = 1 - while size > 0x7f: - premsg[i] = (size & 0x7f) | 0x80 - size >>= 7 - i += 1 - premsg[i] = size - - self.sock.write(premsg, i + 2) - self.sock.write(msg) - self._send_str(self.client_id) - - if self.lastWill_topic: - self._send_str(self.lastWill_topic) - self._send_str(self.lastWill_msg) - - if self.user is not None: - self._send_str(self.user) - self._send_str(self.pswd) - - resp = self.sock.read(4) - assert resp[0] == 0x20 and resp[1] == 0x02 - if resp[3] != 0: - raise MQTTException(resp[3]) - return resp[2] & 1 + break + + return buff + + def _topic_matches_sub(self, sub, topic): + result = True + multilevel_wildcard = False + + slen = len(sub) + tlen = len(topic) + + if slen > 0 and tlen > 0: + if (sub[0] == '$' and topic[0] != '$') or (topic[0] == '$' and sub[0] != '$'): # noqa + return False + + spos = 0 + tpos = 0 + + while spos < slen and tpos < tlen: + if sub[spos] == topic[tpos]: + if tpos == tlen-1: + # Check for e.g. foo matching foo/# + if spos == slen-3 and sub[spos+1] == '/' and sub[spos+2] == '#': # noqa + result = True + multilevel_wildcard = True + break + + spos += 1 + tpos += 1 + + if tpos == tlen and spos == slen-1 and sub[spos] == '+': + spos += 1 + result = True + break + else: + if sub[spos] == '+': + spos += 1 + while tpos < tlen and topic[tpos] != '/': + tpos += 1 + if tpos == tlen and spos == slen: + result = True + break + + elif sub[spos] == '#': + multilevel_wildcard = True + if spos+1 != slen: + result = False + break + else: + result = True + break + + else: + result = False + break + + if not multilevel_wildcard and (tpos < tlen or spos < slen): + result = False + + return result + + def _remove_topic_callback(self, topic): + deleted = False + + self._callback_mutex.acquire() + for i in range(0, len(self._topic_callback_queue)): + if self._topic_callback_queue[i][0] == topic: + self._topic_callback_queue.pop(i) + deleted = True + self._callback_mutex.release() + + return deleted + + def unsubscribe(self, topic): + self._unsubscribeSent = False + self._send_unsubscribe(topic, False) + + count_10ms = 0 + while(count_10ms <= self._mqttOperationTimeout * 100 and not self._unsubscribeSent): # noqa + count_10ms += 1 + time.sleep(0.01) + + if self._unsubscribeSent: + topic = topic.encode('utf-8') + return self._remove_topic_callback(topic) + + return False def disconnect(self): - self.sock.write(b"\xe0\0") - self.sock.close() - - def ping(self): - self.sock.write(b"\xc0\0") - - def publish(self, topic, msg, retain=False, qos=0): - pkt = bytearray(b"\x30\0\0\0") - pkt[0] |= qos << 1 | retain - size = 2 + len(topic) + len(msg) - if qos > 0: - size += 2 - assert size < 2097152 - i = 1 - while size > 0x7f: - pkt[i] = (size & 0x7f) | 0x80 - size >>= 7 - i += 1 - pkt[i] = size - # print('publish', hex(len(pkt)), hexlify(pkt).decode('ascii')) - self.sock.write(pkt, i + 1) - self._send_str(topic) - if qos > 0: - self.pid += 1 - pid = self.pid - struct.pack_into("!H", pkt, 0, pid) - self.sock.write(pkt, 2) - self.sock.write(msg) - - if qos == 1: - while 1: - op = self.wait_msg() - if op == 0x40: - size = self.sock.read(1) - assert size == b"\x02" - rcv_pid = self.sock.read(2) - rcv_pid = rcv_pid[0] << 8 | rcv_pid[1] - if pid == rcv_pid: - return - elif qos == 2: - assert 0 - - def subscribe(self, topic, qos=0): - assert self.cb is not None, "Subscribe callback is not set" - pkt = bytearray(b"\x82\0\0\0") - self.pid += 1 - struct.pack_into("!BH", pkt, 1, 2 + 2 + len(topic) + 1, self.pid) - # print('subscribe', hex(len(pkt)), hexlify(pkt).decode('ascii')) - self.sock.write(pkt) - self._send_str(topic) - self.sock.write(qos.to_bytes(1, "little")) - while 1: - op = self.wait_msg() - if op == 0x90: - resp = self.sock.read(4) - # print('Response subscribe', hexlify(resp).decode('ascii')) - assert resp[1] == pkt[2] and resp[2] == pkt[3] - if resp[3] == 0x80: - raise MQTTException(resp[3]) - return - - # Wait for a single incoming MQTT message and process it. - # Subscribed messages are delivered to a callback previously - # set by .set_callback() method. Other (internal) MQTT - # messages processed internally. - def wait_msg(self): - res = self.sock.read(1) - self.sock.setblocking(True) - if res is None: - return None - if res == b"": - # other tls empty response happens ... - # raise OSError(-1) - return - if res == b"\xd0": # PING RESPONCE - size = self.sock.read(1)[0] - assert size == 0 - return None - op = res[0] - if op & 0xf0 != 0x30: - return op - size = self._recv_len() - topic_len = self.sock.read(2) - topic_len = (topic_len[0] << 8) | topic_len[1] - topic = self.sock.read(topic_len) - size -= topic_len + 2 - if op & 6: - pid = self.sock.read(2) - pid = pid[0] << 8 | pid[1] - size -= 2 - msg = self.sock.read(size) - self.cb(topic, msg) - if op & 6 == 2: - pkt = bytearray(b"\x40\x02\0\0") - struct.pack_into("!H", pkt, 2, pid) - self.sock.write(pkt) - elif op & 6 == 4: - assert 0 - - # Checks whether a pending message from server is available. - # If not, returns immediately with None. Otherwise, does - # the same processing as wait_msg. - def check_msg(self): - self.sock.setblocking(False) - return self.wait_msg() + pkt = struct.pack('!BB', mqttConst.MSG_DISCONNECT, 0) + self._msgHandler.push_on_send_queue(pkt) + time.sleep(self._connectdisconnectTimeout) + self._msgHandler.disconnect() + return True + + def _send_connect(self, clean_session): + pkt_len = (12 + len(self.client_id) + # 10 + 2 + len(client_id) + (2 + len(self._user) if self._user else 0) + + (2 + len(self._password) if self._password else 0)) + + flags = (0x80 if self._user else 0x00) | (0x40 if self._password else 0x00) | (0x02 if clean_session else 0x00) # noqa + + pkt = bytearray([mqttConst.MSG_CONNECT]) # connect + pkt.extend(self._encode_varlen_length(pkt_len)) # len of the remaining + # len of "MQTT" (16 bits), protocol name, and protocol version + pkt.extend(b'\x00\x04MQTT\x04') + pkt.append(flags) + pkt.extend(b'\x00\x00') # disable keepalive + pkt.extend(self._pascal_string(self.client_id)) + + if self._user: + pkt.extend(self._pascal_string(self._user)) + if self._password: + pkt.extend(self._pascal_string(self._password)) + + return self._msgHandler.priority_send(pkt) + + def _send_unsubscribe(self, topic, dup=False): + pkt = bytearray() + msg_type = mqttConst.MSG_UNSUBSCRIBE | (dup << 3) | (1 << 1) + pkt.extend(struct.pack("!B", msg_type)) + + remaining_length = 2 + 2 + len(topic) + pkt.extend(self._encode_varlen_length(remaining_length)) + + self._pid += 1 + pkt.extend(self._encode_16(self._pid)) + pkt.extend(self._pascal_string(topic)) + + return self._msgHandler.push_on_send_queue(pkt) + + def _send_puback(self, msg_id): + remaining_length = 2 + pkt = struct.pack( + '!BBH', + mqttConst.MSG_PUBACK, + remaining_length, + msg_id + ) + + return self._msgHandler.push_on_send_queue(pkt) + + def _send_pubrec(self, msg_id): + remaining_length = 2 + pkt = struct.pack( + '!BBH', + mqttConst.MSG_PUBREC, + remaining_length, + msg_id + ) + + return self._msgHandler.push_on_send_queue(pkt) + + def _parse_connack(self, payload): + if len(payload) != 2: + return False + + (flags, result) = struct.unpack("!BB", payload) + + if result == 0: + self._msgHandler.setConnectionState(mqttConst.STATE_CONNECTED) + return True + else: + self._msgHandler.setConnectionState(mqttConst.STATE_DISCONNECTED) + return False + + def _parse_suback(self, payload): + self._subscribeSent = True + return True + + def _parse_puback(self, payload): + return True + + def _notify_message(self, message): + notified = False + self._callback_mutex.acquire() + for t_obj in self._topic_callback_queue: + if self._topic_matches_sub(t_obj[0], message.topic): + t_obj[1](self, message) + notified = True + self._callback_mutex.release() + + return notified + + def _parse_publish(self, cmd, packet): + msg = MQTTMessage() + msg.dup = (cmd & 0x08) >> 3 + msg.qos = (cmd & 0x06) >> 1 + msg.retain = (cmd & 0x01) + + pack_format = "!H" + str(len(packet)-2) + 's' + (slen, packet) = struct.unpack(pack_format, packet) + pack_format = '!' + str(slen) + 's' + str(len(packet)-slen) + 's' + (msg.topic, packet) = struct.unpack(pack_format, packet) + + if len(msg.topic) == 0: + return False + + if msg.qos > 0: + pack_format = "!H" + str(len(packet)-2) + 's' + (msg.mid, packet) = struct.unpack(pack_format, packet) + + msg.payload = packet + + if msg.qos == 0: + self._notify_message(msg) + elif msg.qos == 1: + self._send_puback(msg.mid) + self._notify_message(msg) + elif msg.qos == 2: + self._send_pubrec(msg.mid) + self._notify_message(msg) + else: + return False + + return True + + def _parse_unsuback(self, payload): + self._unsubscribeSent = True + return True + + def _parse_pingresp(self): + self._msgHandler.setPingFlag(True) + return True + + def _recv_callback(self, cmd, payload): + msg_type = cmd & 0xF0 + + if msg_type == mqttConst.MSG_CONNACK: + return self._parse_connack(payload) + elif msg_type == mqttConst.MSG_SUBACK: + return self._parse_suback(payload) + elif msg_type == mqttConst.MSG_PUBACK: + return self._parse_puback(payload) + elif msg_type == mqttConst.MSG_PUBLISH: + return self._parse_publish(cmd, payload) + elif msg_type == mqttConst.MSG_UNSUBACK: + return self._parse_unsuback(payload) + elif msg_type == mqttConst.MSG_PINGRESP: + return self._parse_pingresp() + else: + print_debug(2, 'Unknown message type: %d' % msg_type) + return False diff --git a/esp32/frozen/Pybytes/_msg_handl.py b/esp32/frozen/Pybytes/_msg_handl.py new file mode 100644 index 0000000000..0047a96084 --- /dev/null +++ b/esp32/frozen/Pybytes/_msg_handl.py @@ -0,0 +1,258 @@ +''' +Copyright (c) 2019, 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 +''' + +try: + from pybytes_constants import MQTTConstants as mqttConst +except: + from _pybytes_constants import MQTTConstants as mqttConst +try: + from pybytes_debug import print_debug +except: + from _pybytes_debug import print_debug + +import time +import socket +import _thread +import select +import struct + + +class MsgHandler: + + def __init__( + self, + receive_callback, + connect_helper, + receive_timeout=3000, + reconnectMethod=None + ): + self._host = "" + self._port = -1 + self._sock = None + self._output_queue_size = -1 + self._output_queue_dropbehavior = -1 + self._mqttOperationTimeout = 0 + self._connection_state = mqttConst.STATE_DISCONNECTED + self._conn_state_mutex = _thread.allocate_lock() + self._poll = select.poll() + self._output_queue = [] + self._out_packet_mutex = _thread.allocate_lock() + _thread.stack_size(8192) + _thread.start_new_thread(self._io_thread_func, ()) + self._recv_callback = receive_callback + self._connect_helper = connect_helper + self._pingSent = False + self._ping_interval = 20 + self._waiting_ping_resp = False + self._ping_cutoff = 3 + self._receive_timeout = receive_timeout + self.reconnectMethod = reconnectMethod + + def setOfflineQueueConfiguration(self, queueSize, dropBehavior): + self._output_queue_size = queueSize + self._output_queue_dropbehavior = dropBehavior + + def setEndpoint(self, srcHost, srcPort): + self._host = srcHost + self._port = srcPort + + def setOperationTimeout(self, timeout): + self._mqttOperationTimeout = timeout + + def createSocketConnection(self): + self._conn_state_mutex.acquire() + self._connection_state = mqttConst.STATE_CONNECTING + self._conn_state_mutex.release() + try: + if self._sock: + self._poll.unregister(self._sock) + self._sock.close() + self._sock = None + + self._sock = socket.socket() + self._sock.settimeout(30) + self._sock.connect( + socket.getaddrinfo(self._host, self._port)[0][-1] + ) + self._poll.register(self._sock, select.POLLIN) + except socket.error as err: + print_debug(2, "Socket create error: {0}".format(err)) + self._conn_state_mutex.acquire() + self._connection_state = mqttConst.STATE_DISCONNECTED + self._conn_state_mutex.release() + return False + return True + + def disconnect(self): + if self._sock: + self._sock.close() + self._sock = None + + def isConnected(self): + connected = False + self._conn_state_mutex.acquire() + if self._connection_state == mqttConst.STATE_CONNECTED: + connected = True + self._conn_state_mutex.release() + + return connected + + def setConnectionState(self, state): + self._conn_state_mutex.acquire() + self._connection_state = state + self._conn_state_mutex.release() + + def _drop_message(self): + if self._output_queue_size == -1: + return False + elif (self._output_queue_size == 0) and (self._connection_state == mqttConst.STATE_CONNECTED): # noqa + return False + else: + return True if len(self._output_queue) >= self._output_queue_size else False # noqa + + def push_on_send_queue(self, packet): + if self._drop_message(): + if self._output_queue_dropbehavior == mqttConst.DROP_OLDEST: + self._out_packet_mutex.acquire() + if self._out_packet_mutex.locked(): + self._output_queue.pop(0) + self._out_packet_mutex.release() + else: + return False + + self._out_packet_mutex.acquire() + if self._out_packet_mutex.locked(): + self._output_queue.append(packet) + self._out_packet_mutex.release() + + return True + + def priority_send(self, packet): + msg_sent = False + self._out_packet_mutex.acquire() + msg_sent = self._send_packet(packet) + self._out_packet_mutex.release() + + return msg_sent + + def _receive_packet(self): + try: + if not self._poll.poll(self._receive_timeout): + return False + except Exception: + return False + + # Read message type + try: + self._sock.setblocking(False) + msg_type = self._sock.recv(1) + except socket.error: + self.disconnect() + self.reconnectMethod() + return False + else: + if len(msg_type) == 0: + return False + msg_type = struct.unpack("!B", msg_type)[0] + self._sock.setblocking(True) + + # Read payload length + + """ + using the old header checking to read the + payload length of the received packet + """ + + bytes_remaining = 0 + read_bytes = 0 + payload = b'' + sh = 0 + while True: + try: + b = self._sock.read(1)[0] + except Exception: + return False + bytes_remaining |= (b & 0x7f) << sh + if not b & 0x80: + break + sh += 7 + + # Read payload + try: + if self._sock: + read_bytes = bytes_remaining + while read_bytes > 0: + new_payload = self._sock.recv(read_bytes) + payload += new_payload + read_bytes -= len(new_payload) + new_payload = b'' + except socket.error: + return False + return self._recv_callback(msg_type, payload) + + def _send_pingreq(self): + pkt = struct.pack('!BB', mqttConst.MSG_PINGREQ, 0) + return self.priority_send(pkt) + + def setPingFlag(self, flag): + self._pingSent = flag + + def _send_packet(self, packet): + written = -1 + try: + if self._sock: + written = self._sock.write(packet) + if(written is None): + written = -1 + else: + print_debug(2, 'Packet sent. (Length: %d)' % written) + except socket.error as err: + print_debug(2, 'Socket send error {0}'.format(err)) + return False + + return True if len(packet) == written else False + + def _verify_connection_state(self): + elapsed = time.time() - self._start_time + if not self._waiting_ping_resp and elapsed > self._ping_interval: + if self._connection_state == mqttConst.STATE_CONNECTED: + self._pingSent = False + self._send_pingreq() + self._waiting_ping_resp = True + elif self._connection_state == mqttConst.STATE_DISCONNECTED: + self._connect_helper() + + self._start_time = time.time() + elif self._waiting_ping_resp and (self._connection_state == mqttConst.STATE_CONNECTED or elapsed > self._mqttOperationTimeout): # noqa + if not self._pingSent: + if self._ping_failures <= self._ping_cutoff: + self._ping_failures += 1 + else: + self._connect_helper() + else: + self._ping_failures = 0 + + self._start_time = time.time() + self._waiting_ping_resp = False + + def _io_thread_func(self): + self._start_time = time.time() + self._ping_failures = 0 + while True: + self._out_packet_mutex.acquire() + if self._ping_failures == 0: + if self._out_packet_mutex.locked() and len(self._output_queue) > 0: # noqa + packet = self._output_queue[0] + if self._send_packet(packet): + self._output_queue.pop(0) + elif self.reconnectMethod is not None: + self._output_queue.pop(0) + self.disconnect() + self.reconnectMethod() + self._out_packet_mutex.release() + self._receive_packet() diff --git a/esp32/frozen/Pybytes/_pybytes.py b/esp32/frozen/Pybytes/_pybytes.py index e55e26f2d1..2082181865 100644 --- a/esp32/frozen/Pybytes/_pybytes.py +++ b/esp32/frozen/Pybytes/_pybytes.py @@ -1,9 +1,19 @@ +''' +Copyright (c) 2019, 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 +''' + import os from machine import Timer + try: from pybytes_connection import PybytesConnection except: from _pybytes_connection import PybytesConnection + try: from pybytes_debug import print_debug except: diff --git a/esp32/frozen/Pybytes/_pybytes_ca.py b/esp32/frozen/Pybytes/_pybytes_ca.py index 58d4c6c782..09d96a6ed0 100644 --- a/esp32/frozen/Pybytes/_pybytes_ca.py +++ b/esp32/frozen/Pybytes/_pybytes_ca.py @@ -1,3 +1,11 @@ +''' +Copyright (c) 2019, 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 +''' + PYBYTES_CA = """-----BEGIN CERTIFICATE----- MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/ MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT diff --git a/esp32/frozen/Pybytes/_pybytes_config.py b/esp32/frozen/Pybytes/_pybytes_config.py index f329630e4e..92e4e83b4b 100644 --- a/esp32/frozen/Pybytes/_pybytes_config.py +++ b/esp32/frozen/Pybytes/_pybytes_config.py @@ -1,3 +1,11 @@ +''' +Copyright (c) 2019, 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 PybytesConfig: def read_config(self, file='/flash/pybytes_config.json'): diff --git a/esp32/frozen/Pybytes/_pybytes_connection.py b/esp32/frozen/Pybytes/_pybytes_connection.py index b7c3f49f90..f73d6cf0cd 100644 --- a/esp32/frozen/Pybytes/_pybytes_connection.py +++ b/esp32/frozen/Pybytes/_pybytes_connection.py @@ -1,15 +1,26 @@ +''' +Copyright (c) 2019, 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 +''' + try: from mqtt import MQTTClient except: from _mqtt import MQTTClient + try: from pybytes_protocol import PybytesProtocol except: from _pybytes_protocol import PybytesProtocol + try: from pybytes_constants import constants except: from _pybytes_constants import constants + try: from pybytes_debug import print_debug except: @@ -23,6 +34,7 @@ import binascii from machine import WDT + class PybytesConnection: def __init__(self, config, message_callback): self.__conf = config @@ -35,7 +47,9 @@ def __init__(self, config, message_callback): self.__mqtt_upload_topic = "u" + self.__device_id self.__connection = None self.__connection_status = constants.__CONNECTION_STATUS_DISCONNECTED - self.__pybytes_protocol = PybytesProtocol(config, message_callback, pybytes_connection=self) + self.__pybytes_protocol = PybytesProtocol( + config, message_callback, pybytes_connection=self + ) self.__lora_socket = None self.lora = None self.lora_lock = _thread.allocate_lock() @@ -59,25 +73,30 @@ def print_pretty_response(self, rsp): def __initialise_watchdog(self): if self.__conf.get('connection_watchdog', True): - self.__wifi_lte_watchdog = WDT(timeout=constants.__WDT_TIMEOUT_MILLISECONDS) - print('Initialized watchdog for WiFi and LTE connection with timeout {} ms'.format(constants.__WDT_TIMEOUT_MILLISECONDS)) + self.__wifi_lte_watchdog = WDT( + timeout=constants.__WDT_TIMEOUT_MILLISECONDS + ) + print('Initialized watchdog for WiFi and LTE connection with timeout {} ms'.format(constants.__WDT_TIMEOUT_MILLISECONDS)) # noqa else: - print('Watchdog for WiFi and LTE was disabled, enable with "connection_watchdog": true in pybytes_config.json') + print('Watchdog for WiFi and LTE was disabled, enable with "connection_watchdog": true in pybytes_config.json') # noqa # Establish a connection through WIFI before connecting to mqtt server def connect_wifi(self, reconnect=True, check_interval=0.5): self.__initialise_watchdog() - if self.__connection_status != constants.__CONNECTION_STATUS_DISCONNECTED: - print("Error connect_wifi: Connection already exists. Disconnect First") + if self.__connection_status != constants.__CONNECTION_STATUS_DISCONNECTED: # noqa + print("Error connect_wifi: Connection already exists. Disconnect First") # noqa return False try: from network import WLAN antenna = self.__conf.get('wlan_antenna', WLAN.INT_ANT) - known_nets = [((self.__conf['wifi']['ssid'], self.__conf['wifi']['password']))] + known_nets = [((self.__conf['wifi']['ssid'], self.__conf['wifi']['password']))] # noqa if antenna == WLAN.EXT_ANT: print("WARNING! Using external WiFi antenna.") - '''to connect it to an existing network, the WiFi class must be configured as a station''' + + '''to connect it to an existing network, + the WiFi class must be configured as a station''' + self.wlan = WLAN(mode=WLAN.STA, antenna=antenna) available_nets = self.wlan.scan() @@ -87,7 +106,7 @@ def connect_wifi(self, reconnect=True, check_interval=0.5): try: net_to_use = net_to_use[0] pwd = dict(known_nets)[net_to_use] - sec = [e.sec for e in available_nets if e.ssid == net_to_use][0] + sec = [e.sec for e in available_nets if e.ssid == net_to_use][0] # noqa self.wlan.connect(net_to_use, (sec, pwd), timeout=10000) while not self.wlan.isconnected(): time.sleep(0.1) @@ -101,18 +120,25 @@ def connect_wifi(self, reconnect=True, check_interval=0.5): return False self.__network_type = constants.__NETWORK_TYPE_WIFI print("WiFi connection established") - self.__mqtt_check_interval = check_interval try: - self.__connection = MQTTClient(self.__device_id, self.__host, self.__mqtt_download_topic, user=self.__user_name, - password=self.__device_id, reconnect=reconnect, ssl=self.__ssl, ssl_params = self.__ssl_params) + 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 - self.__pybytes_protocol.start_MQTT(self, check_interval, constants.__NETWORK_TYPE_WIFI) - print("Connected to MQTT {}".format(self.__host)) + 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)) + print('MQTT ERROR! Bad credentials when connecting to server: "{}"'.format(self.__host)) # noqa else: print("MQTT ERROR! {}".format(ex)) return False @@ -127,37 +153,54 @@ def connect_lte(self, reconnect=True, check_interval=0.5): lte_cfg = self.__conf.get('lte') if lte_cfg is not None: if (os.uname()[0] not in ['FiPy', 'GPy']): - print("You need a device with FiPy or GPy firmware to connect via LTE") + print("You need a device with FiPy or GPy firmware to connect via LTE") # noqa return False 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))) - self.lte = LTE(carrier=lte_cfg.get('carrier')) # instantiate the LTE object - print_debug(1, 'LTE attach(band={}, apn={}, type={})'.format(lte_cfg.get('band'), lte_cfg.get('apn'), lte_cfg.get('type'))) - self.lte.attach(band=lte_cfg.get('band'), apn=lte_cfg.get('apn'), type=lte_cfg.get('type')) # attach the cellular modem to a base station + print_debug(1, 'LTE init(carrier={}, cid={})'.format(lte_cfg.get('carrier'), lte_cfg.get('cid', 1))) # noqa + # instantiate the LTE object + self.lte = LTE(carrier=lte_cfg.get('carrier')) + print_debug( + 1, + 'LTE attach(band={}, apn={}, type={})'.format( + lte_cfg.get('band'), + lte_cfg.get('apn'), + lte_cfg.get('type') + ) + ) + self.lte.attach(band=lte_cfg.get('band'), apn=lte_cfg.get('apn'), type=lte_cfg.get('type')) # noqa # attach the cellular modem to a base station while not self.lte.isattached(): time.sleep(0.25) time.sleep(1) print_debug(1, 'LTE connect()') - self.lte.connect() # start a data session and obtain an IP address + # start a data session and obtain an IP address + self.lte.connect() 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 - self.__mqtt_check_interval = check_interval try: - self.__connection = MQTTClient(self.__device_id, self.__host, self.__mqtt_download_topic, user=self.__user_name, - password=self.__device_id, reconnect=reconnect, ssl=self.__ssl, ssl_params = self.__ssl_params) + 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 - self.__pybytes_protocol.start_MQTT(self, check_interval, constants.__NETWORK_TYPE_WIFI) + self.__connection_status = constants.__CONNECTION_STATUS_CONNECTED_MQTT_LTE # noqa + self.__pybytes_protocol.start_MQTT( + self, + constants.__NETWORK_TYPE_WIFI + ) 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)) + print('MQTT ERROR! Bad credentials when connecting to server: "{}"'.format(self.__host)) # noqa else: print("MQTT ERROR! {}".format(ex)) return False @@ -170,8 +213,8 @@ def connect_lte(self, reconnect=True, check_interval=0.5): # LORA def connect_lora_abp(self, lora_timeout, nanogateway): - if (self.__connection_status != constants.__CONNECTION_STATUS_DISCONNECTED): - print("Error connect_lora_abp: Connection already exists. Disconnect First") + if (self.__connection_status != constants.__CONNECTION_STATUS_DISCONNECTED): # noqa + print("Error connect_lora_abp: Connection already exists. Disconnect First") # noqa return False try: from network import LoRa @@ -187,14 +230,17 @@ def connect_lora_abp(self, lora_timeout, nanogateway): app_swkey = self.__conf['lora']['abp']['app_skey'] timeout_ms = self.__conf.get('lora_timeout', lora_timeout) * 1000 - dev_addr = struct.unpack(">l", binascii.unhexlify(dev_addr.replace(' ', '')))[0] + dev_addr = struct.unpack(">l", binascii.unhexlify(dev_addr.replace(' ', '')))[0] # noqa nwk_swkey = binascii.unhexlify(nwk_swkey.replace(' ', '')) app_swkey = binascii.unhexlify(app_swkey.replace(' ', '')) try: 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) + 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(): @@ -214,8 +260,8 @@ def connect_lora_abp(self, lora_timeout, nanogateway): return False def connect_lora_otta(self, lora_timeout, nanogateway): - if (self.__connection_status != constants.__CONNECTION_STATUS_DISCONNECTED): - print("Error connect_lora_otta: Connection already exists. Disconnect First") + if (self.__connection_status != constants.__CONNECTION_STATUS_DISCONNECTED): # noqa + print("Error connect_lora_otta: Connection already exists. Disconnect First") # noqa return False try: from network import LoRa @@ -226,7 +272,7 @@ def connect_lora_otta(self, lora_timeout, nanogateway): 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'] - timeout_ms = self.__conf.get('lora_timeout',lora_timeout) * 1000 + timeout_ms = self.__conf.get('lora_timeout', lora_timeout) * 1000 self.lora = LoRa(mode=LoRa.LORAWAN) self.lora.nvram_restore() @@ -236,8 +282,11 @@ def connect_lora_otta(self, lora_timeout, nanogateway): app_key = binascii.unhexlify(app_key.replace(' ', '')) try: print("Trying to join LoRa.OTAA for %d seconds..." % lora_timeout) - self.lora.join(activation=LoRa.OTAA, auth=(dev_eui, app_eui, app_key), - timeout=timeout_ms) + self.lora.join( + activation=LoRa.OTAA, + auth=(dev_eui, app_eui, app_key), + timeout=timeout_ms + ) # if you want, uncomment this code, but timeout must be 0 # while not self.lora.has_joined(): @@ -277,12 +326,12 @@ def __open_lora_socket(self, nanogateway): # SIGFOX def connect_sigfox(self): - if (self.__connection_status != constants.__CONNECTION_STATUS_DISCONNECTED): + if (self.__connection_status != constants.__CONNECTION_STATUS_DISCONNECTED): # noqa print("Error: Connection already exists. Disconnect First") pass try: from network import Sigfox - except: + except Exception: print("This device does not support Sigfox connections") return sigfox_config = self.__conf.get('sigfox', {}) @@ -291,14 +340,16 @@ def connect_sigfox(self): try: sf_rcz = int(sigfox_config.get('RCZ', 1)) - 1 if sf_rcz >= 0 and sf_rcz <= 3: - sigfox = Sigfox(mode=Sigfox.SIGFOX, rcz=sf_rcz) - self.__sigfox_socket = socket.socket(socket.AF_SIGFOX, socket.SOCK_RAW) + Sigfox(mode=Sigfox.SIGFOX, rcz=sf_rcz) + self.__sigfox_socket = socket.socket(socket.AF_SIGFOX, socket.SOCK_RAW) # noqa self.__sigfox_socket.setblocking(True) - self.__sigfox_socket.setsockopt(socket.SOL_SIGFOX, socket.SO_RX, False) + self.__sigfox_socket.setsockopt(socket.SOL_SIGFOX, socket.SO_RX, False) # noqa self.__network_type = constants.__NETWORK_TYPE_SIGFOX - self.__connection_status = constants.__CONNECTION_STATUS_CONNECTED_SIGFOX + self.__connection_status = constants.__CONNECTION_STATUS_CONNECTED_SIGFOX # noqa self.__pybytes_protocol.start_Sigfox(self) - print("Connected using Sigfox. Only upload stream is supported") + print( + "Connected using Sigfox. Only upload stream is supported" + ) return True else: print('Invalid Sigfox RCZ specified in config!') @@ -309,29 +360,34 @@ def connect_sigfox(self): # COMMON def disconnect(self): - print_debug(1, 'self.__connection_status={} | self.__network_type={}'.format(self.__connection_status, self.__network_type)) - - if (self.__connection_status == constants.__CONNECTION_STATUS_DISCONNECTED): + print_debug( + 1, + 'self.__connection_status={} | self.__network_type={}'.format( + self.__connection_status, self.__network_type + ) + ) + + if (self.__connection_status == constants.__CONNECTION_STATUS_DISCONNECTED): # noqa print("Already disconnected") pass - if (constants.__CONNECTION_STATUS_CONNECTED_MQTT_WIFI <= self.__connection_status <= constants.__CONNECTION_STATUS_CONNECTED_MQTT_LTE): + if (constants.__CONNECTION_STATUS_CONNECTED_MQTT_WIFI <= self.__connection_status <= constants.__CONNECTION_STATUS_CONNECTED_MQTT_LTE): # noqa print_debug(1, 'MQTT over WIFI||LTE... disconnecting MQTT') try: self.__connection.disconnect() - self.__connection_status = constants.__CONNECTION_STATUS_DISCONNECTED + self.__connection_status = constants.__CONNECTION_STATUS_DISCONNECTED # noqa except Exception as e: print("Error disconnecting: {}".format(e)) - if (self.__connection_status == constants.__CONNECTION_STATUS_CONNECTED_LORA): - print_debug(1, 'Connected over LORA... closing socket and saving nvram') + if (self.__connection_status == constants.__CONNECTION_STATUS_CONNECTED_LORA): # noqa + print_debug(1, 'Connected over LORA... closing socket and saving nvram') # noqa try: self.__lora_socket.close() self.lora.nvram_save() except Exception as e: print("Error disconnecting: {}".format(e)) - if (self.__connection_status == constants.__CONNECTION_STATUS_CONNECTED_SIGFOX): + if (self.__connection_status == constants.__CONNECTION_STATUS_CONNECTED_SIGFOX): # noqa print_debug(1, 'Connected over SIGFOX... closing socket') try: self.__sigfox_socket.close() @@ -349,7 +405,7 @@ def disconnect(self): print_debug(1, 'Connected over LTE... disconnecting') try: lte_cfg = self.__conf.get('lte') - print_debug(1, 'lte.deinit(reset={})'.format(lte_cfg.get('reset', False))) + print_debug(1, 'lte.deinit(reset={})'.format(lte_cfg.get('reset', False))) # noqa self.lte.deinit(reset=lte_cfg.get('reset', False)) except Exception as e: print("Error disconnecting: {}".format(e)) @@ -358,4 +414,4 @@ def disconnect(self): self.__connection_status = constants.__CONNECTION_STATUS_DISCONNECTED def is_connected(self): - return not (self.__connection_status == constants.__CONNECTION_STATUS_DISCONNECTED) + return not (self.__connection_status == constants.__CONNECTION_STATUS_DISCONNECTED) # noqa diff --git a/esp32/frozen/Pybytes/_pybytes_constants.py b/esp32/frozen/Pybytes/_pybytes_constants.py index 9fdbec01f4..aee145e8c4 100644 --- a/esp32/frozen/Pybytes/_pybytes_constants.py +++ b/esp32/frozen/Pybytes/_pybytes_constants.py @@ -1,3 +1,42 @@ +''' +Copyright (c) 2019, 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 MQTTConstants: + # - Protocol types + MQTTv3_1 = 3 + MQTTv3_1_1 = 4 + + # - OfflinePublishQueueing drop behavior + DROP_OLDEST = 0 + DROP_NEWEST = 1 + + # Message types + MSG_CONNECT = 0x10 + MSG_CONNACK = 0x20 + MSG_PUBLISH = 0x30 + MSG_PUBACK = 0x40 + MSG_PUBREC = 0x50 + MSG_PUBREL = 0x60 + MSG_PUBCOMP = 0x70 + MSG_SUBSCRIBE = 0x80 + MSG_SUBACK = 0x90 + MSG_UNSUBSCRIBE = 0xA0 + MSG_UNSUBACK = 0xB0 + MSG_PINGREQ = 0xC0 + MSG_PINGRESP = 0xD0 + MSG_DISCONNECT = 0xE0 + + # Connection state + STATE_CONNECTED = 0x01 + STATE_CONNECTING = 0x02 + STATE_DISCONNECTED = 0x03 + + class constants: __NETWORK_INFO_MASK = 0x30 __NETWORK_TYPE_WIFI = 0 @@ -57,7 +96,8 @@ class constants: __DEVICE_TYPE_LOPY_4 = 0x04 __DEVICE_TYPE_UNKNOWN = 0x05 - __WIFI_NETWORK_FORMAT = ">6sBb" # {"ssid":"%s", "mac_addr":"%s", "channel":"%s", "power":"%s"} + # {"ssid":"%s", "mac_addr":"%s", "channel":"%s", "power":"%s"} + __WIFI_NETWORK_FORMAT = ">6sBb" # __USER_SYSTEM_MASK = 0x80 __NETWORK_TYPE_MASK = 0x30 @@ -65,8 +105,8 @@ class constants: __SIGFOX_WARNING = """WARNING! Your sigfox radio configuration (RC) is currently using the default (1) You can set your RC with command (ex: RC 3): pybytes.set_config('sigfox', {'RCZ': 3}) -See all available zone options for RC at https://support.sigfox.com/docs/radio-configuration """ - - __KEEP_ALIVE_PING_INTERVAL = 600 # in seconds - # set watch dog timeout one minute after __KEEP_ALIVE_PING_INTERVAL - __WDT_TIMEOUT_MILLISECONDS = (__KEEP_ALIVE_PING_INTERVAL + 60) * 1000 +See all available zone options for RC at https://support.sigfox.com/docs/radio-configuration """ # noqa + __ONE_MINUTE = 60 # in seconds + __KEEP_ALIVE_PING_INTERVAL = 600 # in seconds + # set watch dog timeout to 21 minutes ~ (2 * 10) + 1 + __WDT_TIMEOUT_MILLISECONDS = (2 * __KEEP_ALIVE_PING_INTERVAL + __ONE_MINUTE) * 1000 # noqa diff --git a/esp32/frozen/Pybytes/_pybytes_debug.py b/esp32/frozen/Pybytes/_pybytes_debug.py index b348ee06ce..7fa973ec10 100644 --- a/esp32/frozen/Pybytes/_pybytes_debug.py +++ b/esp32/frozen/Pybytes/_pybytes_debug.py @@ -1,6 +1,63 @@ -import pycom -DEBUG = pycom.nvs_get('pybytes_debug') +''' +Copyright (c) 2019, 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 +''' + +import os +import pycom # pylint: disable=import-error + +from machine import RTC +from time import timezone + +# For compatibility with new 1.18 firmware release +try: + DEBUG = pycom.nvs_get('pybytes_debug') +except: + DEBUG = None + def print_debug(level, msg): + """Print log messages into console.""" if DEBUG is not None and level <= DEBUG: print(msg) + + +def print_debug_local(level, msg): + """ + Print log messages. + + log messages will be stored in the device so + the user can access that using FTP or Flash OTA. + """ + if DEBUG is not None and level <= DEBUG: + print_debug(0, 'adding local log') + rtc = RTC() + if not rtc.synced(): + rtc.ntp_sync("pool.ntp.org") + while not rtc.synced(): + pass + current_year, current_month, current_day, current_hour, current_minute, current_second, current_microsecond, current_tzinfo = rtc.now() # noqa + msg = '\n {}-{}-{} {}:{}:{} (GMT+{}) >>> {}'.format( + current_day, + current_month, + current_year, + current_hour, + current_minute, + current_second, + timezone(), + msg + ) + try: + fsize = os.stat('logs.log') + if fsize.st_size > 1000000: + # logs are bigger than 1 MB + os.remove("logs.log") + except Exception: + pass + + log_file = open('logs.log', 'a+') + log_file.write(msg) + log_file.close() diff --git a/esp32/frozen/Pybytes/_pybytes_library.py b/esp32/frozen/Pybytes/_pybytes_library.py index 89f74b231f..20db346011 100644 --- a/esp32/frozen/Pybytes/_pybytes_library.py +++ b/esp32/frozen/Pybytes/_pybytes_library.py @@ -1,7 +1,16 @@ +''' +Copyright (c) 2019, 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 +''' + try: from pybytes_constants import constants except: from _pybytes_constants import constants + try: from pybytes_debug import print_debug except: diff --git a/esp32/frozen/Pybytes/_pybytes_protocol.py b/esp32/frozen/Pybytes/_pybytes_protocol.py index ae709fd0c8..8292243b43 100644 --- a/esp32/frozen/Pybytes/_pybytes_protocol.py +++ b/esp32/frozen/Pybytes/_pybytes_protocol.py @@ -1,23 +1,36 @@ +''' +Copyright (c) 2019, 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 +''' + try: from pybytes_library import PybytesLibrary except: from _pybytes_library import PybytesLibrary + try: from pybytes_constants import constants except: from _pybytes_constants import constants + try: from terminal import Terminal except: from _terminal import Terminal + try: from OTA import WiFiOTA except: from _OTA import WiFiOTA + try: from flash_control_OTA import FCOTA except: from _flash_control_OTA import FCOTA + try: from pybytes_debug import print_debug except: @@ -28,16 +41,13 @@ from machine import PWM from machine import Timer from machine import reset -from machine import WDT import os -import sys import _thread import time -import socket import struct import machine -import binascii + class PybytesProtocol: def __init__(self, config, message_callback, pybytes_connection): @@ -46,9 +56,9 @@ def __init__(self, config, message_callback, pybytes_connection): self.__device_id = config['device_id'] self.__mqtt_download_topic = "d" + self.__device_id self.__mqtt_upload_topic = "u" + self.__device_id - self.__mqtt_check_interval = 0.5 self.__pybytes_connection = pybytes_connection - self.__pybytes_library = PybytesLibrary(pybytes_connection=pybytes_connection, pybytes_protocol=self) + self.__pybytes_library = PybytesLibrary( + pybytes_connection=pybytes_connection, pybytes_protocol=self) self.__user_message_callback = message_callback self.__pins = {} self.__pin_modes = {} @@ -66,44 +76,32 @@ def start_Lora(self, pybytes_connection): _thread.stack_size(self.__thread_stack_size) _thread.start_new_thread(self.__check_lora_messages, ()) - def start_MQTT(self, pybytes_connection, check_interval, networkType): - print_debug(5, "This is PybytesProtocol.start_MQTT(check_interval={}, networkType={})".format(check_interval, networkType)) + def start_MQTT(self, pybytes_connection, networkType): + print_debug(5, "This is PybytesProtocol.start_MQTT") self.__pybytes_connection = pybytes_connection self.__pybytes_library.set_network_type(networkType) - self.__mqtt_check_interval = check_interval - self.__start_recv_mqtt() + self.__pybytes_connection.__connection.subscribe( + self.__mqtt_download_topic) + self.__connectionAlarm = Timer.Alarm( + self.__keep_connection, + constants.__KEEP_ALIVE_PING_INTERVAL, + periodic=True + ) def start_Sigfox(self, pybytes_connection): print_debug(5, "This is PybytesProtocol.start_Sigfox()") - self.__pybytes_library.set_network_type(constants.__NETWORK_TYPE_SIGFOX) + self.__pybytes_library.set_network_type( + constants.__NETWORK_TYPE_SIGFOX) self.__pybytes_connection = pybytes_connection - def __start_recv_mqtt(self): - print_debug(5, "This is PybytesProtocol.__start_recv_mqtt()") - self.__pybytes_connection.__connection.set_callback(self.__recv_mqtt) - self.__pybytes_connection.__connection.subscribe(self.__mqtt_download_topic) - print_debug(2, 'Using {} bytes as stack size'.format(self.__thread_stack_size)) - - _thread.stack_size(self.__thread_stack_size) - _thread.start_new_thread(self.__check_mqtt_message, ()) - self.__connectionAlarm = Timer.Alarm(self.__keep_connection, constants.__KEEP_ALIVE_PING_INTERVAL, periodic=True) - 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 - - def __check_mqtt_message(self): - print_debug(5, "This is PybytesProtocol.__check_mqtt_message()") - while self.__wifi_or_lte_connection(): - try: - self.__pybytes_connection.__connection.check_msg() - time.sleep(self.__mqtt_check_interval) - except Exception as ex: - print("Error receiving MQTT. Ignore this message if you disconnected") - print_debug(2, "Exception: {}".format(ex)) - sys.print_exception(ex) + 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 def __keep_connection(self, alarm): - print_debug(5, "This is PybytesProtocol.__keep_connection(alarm={})".format(alarm)) + print_debug( + 5, + "This is PybytesProtocol.__keep_connection(alarm={})".format(alarm) + ) if self.__wifi_or_lte_connection(): self.send_ping_message() @@ -116,28 +114,24 @@ 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)) + print_debug(5, "Exception in PybytesProtocol.__check_lora_messages: {}".format(ex)) # noqa if (message): self.__process_recv_message(message) time.sleep(0.5) - def __recv_mqtt(self, topic, message): - print_debug(5, "This is PybytesProtocol.__recv_mqtt()") - print_debug(2, 'Topic: {}\n Message: {}'.format(topic, message)) - self.__process_recv_message(message) - def __process_recv_message(self, message): print_debug(5, "This is PybytesProtocol.__process_recv_message()") - network_type, message_type, body = self.__pybytes_library.unpack_message(message) - print_debug(2, 'Recv message of type{}'.format(message_type)) - print_debug(6, "network_type={}, message_type={}\nbody={}".format(network_type, message_type, body)) + if message.payload: + network_type, message_type, body = self.__pybytes_library.unpack_message(message.payload) # noqa + else: + # for lora messages + network_type, message_type, body = self.__pybytes_library.unpack_message(message) # noqa 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): - print_debug(1,'message type pong received, feeding watchdog...') + elif message_type == constants.__TYPE_PONG and self.__conf.get('connection_watchdog', True): # noqa self.__pybytes_connection.__wifi_lte_watchdog.feed() elif (message_type == constants.__TYPE_INFO): @@ -147,20 +141,28 @@ def __process_recv_message(self, message): self.send_network_info_message() elif (message_type == constants.__TYPE_SCAN_INFO): - self.__send_message(self.__pybytes_library.pack_scan_info_message(self.__pybytes_connection.lora)) + self.__send_message( + self.__pybytes_library.pack_scan_info_message( + self.__pybytes_connection.lora + ) + ) elif (message_type == constants.__TYPE_BATTERY_INFO): self.send_battery_info() elif (message_type == constants.__TYPE_OTA): - 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('Connecting to WiFi') + 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("Performing OTA") + print_debug(5, "Performing OTA") result = ota.update() self.send_ota_response(result) time.sleep(1.5) @@ -170,12 +172,12 @@ 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): - print('Not connected, Re-Connecting ...') + if (self.__pybytes_connection.__connection_status == constants.__CONNECTION_STATUS_DISCONNECTED): # noqa + print_debug(5, 'Not connected, Re-Connecting ...') ota.connect() command = body[0] - if (command == constants.__FCOTA_COMMAND_HIERARCHY_ACQUISITION): + if (command == constants.__FCOTA_COMMAND_HIERARCHY_ACQUISITION): # noqa self.send_fcota_ping('acquiring hierarchy...') hierarchy = self.__FCOTA.get_flash_hierarchy() self.send_fcota_hierarchy(hierarchy) @@ -193,11 +195,11 @@ def __process_recv_message(self, message): splittedBody = bodyString.split(',') if (len(splittedBody) >= 2): path = splittedBody[0] - print(path[len(path)-7:len(path)]) + 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) == True): + if (self.__FCOTA.update_file_content(path, newContent) is True): # noqa size = self.__FCOTA.get_file_size(path) self.send_fcota_file(newContent, path, size) if (path[len(path)-7:len(path)] != '.pymakr'): @@ -219,14 +221,16 @@ def __process_recv_message(self, message): self.send_fcota_ping('deleting file...') path = body[1:len(body)].decode() success = self.__FCOTA.delete_file(path) - if (success == True): + if (success is True): self.send_fcota_ping('file deleted!') - self.send_fcota_hierarchy(self.__FCOTA.get_flash_hierarchy()) + self.send_fcota_hierarchy( + self.__FCOTA.get_flash_hierarchy() + ) else: self.send_fcota_ping('deletion failed!') else: - print("Unknown FCOTA command received") + print_debug(2, "Unknown FCOTA command received") elif (message_type == constants.__TYPE_PYBYTES): command = body[0] @@ -244,12 +248,15 @@ def __process_recv_message(self, message): try: pin_mode = self.__pin_modes[pin_number] except Exception as ex: + print_debug(2, '{}'.format(ex)) pin_mode = Pin.PULL_UP - self.send_pybytes_digital_value(False, pin_number, pin_mode) + self.send_pybytes_digital_value( + False, pin_number, pin_mode + ) elif (command == constants.__COMMAND_DIGITAL_WRITE): - if (not pin_number in self.__pins): + if (pin_number not in self.__pins): self.__configure_digital_pin(pin_number, Pin.OUT, None) pin = self.__pins[pin_number] pin(value) @@ -258,13 +265,13 @@ def __process_recv_message(self, message): self.send_pybytes_analog_value(False, pin_number) elif (command == constants.__COMMAND_ANALOG_WRITE): - if (not pin_number in self.__pins): + if (pin_number not in self.__pins): self.__configure_pwm_pin(pin_number) pin = self.__pins[pin_number] pin.duty_cycle(value * 100) elif (command == constants.__COMMAND_CUSTOM_METHOD): - if (pin_number == constants.__TERMINAL_PIN and self.__terminal_enabled): + if (pin_number == constants.__TERMINAL_PIN and self.__terminal_enabled): # noqa self.__terminal.message_sent_from_pybytes_start() terminal_command = body[2: len(body)] terminal_command = terminal_command.decode("utf-8") @@ -275,7 +282,7 @@ def __process_recv_message(self, message): print(repr(out)) else: print('\n') - except: + except Exception: try: exec(terminal_command) print('\n') @@ -291,13 +298,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) + method_return = self.__custom_methods[pin_number](parameters) # noqa - if (method_return is not None and len(method_return) > 0): - self.send_pybytes_custom_method_values(pin_number, method_return) + if (method_return is not None and len(method_return) > 0): # noqa + self.send_pybytes_custom_method_values( + pin_number, method_return + ) else: - print("WARNING: Trying to write to an unregistered Virtual Pin") + print("WARNING: Trying to write to an unregistered Virtual Pin") # noqa else: try: @@ -307,7 +316,11 @@ def __process_recv_message(self, message): def __configure_digital_pin(self, pin_number, pin_mode, pull_mode): # TODO: Add a check for WiPy 1.0 - self.__pins[pin_number] = Pin("P" + str(pin_number), mode = pin_mode, pull=pull_mode) + self.__pins[pin_number] = Pin( + "P" + str(pin_number), + mode=pin_mode, + pull=pull_mode + ) def __configure_analog_pin(self, pin_number): # TODO: Add a check for WiPy 1.0 @@ -332,24 +345,26 @@ def __configure_pwm_pin(self, pin_number): 22: (1, 5), 23: (1, 6)} pwm = PWM(_PWMMap[pin_number][0], frequency=5000) - self.__pins[pin_number] = pwm.channel(_PWMMap[pin_number][1], pin="P" + str(pin_number), - duty_cycle=0) + self.__pins[pin_number] = pwm.channel( + _PWMMap[pin_number][1], pin="P" + str(pin_number), + duty_cycle=0 + ) - def __send_message(self, message, topic=None): + 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 - - print_debug(2, "Sending message:[{}] with topic:[{}] and finalTopic: [{}]".format(binascii.hexlify(message), topic, finalTopic)) + 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) - elif (self.__pybytes_connection.__connection_status == constants.__CONNECTION_STATUS_CONNECTED_LORA): + 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): + 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") + print("WARNING: Message not sent, Sigfox only supports 12 Bytes messages") # noqa return self.__pybytes_connection.__sigfox_socket.send(message) @@ -360,7 +375,11 @@ def __send_message(self, message, topic=None): print(ex) def send_user_message(self, message_type, body): - self.__send_message(self.__pybytes_library.pack_user_message(message_type, body)) + self.__send_message( + self.__pybytes_library.pack_user_message( + message_type, body + ) + ) def send_ping_message(self): self.__send_message(self.__pybytes_library.pack_ping_message()) @@ -372,74 +391,117 @@ 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.') + print('WARNING! send_scan_info_message is deprecated and should be called only from Pybytes.') # noqa def send_battery_info(self): - self.__send_message(self.__pybytes_library.pack_battery_info(self.__battery_level)) + self.__send_message( + self.__pybytes_library.pack_battery_info( + self.__battery_level + ) + ) def send_ota_response(self, result): print_debug(2, 'Sending OTA result back {}'.format(result)) - self.__send_message(self.__pybytes_library.pack_ota_message(result), 'ota') + self.__send_message( + self.__pybytes_library.pack_ota_message(result), + 'ota' + ) def send_fcota_hierarchy(self, hierarchy): print_debug(2, 'Sending FCOTA hierarchy back') - self.__send_message(self.__pybytes_library.pack_fcota_hierarchy_message(hierarchy), 'fcota') + self.__send_message( + self.__pybytes_library.pack_fcota_hierarchy_message(hierarchy), + 'fcota' + ) def send_fcota_file(self, content, path, size): print_debug(2, 'Sending FCOTA file back') - self.__send_message(self.__pybytes_library.pack_fcota_file_message(content, path, size), 'fcota') + self.__send_message( + self.__pybytes_library.pack_fcota_file_message( + content, path, size + ), + 'fcota' + ) def send_fcota_ping(self, activity): print_debug(2, 'Sending FCOTA ping back: {}'.format(activity)) - self.__send_message(self.__pybytes_library.pack_fcota_ping_message(activity), 'fcota') + self.__send_message( + self.__pybytes_library.pack_fcota_ping_message(activity), + 'fcota' + ) def send_pybytes_digital_value(self, pin_number, pull_mode): - if (not pin_number in self.__pins): + if (pin_number not in self.__pins): self.__configure_digital_pin(pin_number, Pin.IN, pull_mode) pin = self.__pins[pin_number] self.send_pybytes_custom_method_values(pin_number, [pin()]) def send_pybytes_analog_value(self, pin_number): - if (not pin_number in self.__pins): + if (pin_number not in self.__pins): self.__configure_analog_pin(pin_number) pin = self.__pins[pin_number] self.send_pybytes_custom_method_values(pin_number, [pin()]) - def send_pybytes_custom_method_values(self, method_id, parameters): - print(method_id, parameters) 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) + self.__send_pybytes_message_variable( + constants.__COMMAND_CUSTOM_METHOD, method_id, values + ) elif(isinstance(parameters[0], float)): values = bytearray(struct.pack("attrs = m_malloc(cfg->attr_count * sizeof(struct lfs_attr)); // Set attribute for storing the timestamp - cfg->attrs->size = sizeof(lfs_timestamp_attribute_t); - cfg->attrs->type = LFS_ATTRIBUTE_TIMESTAMP; - cfg->attrs->buffer = m_malloc(sizeof(lfs_timestamp_attribute_t)); + cfg->attrs[0].size = sizeof(lfs_timestamp_attribute_t); + cfg->attrs[0].type = LFS_ATTRIBUTE_TIMESTAMP; + cfg->attrs[0].buffer = m_malloc(sizeof(lfs_timestamp_attribute_t)); + } void littlefs_free_up_attributes(struct lfs_file_config *cfg) { + cfg->attr_count = 0; // Currently we only have 1 attribute for timestamp m_free(cfg->attrs[0].buffer); m_free(cfg->attrs); @@ -369,31 +378,26 @@ int littlefs_update_timestamp(lfs_t* lfs, const char* file_relative_path) lfs_timestamp_attribute_t ts; ts.fdate = (WORD)(tm >> 16); ts.ftime = (WORD)tm; + // Write directly the timestamp value onto the flash return lfs_setattr(lfs, file_relative_path, LFS_ATTRIBUTE_TIMESTAMP, &ts, sizeof(lfs_timestamp_attribute_t)); } -void littlefs_update_timestamp_fp(lfs_file_t* fp) +void littlefs_update_timestamp_cfg(struct lfs_file_config *cfg) { - DWORD tm = get_fattime(); - lfs_timestamp_attribute_t ts; - ts.fdate = (WORD)(tm >> 16); - ts.ftime = (WORD)tm; - - // Until we only have 1 attribute, it is good to write the 0th element - memcpy(fp->cfg->attrs[0].buffer, &ts, sizeof(ts)); -} - -lfs_timestamp_attribute_t littlefs_get_timestamp_fp(lfs_file_t* fp) -{ - lfs_timestamp_attribute_t ts; - - // Until we only have 1 attribute, it is good to read the 0th element - memcpy(&ts, fp->cfg->attrs[0].buffer, sizeof(ts)); - return ts; + // Check is needed to prevent any accidental case when cfg->attrs[x].buffer is already freed up by someone else + if(cfg->attr_count > 0) { + DWORD tm = get_fattime(); + lfs_timestamp_attribute_t ts; + ts.fdate = (WORD)(tm >> 16); + ts.ftime = (WORD)tm; + + // Until we only have 1 attribute, it is good to write the 0th element + // This will automatically written out to the flash when close is performed + memcpy(cfg->attrs[0].buffer, &ts, sizeof(ts)); + } } - typedef struct _mp_vfs_littlefs_ilistdir_it_t { mp_obj_base_t base; mp_fun_1_t iternext; diff --git a/esp32/littlefs/vfs_littlefs.h b/esp32/littlefs/vfs_littlefs.h index 36af99883a..48a1e98ed4 100644 --- a/esp32/littlefs/vfs_littlefs.h +++ b/esp32/littlefs/vfs_littlefs.h @@ -45,7 +45,7 @@ extern int littlefs_close_common_helper(lfs_t *lfs, lfs_file_t *fp, struct lfs_f extern int littlefs_stat_common_helper(lfs_t *lfs, const char* file_path, struct lfs_info *fno, lfs_timestamp_attribute_t *ts); extern void littlefs_free_up_attributes(struct lfs_file_config *cfg); extern int littlefs_update_timestamp(lfs_t* lfs, const char* file_relative_path); -extern void littlefs_update_timestamp_fp(lfs_file_t* fp); +extern void littlefs_update_timestamp_cfg(struct lfs_file_config *cfg); extern lfs_timestamp_attribute_t littlefs_get_timestamp_fp(lfs_file_t* fp); diff --git a/esp32/littlefs/vfs_littlefs_file.c b/esp32/littlefs/vfs_littlefs_file.c index 5d0d31e415..7f59350b3c 100644 --- a/esp32/littlefs/vfs_littlefs_file.c +++ b/esp32/littlefs/vfs_littlefs_file.c @@ -91,7 +91,7 @@ STATIC mp_uint_t file_obj_ioctl(mp_obj_t o_in, mp_uint_t request, uintptr_t arg, xSemaphoreTake(self->littlefs->mutex, portMAX_DELAY); int res = lfs_file_sync(&self->littlefs->lfs, &self->fp); - xSemaphoreGive(self->littlefs->mutex);; + xSemaphoreGive(self->littlefs->mutex); if (res < 0) { *errcode = littleFsErrorToErrno(res); @@ -108,6 +108,9 @@ STATIC mp_uint_t file_obj_ioctl(mp_obj_t o_in, mp_uint_t request, uintptr_t arg, *errcode = littleFsErrorToErrno(res); return MP_STREAM_ERROR; } + // Free up the object so GC does not need to do that + m_del_obj(pyb_file_obj_t, self); + return 0; } else { *errcode = MP_EINVAL; @@ -170,7 +173,6 @@ STATIC mp_obj_t file_open(fs_user_mount_t *vfs, const mp_obj_type_t *type, mp_ar m_free((void*)fname); if (res < LFS_ERR_OK) { - littlefs_free_up_attributes(&o->cfg); m_del_obj(pyb_file_obj_t, o); mp_raise_OSError(littleFsErrorToErrno(res)); } diff --git a/esp32/lte/lteppp.c b/esp32/lte/lteppp.c index 2e0523d941..71f400094c 100644 --- a/esp32/lte/lteppp.c +++ b/esp32/lte/lteppp.c @@ -85,7 +85,7 @@ static ip_addr_t ltepp_dns_info[2]={0}; DECLARE PRIVATE FUNCTIONS ******************************************************************************/ static void TASK_LTE (void *pvParameters); -static bool lteppp_send_at_cmd_exp(const char *cmd, uint32_t timeout, const char *expected_rsp, void* data_rem); +static bool lteppp_send_at_cmd_exp(const char *cmd, uint32_t timeout, const char *expected_rsp, void* data_rem, size_t len); static bool lteppp_send_at_cmd(const char *cmd, uint32_t timeout); static bool lteppp_check_sim_present(void); static void lteppp_status_cb (ppp_pcb *pcb, int err_code, void *ctx); @@ -225,12 +225,6 @@ void lteppp_send_at_command (lte_task_cmd_data_t *cmd, lte_task_rsp_data_t *rsp) xQueueReceive(xRxQueue, rsp, (TickType_t)portMAX_DELAY); } -void lteppp_send_at_command_delay (lte_task_cmd_data_t *cmd, lte_task_rsp_data_t *rsp, TickType_t delay) { - xQueueSend(xCmdQueue, (void *)cmd, (TickType_t)portMAX_DELAY); - vTaskDelay(delay); - xQueueReceive(xRxQueue, rsp, (TickType_t)portMAX_DELAY); -} - bool lteppp_wait_at_rsp (const char *expected_rsp, uint32_t timeout, bool from_mp, void* data_rem) { uint32_t rx_len = 0; @@ -514,7 +508,7 @@ static void TASK_LTE (void *pvParameters) { } xSemaphoreGive(xLTESem); if (xQueueReceive(xCmdQueue, lteppp_trx_buffer, 0)) { - lteppp_send_at_cmd_exp(lte_task_cmd->data, lte_task_cmd->timeout, NULL, &(lte_task_rsp->data_remaining)); + lteppp_send_at_cmd_exp(lte_task_cmd->data, lte_task_cmd->timeout, NULL, &(lte_task_rsp->data_remaining), lte_task_cmd->dataLen); xQueueSend(xRxQueue, (void *)lte_task_rsp, (TickType_t)portMAX_DELAY); } else { lte_state_t state = lteppp_get_state(); @@ -555,7 +549,7 @@ static void TASK_LTE (void *pvParameters) { } -static bool lteppp_send_at_cmd_exp (const char *cmd, uint32_t timeout, const char *expected_rsp, void* data_rem) { +static bool lteppp_send_at_cmd_exp (const char *cmd, uint32_t timeout, const char *expected_rsp, void* data_rem, size_t len) { if(strstr(cmd, "Pycom_Dummy") != NULL) { @@ -577,7 +571,7 @@ static bool lteppp_send_at_cmd_exp (const char *cmd, uint32_t timeout, const cha } else { - uint32_t cmd_len = strlen(cmd); + size_t cmd_len = len; // char tmp_buf[128]; #ifdef LTE_DEBUG_BUFF if (lteppp_log.ptr < (LTE_LOG_BUFF_SIZE - strlen("[CMD]:") - cmd_len + 1)) @@ -611,7 +605,7 @@ static bool lteppp_send_at_cmd_exp (const char *cmd, uint32_t timeout, const cha } static bool lteppp_send_at_cmd(const char *cmd, uint32_t timeout) { - return lteppp_send_at_cmd_exp (cmd, timeout, LTE_OK_RSP, NULL); + return lteppp_send_at_cmd_exp (cmd, timeout, LTE_OK_RSP, NULL, strlen(cmd) ); } static bool lteppp_check_sim_present(void) { diff --git a/esp32/lte/lteppp.h b/esp32/lte/lteppp.h index 9977aa9765..1679da9b22 100644 --- a/esp32/lte/lteppp.h +++ b/esp32/lte/lteppp.h @@ -72,6 +72,7 @@ typedef struct { typedef struct { uint32_t timeout; char data[LTE_AT_CMD_SIZE_MAX - 4]; + size_t dataLen; } lte_task_cmd_data_t; #pragma pack(1) typedef struct { @@ -111,8 +112,6 @@ extern void lteppp_deinit (void); extern void lteppp_send_at_command (lte_task_cmd_data_t *cmd, lte_task_rsp_data_t *rsp); -extern void lteppp_send_at_command_delay (lte_task_cmd_data_t *cmd, lte_task_rsp_data_t *rsp, TickType_t delay); - extern bool lteppp_wait_at_rsp (const char *expected_rsp, uint32_t timeout, bool from_mp, void* data_rem); lte_modem_conn_state_t lteppp_modem_state(void); diff --git a/esp32/mods/machuart.c b/esp32/mods/machuart.c index 9ad7517528..1618bb8aa5 100644 --- a/esp32/mods/machuart.c +++ b/esp32/mods/machuart.c @@ -379,7 +379,7 @@ STATIC mp_obj_t mach_uart_init_helper(mach_uart_obj_t *self, const mp_arg_val_t int rx_buffer_size = args[6].u_int; // Check is needed because return value of uart_driver_install is not checked if(!(rx_buffer_size > UART_FIFO_LEN)) { - nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_ValueError, "invalid RX buffer size, minimum is 128")); + nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_ValueError, "invalid RX buffer size, should be > 128 bytes")); } if (self->config.baud_rate > 0) { diff --git a/esp32/mods/modlora.c b/esp32/mods/modlora.c index ca018e1d5f..d99a67fe49 100644 --- a/esp32/mods/modlora.c +++ b/esp32/mods/modlora.c @@ -770,6 +770,21 @@ static void OnTxNextActReqTimerEvent(void) { } } +static void MlmeIndication( MlmeIndication_t *mlmeIndication ) +{ + switch( mlmeIndication->MlmeIndication ) + { + case MLME_SCHEDULE_UPLINK: + {// The MAC signals that we shall provide an uplink as soon as possible + printf("Trying to send uplink\n"); + OnTxNextActReqTimerEvent( ); + break; + } + default: + break; + } +} + static void TASK_LoRa (void *pvParameters) { MibRequestConfirm_t mibReq; MlmeReq_t mlmeReq; @@ -796,6 +811,7 @@ static void TASK_LoRa (void *pvParameters) { LoRaMacPrimitives.MacMcpsConfirm = McpsConfirm; LoRaMacPrimitives.MacMcpsIndication = McpsIndication; LoRaMacPrimitives.MacMlmeConfirm = MlmeConfirm; + LoRaMacPrimitives.MacMlmeIndication = MlmeIndication; LoRaMacCallbacks.GetBatteryLevel = BoardGetBatteryLevel; LoRaMacInitialization(&LoRaMacPrimitives, &LoRaMacCallbacks, task_cmd_data.info.init.region); diff --git a/esp32/mods/modlte.c b/esp32/mods/modlte.c index f171b8afb6..b434201912 100644 --- a/esp32/mods/modlte.c +++ b/esp32/mods/modlte.c @@ -116,7 +116,7 @@ extern TaskHandle_t xLTETaskHndl; /****************************************************************************** DECLARE PRIVATE FUNCTIONS ******************************************************************************/ -static bool lte_push_at_command_ext (char *cmd_str, uint32_t timeout, const char *expected_rsp); +static bool lte_push_at_command_ext (char *cmd_str, uint32_t timeout, const char *expected_rsp, size_t len); static bool lte_push_at_command (char *cmd_str, uint32_t timeout); static void lte_pause_ppp(void); static bool lte_check_attached(bool legacy); @@ -146,25 +146,12 @@ void modlte_start_modem(void) // DEFINE STATIC FUNCTIONS //***************************************************************************** -static bool lte_push_at_command_ext(char *cmd_str, uint32_t timeout, const char *expected_rsp) { - lte_task_cmd_data_t cmd = { .timeout = timeout }; - memcpy(cmd.data, cmd_str, strlen(cmd_str)); +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); lteppp_send_at_command(&cmd, &modlte_rsp); - if (strstr(modlte_rsp.data, expected_rsp) != NULL) { - //printf("[OK] %s\n", modlte_rsp.data); - return true; - } - //printf("[FAIL] %s\n", modlte_rsp.data); - return false; -} - -static bool lte_push_at_command_delay_ext (char *cmd_str, uint32_t timeout, const char *expected_rsp, TickType_t delay) { - lte_task_cmd_data_t cmd = { .timeout = timeout }; - memcpy(cmd.data, cmd_str, strlen(cmd_str)); - //printf("[CMD] %s\n", cmd_str); - lteppp_send_at_command_delay(&cmd, &modlte_rsp, delay); - if (strstr(modlte_rsp.data, expected_rsp) != NULL) { + if ((expected_rsp == NULL) || (strstr(modlte_rsp.data, expected_rsp) != NULL)) { //printf("[OK] %s\n", modlte_rsp.data); return true; } @@ -173,11 +160,7 @@ static bool lte_push_at_command_delay_ext (char *cmd_str, uint32_t timeout, cons } static bool lte_push_at_command (char *cmd_str, uint32_t timeout) { - return lte_push_at_command_ext(cmd_str, timeout, LTE_OK_RSP); -} - -static bool lte_push_at_command_delay (char *cmd_str, uint32_t timeout, TickType_t delay) { - return lte_push_at_command_delay_ext(cmd_str, timeout, LTE_OK_RSP, delay); + return lte_push_at_command_ext(cmd_str, timeout, LTE_OK_RSP, strlen(cmd_str)); } static void lte_pause_ppp(void) { @@ -969,14 +952,14 @@ STATIC mp_obj_t lte_connect(mp_uint_t n_args, const mp_obj_t *pos_args, mp_map_t } if (lteppp_get_state() == E_LTE_ATTACHED || (args[1].u_bool && lteppp_get_state() == E_LTE_SUSPENDED)) { - if (args[1].u_bool || !lte_push_at_command_ext("ATO", LTE_RX_TIMEOUT_MAX_MS, LTE_CONNECT_RSP)) { + if (args[1].u_bool || !lte_push_at_command_ext("ATO", LTE_RX_TIMEOUT_MAX_MS, LTE_CONNECT_RSP, strlen("ATO") )) { char at_cmd[LTE_AT_CMD_SIZE_MAX - 4]; if (args[0].u_obj != mp_const_none) { lte_obj.cid = args[0].u_int; } sprintf(at_cmd, "AT+CGDATA=\"PPP\",%d", lte_obj.cid); // set the PPP state in advance, to avoid CEREG? to be sent right after PPP is entered - if (!lte_push_at_command_ext(at_cmd, LTE_RX_TIMEOUT_MAX_MS, LTE_CONNECT_RSP)) { + if (!lte_push_at_command_ext(at_cmd, LTE_RX_TIMEOUT_MAX_MS, LTE_CONNECT_RSP, strlen(at_cmd) )) { nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, mpexception_os_operation_failed)); } } @@ -1019,7 +1002,7 @@ STATIC mp_obj_t lte_resume(mp_uint_t n_args, const mp_obj_t *pos_args, mp_map_t lte_obj.cid = args[0].u_int; } - if (lte_push_at_command_ext("ATO", LTE_RX_TIMEOUT_MAX_MS, LTE_CONNECT_RSP)) { + if (lte_push_at_command_ext("ATO", LTE_RX_TIMEOUT_MAX_MS, LTE_CONNECT_RSP, strlen("ATO") )) { lteppp_connect(); lteppp_resume(); lteppp_set_state(E_LTE_PPP); @@ -1090,7 +1073,6 @@ STATIC mp_obj_t lte_send_at_cmd(mp_uint_t n_args, const mp_obj_t *pos_args, mp_m lte_check_inppp(); STATIC const mp_arg_t allowed_args[] = { { MP_QSTR_cmd, MP_ARG_OBJ, {.u_obj = mp_const_none} }, - { MP_QSTR_delay, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, }; // parse args mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; @@ -1098,15 +1080,23 @@ STATIC mp_obj_t lte_send_at_cmd(mp_uint_t n_args, const mp_obj_t *pos_args, mp_m if (args[0].u_obj == mp_const_none) { nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_ValueError, "the command must be specified!")); } - const char *cmd = mp_obj_str_get_str(args[0].u_obj); - lte_push_at_command_delay((char *)cmd, LTE_RX_TIMEOUT_MAX_MS, args[1].u_int); + if (MP_OBJ_IS_STR_OR_BYTES(args[0].u_obj)) + { + size_t len; + lte_push_at_command_ext((char *)(mp_obj_str_get_data(args[0].u_obj, &len)), LTE_RX_TIMEOUT_MAX_MS, NULL, len); + } + else + { + nlr_raise(mp_obj_new_exception_msg(&mp_type_TypeError, mpexception_num_type_invalid_arguments)); + } + vstr_t vstr; vstr_init(&vstr, 0); vstr_add_str(&vstr, modlte_rsp.data); MP_THREAD_GIL_EXIT(); while(modlte_rsp.data_remaining) { - lte_push_at_command_delay("Pycom_Dummy", LTE_RX_TIMEOUT_MAX_MS, args[1].u_int); + lte_push_at_command_ext("Pycom_Dummy", LTE_RX_TIMEOUT_MAX_MS, NULL, strlen("Pycom_Dummy") ); vstr_add_str(&vstr, modlte_rsp.data); } MP_THREAD_GIL_ENTER(); diff --git a/esp32/mods/modpycom.c b/esp32/mods/modpycom.c index 5160d35aa5..94012da8a9 100644 --- a/esp32/mods/modpycom.c +++ b/esp32/mods/modpycom.c @@ -478,6 +478,30 @@ STATIC mp_obj_t mod_pycom_bootmgr (size_t n_args, const mp_obj_t *pos_args, mp_m STATIC MP_DEFINE_CONST_FUN_OBJ_KW(mod_pycom_bootmgr_obj, 0, mod_pycom_bootmgr); +STATIC mp_obj_t mod_pycom_get_free_heap (void) { + + size_t heap_psram_free = 0; + mp_obj_t items[2]; + esp_chip_info_t chip_info; + + esp_chip_info(&chip_info); + if (chip_info.revision > 0) { + heap_psram_free = heap_caps_get_free_size(MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); + } + items[0] = mp_obj_new_int(heap_caps_get_free_size(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)); + if(heap_psram_free) + { + items[1] = mp_obj_new_int(heap_psram_free); + } + else + { + items[1] = mp_obj_new_str("NO_EXT_RAM", strlen("NO_EXT_RAM")); + } + + return mp_obj_new_tuple(2, items); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_pycom_get_free_heap_obj, mod_pycom_get_free_heap); + #if (VARIANT == PYBYTES) STATIC mp_obj_t mod_pycom_pybytes_device_token (void) { uint8_t pybytes_device_token[39]; @@ -546,6 +570,7 @@ STATIC const mp_map_elem_t pycom_module_globals_table[] = { { MP_OBJ_NEW_QSTR(MP_QSTR_wifi_pwd), (mp_obj_t)&mod_pycom_wifi_pwd_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 }, #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 }, diff --git a/esp32/pycom_version.h b/esp32/pycom_version.h index c13efbe5fa..7444eef19d 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.0.rc12.1" +#define SW_VERSION_NUMBER "1.20.0.rc13" #define LORAWAN_VERSION_NUMBER "1.0.2" #define SIGFOX_VERSION_NUMBER "1.0.1" #if (VARIANT == PYBYTES) -#define PYBYTES_VERSION_NUMBER "0.9.12" +#define PYBYTES_VERSION_NUMBER "1.0.0" #endif #endif /* VERSION_H_ */ diff --git a/esp32/tools/idfVerCheck.sh b/esp32/tools/idfVerCheck.sh new file mode 100644 index 0000000000..1ac3e3231b --- /dev/null +++ b/esp32/tools/idfVerCheck.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# +# Copyright (c) 2019, 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 +# + +IDF_VER="idf_v"$2 +CURR_VER="$(git --git-dir=$1/.git branch | grep \* | cut -d ' ' -f2)" + +if [ "${CURR_VER}" = "${IDF_VER}" ]; then + echo "IDF Version OK!" + 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 diff --git a/lib/lora/mac/LoRaMac.c b/lib/lora/mac/LoRaMac.c index 64f8bba189..aaf8027127 100644 --- a/lib/lora/mac/LoRaMac.c +++ b/lib/lora/mac/LoRaMac.c @@ -257,7 +257,7 @@ static uint8_t MaxDCycle = 0; static uint16_t AggregatedDCycle; static TimerTime_t AggregatedLastTxDoneTime; static TimerTime_t AggregatedTimeOff; -static TimerEvent_t RadioTxDoneClassCTimer; +//static TimerEvent_t RadioTxDoneClassCTimer; /*! * Enables/Disables duty cycle management (Test only) @@ -396,6 +396,11 @@ static McpsIndication_t McpsIndication; */ static McpsConfirm_t McpsConfirm; +/*! + * Structure to hold MLME indication data. + */ +static MlmeIndication_t MlmeIndication; + /*! * Structure to hold MLME confirm data. */ @@ -462,6 +467,19 @@ static void OnRxWindow1TimerEvent( void ); */ static void OnRxWindow2TimerEvent( void ); +/*! + * \brief Check if the OnAckTimeoutTimer has do be disabled. If so, the + * function disables it. + * + * \param [IN] nodeAckRequested Set to true, if the node has requested an ACK + * \param [IN] class The device class + * \param [IN] ackReceived Set to true, if the node has received an ACK + * \param [IN] ackTimeoutRetriesCounter Retries counter for confirmed uplinks + * \param [IN] ackTimeoutRetries Maximum retries for confirmed uplinks + */ +static void CheckToDisableAckTimeout( bool nodeAckRequested, DeviceClass_t devClass, bool ackReceived, + uint8_t ackTimeoutRetriesCounter, uint8_t ackTimeoutRetries ); + /*! * \brief Function executed on AckTimeout timer event */ @@ -475,6 +493,19 @@ static void OnAckTimeoutTimerEvent( void ); */ static void RxWindowSetup( bool rxContinuous, uint32_t maxRxWindow ); +/*! + * \brief Verifies if sticky MAC commands are pending. + * + * \retval [true: sticky MAC commands pending, false: No MAC commands pending] + */ +static bool IsStickyMacCommandPending( void ); + +/*! + * \brief Configures the events to trigger an MLME-Indication with + * a MLME type of MLME_SCHEDULE_UPLINK. + */ +static void SetMlmeScheduleUplinkIndication( void ); + /*! * \brief Adds a new MAC command to be sent. * @@ -589,6 +620,21 @@ LoRaMacStatus_t SetTxContinuousWave1( uint16_t timeout, uint32_t frequency, uint */ static void ResetMacParameters( void ); +/*! + * \brief Resets MAC specific parameters to default + * + * \param [IN] fPort The fPort + * + * \retval [false: fPort not allowed, true: fPort allowed] + */ +static bool IsFPortAllowed( uint8_t fPort ); + +/*! + * \brief Opens up a continuous RX 2 window. This is used for + * class c devices. + */ +static void OpenContinuousRx2Window( void ); + static IRAM_ATTR void OnRadioTxDone( void ) { GetPhyParams_t getPhy; @@ -602,7 +648,7 @@ static IRAM_ATTR void OnRadioTxDone( void ) } else { - TimerStart(&RadioTxDoneClassCTimer); + OpenContinuousRx2Window( ); } // Setup timers @@ -686,7 +732,6 @@ static void OnRadioRxDone( uint8_t *payload, uint32_t timestamp, uint16_t size, ApplyCFListParams_t applyCFList; GetPhyParams_t getPhy; PhyParam_t phyParam; - bool skipIndication = false; uint8_t pktHeaderLen = 0; uint32_t address = 0; @@ -937,8 +982,8 @@ static void OnRadioRxDone( uint8_t *payload, uint32_t timestamp, uint16_t size, // In this case, the MAC layer shall accept the MAC commands // which are included in the downlink retransmission. // It should not provide the same frame to the application - // layer again. - skipIndication = true; + // layer again. The MAC layer accepts the acknowledgement. + LoRaMacFlags.Bits.McpsIndSkip = 1; } } else @@ -967,6 +1012,9 @@ static void OnRadioRxDone( uint8_t *payload, uint32_t timestamp, uint16_t size, if( fCtrl.Bits.Ack == 1 ) {// Reset MacCommandsBufferIndex when we have received an ACK. MacCommandsBufferIndex = 0; + // Update acknowledgement information + McpsConfirm.AckReceived = fCtrl.Bits.Ack; + McpsIndication.AckReceived = fCtrl.Bits.Ack; } } else @@ -1000,7 +1048,10 @@ static void OnRadioRxDone( uint8_t *payload, uint32_t timestamp, uint16_t size, } else { - skipIndication = true; + LoRaMacFlags.Bits.McpsIndSkip = 1; + // This is not a valid frame. Drop it and reset the ACK bits + McpsConfirm.AckReceived = false; + McpsIndication.AckReceived = false; } } else @@ -1019,12 +1070,9 @@ static void OnRadioRxDone( uint8_t *payload, uint32_t timestamp, uint16_t size, downLinkCounter, LoRaMacRxPayload ); - if( skipIndication == false ) - { - McpsIndication.Buffer = LoRaMacRxPayload; - McpsIndication.BufferSize = frameLen; - McpsIndication.RxData = true; - } + McpsIndication.Buffer = LoRaMacRxPayload; + McpsIndication.BufferSize = frameLen; + McpsIndication.RxData = true; } } else @@ -1036,34 +1084,9 @@ static void OnRadioRxDone( uint8_t *payload, uint32_t timestamp, uint16_t size, } } - if( skipIndication == false ) - { - // Check if the frame is an acknowledgement - if( fCtrl.Bits.Ack == 1 ) - { - McpsConfirm.AckReceived = true; - McpsIndication.AckReceived = true; - - // Stop the AckTimeout timer as no more retransmissions - // are needed. - TimerStop( &AckTimeoutTimer ); - } - else - { - McpsConfirm.AckReceived = false; - - if( AckTimeoutRetriesCounter > AckTimeoutRetries ) - { - // Stop the AckTimeout timer as no more retransmissions - // are needed. - TimerStop( &AckTimeoutTimer ); - } - } - } // Provide always an indication, skip the callback to the user application, // in case of a confirmed downlink retransmission. LoRaMacFlags.Bits.McpsInd = 1; - LoRaMacFlags.Bits.McpsIndSkip = skipIndication; } else { @@ -1091,11 +1114,19 @@ static void OnRadioRxDone( uint8_t *payload, uint32_t timestamp, uint16_t size, PrepareRxDoneAbort( ); break; } - LoRaMacFlags.Bits.MacDone = 1; - // Trig OnMacCheckTimerEvent call as soon as possible - TimerSetValue( &MacStateCheckTimer, 1 ); - TimerStart( &MacStateCheckTimer ); + // Verify if we need to disable the AckTimeoutTimer + CheckToDisableAckTimeout( NodeAckRequested, LoRaMacDeviceClass, McpsConfirm.AckReceived, + AckTimeoutRetriesCounter, AckTimeoutRetries ); + + if( AckTimeoutTimer.IsRunning == false ) + {// Procedure is completed when the AckTimeoutTimer is not running anymore + LoRaMacFlags.Bits.MacDone = 1; + + // Trig OnMacCheckTimerEvent call as soon as possible + TimerSetValue( &MacStateCheckTimer, 1 ); + TimerStart( &MacStateCheckTimer ); + } } static void OnRadioTxTimeout( void ) @@ -1106,7 +1137,7 @@ static void OnRadioTxTimeout( void ) } else { - OnRxWindow2TimerEvent( ); + OpenContinuousRx2Window( ); } McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_TX_TIMEOUT; @@ -1120,10 +1151,6 @@ static void OnRadioRxError( void ) { Radio.Sleep( ); } - else - { - OnRxWindow2TimerEvent( ); - } if( RxSlot == 0 ) { @@ -1147,6 +1174,11 @@ static void OnRadioRxError( void ) MlmeConfirm.Status = LORAMAC_EVENT_INFO_STATUS_RX2_ERROR; LoRaMacFlags.Bits.MacDone = 1; } + + if( LoRaMacDeviceClass == CLASS_C ) + { + OpenContinuousRx2Window( ); + } } static void OnRadioRxTimeout( void ) @@ -1186,6 +1218,11 @@ static void OnRadioRxTimeout( void ) LoRaMacFlags.Bits.MacDone = 1; } } + + if( LoRaMacDeviceClass == CLASS_C ) + { + OpenContinuousRx2Window( ); + } } static void OnMacStateCheckTimerEvent( void ) @@ -1383,6 +1420,12 @@ static void OnMacStateCheckTimerEvent( void ) LoRaMacFlags.Bits.MlmeReq = 0; } + // Verify if sticky MAC commands are pending or not + if( IsStickyMacCommandPending( ) == true ) + {// Setup MLME indication + SetMlmeScheduleUplinkIndication( ); + } + // Procedure done. Reset variables. LoRaMacFlags.Bits.MacDone = 0; } @@ -1392,6 +1435,28 @@ static void OnMacStateCheckTimerEvent( void ) TimerSetValue( &MacStateCheckTimer, MAC_STATE_CHECK_TIMEOUT ); TimerStart( &MacStateCheckTimer ); } + + // Handle MCPS indication + if( LoRaMacFlags.Bits.McpsInd == 1 ) + { + LoRaMacFlags.Bits.McpsInd = 0; + if( LoRaMacDeviceClass == CLASS_C ) + {// Activate RX2 window for Class C + OpenContinuousRx2Window( ); + } + if( LoRaMacFlags.Bits.McpsIndSkip == 0 ) + { + LoRaMacPrimitives->MacMcpsIndication( &McpsIndication ); + } + LoRaMacFlags.Bits.McpsIndSkip = 0; + } + + // Handle MLME indication + if( LoRaMacFlags.Bits.MlmeInd == 1 ) + { + LoRaMacFlags.Bits.MlmeInd = 0; + LoRaMacPrimitives->MacMlmeIndication( &MlmeIndication ); + } } static void OnTxDelayedTimerEvent( void ) @@ -1475,6 +1540,41 @@ static void OnRxWindow2TimerEvent( void ) } } +static void CheckToDisableAckTimeout( bool nodeAckRequested, DeviceClass_t devClass, bool ackReceived, + uint8_t ackTimeoutRetriesCounter, uint8_t ackTimeoutRetries ) +{ + // There are three cases where we need to stop the AckTimeoutTimer: + if( nodeAckRequested == false ) + { + if( devClass == CLASS_C ) + {// FIRST CASE + // We have performed an unconfirmed uplink in class c mode + // and have received a downlink in RX1 or RX2. + TimerStop( &AckTimeoutTimer ); + } + } + else + { + if( ackReceived == 1 ) + {// SECOND CASE + // We have performed a confirmed uplink and have received a + // downlink with a valid ACK. + TimerStop( &AckTimeoutTimer ); + } + else + {// THIRD CASE + if( ackTimeoutRetriesCounter > ackTimeoutRetries ) + { + // We have performed a confirmed uplink and have not + // received a downlink with a valid ACK. In this case + // we need to verify if the maximum retries have been + // elapsed. If so, stop the timer. + TimerStop( &AckTimeoutTimer ); + } + } + } +} + static void OnAckTimeoutTimerEvent( void ) { TimerStop( &AckTimeoutTimer ); @@ -1533,6 +1633,22 @@ bool ValidatePayloadLength( uint8_t lenN, int8_t datarate, uint8_t fOptsLen ) return false; } +static bool IsStickyMacCommandPending( void ) +{ + if( MacCommandsBufferToRepeatIndex > 0 ) + { + // Sticky MAC commands pending + return true; + } + return false; +} + +static void SetMlmeScheduleUplinkIndication( void ) +{ + MlmeIndication.MlmeIndication = MLME_SCHEDULE_UPLINK; + LoRaMacFlags.Bits.MlmeInd = 1; +} + static LoRaMacStatus_t AddMacCommand( uint8_t cmd, uint8_t p1, uint8_t p2 ) { LoRaMacStatus_t status = LORAMAC_STATUS_BUSY; @@ -1572,6 +1688,8 @@ static LoRaMacStatus_t AddMacCommand( uint8_t cmd, uint8_t p1, uint8_t p2 ) MacCommandsBuffer[MacCommandsBufferIndex++] = cmd; // Status: Datarate ACK, Channel ACK MacCommandsBuffer[MacCommandsBufferIndex++] = p1; + // This is a sticky MAC command answer. Setup indication + SetMlmeScheduleUplinkIndication( ); status = LORAMAC_STATUS_OK; } break; @@ -1600,6 +1718,8 @@ static LoRaMacStatus_t AddMacCommand( uint8_t cmd, uint8_t p1, uint8_t p2 ) { MacCommandsBuffer[MacCommandsBufferIndex++] = cmd; // No payload for this answer + // This is a sticky MAC command answer. Setup indication + SetMlmeScheduleUplinkIndication( ); status = LORAMAC_STATUS_OK; } break; @@ -1617,6 +1737,10 @@ static LoRaMacStatus_t AddMacCommand( uint8_t cmd, uint8_t p1, uint8_t p2 ) MacCommandsBuffer[MacCommandsBufferIndex++] = cmd; // Status: Uplink frequency exists, Channel frequency OK MacCommandsBuffer[MacCommandsBufferIndex++] = p1; + + // This is a sticky MAC command answer. Setup indication + SetMlmeScheduleUplinkIndication( ); + status = LORAMAC_STATUS_OK; } break; @@ -2038,6 +2162,21 @@ static void ResetMacParameters( void ) LastTxChannel = Channel; } +static bool IsFPortAllowed( uint8_t fPort ) +{ + if( fPort > 224 ) + { + return false; + } + return true; +} + +static void OpenContinuousRx2Window( void ) +{ + OnRxWindow2TimerEvent( ); + RxSlot = 2; +} + LoRaMacStatus_t PrepareFrame( LoRaMacHeader_t *macHdr, LoRaMacFrameCtrl_t *fCtrl, uint8_t fPort, void *fBuffer, uint16_t fBufferSize ) { AdrNextParams_t adrNext; @@ -2292,7 +2431,8 @@ LoRaMacStatus_t LoRaMacInitialization( LoRaMacPrimitives_t *primitives, LoRaMacC if( ( primitives->MacMcpsConfirm == NULL ) || ( primitives->MacMcpsIndication == NULL ) || - ( primitives->MacMlmeConfirm == NULL ) ) + ( primitives->MacMlmeConfirm == NULL ) || + ( primitives->MacMlmeIndication == NULL ) ) { return LORAMAC_STATUS_PARAMETER_INVALID; } @@ -2407,8 +2547,8 @@ LoRaMacStatus_t LoRaMacInitialization( LoRaMacPrimitives_t *primitives, LoRaMacC TimerInit( &RxWindowTimer1, OnRxWindow1TimerEvent ); TimerInit( &RxWindowTimer2, OnRxWindow2TimerEvent ); TimerInit( &AckTimeoutTimer, OnAckTimeoutTimerEvent ); - TimerInit( &RadioTxDoneClassCTimer, OnRxWindow2TimerEvent ); - TimerSetValue( &RadioTxDoneClassCTimer, 1 ); + //TimerInit( &RadioTxDoneClassCTimer, OnRxWindow2TimerEvent ); + //TimerSetValue( &RadioTxDoneClassCTimer, 1 ); // Store the current initialization time LoRaMacInitializationTime = TimerGetCurrentTime( ); @@ -3348,6 +3488,12 @@ LoRaMacStatus_t LoRaMacMcpsRequest( McpsReq_t *mcpsRequest ) break; } + // Filter fPorts + if( IsFPortAllowed( fPort ) == false ) + { + return LORAMAC_STATUS_PARAMETER_INVALID; + } + // Get the minimum possible datarate getPhy.Attribute = PHY_MIN_TX_DR; getPhy.UplinkDwellTime = LoRaMacParams.UplinkDwellTime; diff --git a/lib/lora/mac/LoRaMac.h b/lib/lora/mac/LoRaMac.h index 8eb05415fa..b53b5b41d1 100644 --- a/lib/lora/mac/LoRaMac.h +++ b/lib/lora/mac/LoRaMac.h @@ -680,6 +680,10 @@ typedef union eLoRaMacFlags_t * MLME-Req pending */ uint8_t MlmeReq : 1; + /*! + * MLME-Ind pending + */ + uint8_t MlmeInd : 1; /*! * MAC cycle done */ @@ -1010,6 +1014,11 @@ typedef enum eMlme * LoRaWAN end-device certification */ MLME_TXCW_1, + /*! + * Indicates that the application shall perform an uplink as + * soon as possible. + */ + MLME_SCHEDULE_UPLINK }Mlme_t; /*! @@ -1122,6 +1131,17 @@ typedef struct sMlmeConfirm uint8_t NbRetries; }MlmeConfirm_t; +/*! + * LoRaMAC MLME-Indication primitive + */ +typedef struct sMlmeIndication +{ + /*! + * MLME-Indication type + */ + Mlme_t MlmeIndication; +}MlmeIndication_t; + /*! * LoRa Mac Information Base (MIB) * @@ -1721,6 +1741,12 @@ typedef struct sLoRaMacPrimitives * \param [OUT] MLME-Confirm parameters */ void ( *MacMlmeConfirm )( MlmeConfirm_t *MlmeConfirm ); + /*! + * \brief MLME-Indication primitive + * + * \param [OUT] MLME-Indication parameters + */ + void ( *MacMlmeIndication )( MlmeIndication_t *MlmeIndication ); }LoRaMacPrimitives_t; /*!