Skip to content

Commit 156f059

Browse files
Enhanced firmware OTA update (#98)
* Added firmware update exposed to user * Added firmware update error attribute * Removed random dummy failed on update checksum verification * Updated firmware update example
1 parent 88fbb1c commit 156f059

File tree

3 files changed

+70
-32
lines changed

3 files changed

+70
-32
lines changed

examples/device/firmware_update.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,26 @@
1414

1515
import time
1616
import logging
17-
from tb_device_mqtt import TBDeviceMqttClient, FW_STATE_ATTR
17+
from random import randint
18+
from tb_device_mqtt import TBDeviceMqttClient, TBFirmwareState, FW_STATE_ATTR, FW_TITLE_ATTR
1819

1920
logging.basicConfig(level=logging.INFO)
2021

22+
def on_firmware_received_example(client, firmware_data, version_to):
23+
client.update_firmware_info(state = TBFirmwareState.UPDATING)
24+
time.sleep(1)
25+
26+
with open(client.firmware_info.get(FW_TITLE_ATTR), "wb") as firmware_file:
27+
firmware_file.write(firmware_data)
28+
29+
random_value = randint(0, 5)
30+
if random_value > 3:
31+
logging.error('Dummy fail! Do not panic, just restart and try again the chance of this fail is ~20%')
32+
client.update_firmware_info(state = TBFirmwareState.FAILED, error = "Dummy fail! Do not panic, just restart and try again the chance of this fail is ~20%")
33+
else:
34+
logging.info("Successfully updated!")
35+
client.update_firmware_info(version = version_to, state = TBFirmwareState.UPDATED)
36+
2137

2238
def main():
2339
client = TBDeviceMqttClient("127.0.0.1", username="A2_TEST_TOKEN")
@@ -26,7 +42,7 @@ def main():
2642
client.get_firmware_update()
2743

2844
# Waiting for firmware to be delivered
29-
while not client.current_firmware_info[FW_STATE_ATTR] == 'UPDATED':
45+
while not client.current_firmware_info[FW_STATE_ATTR] == TBFirmwareState.UPDATED.value:
3046
time.sleep(1)
3147

3248
client.disconnect()

sdk_utils.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
from random import randint
1615
from zlib import crc32
1716
from hashlib import sha256, sha384, sha512, md5
1817
import logging
@@ -78,8 +77,4 @@ def verify_checksum(firmware_data, checksum_alg, checksum):
7877
else:
7978
log.error('Client error. Unsupported checksum algorithm.')
8079
log.debug(checksum_of_received_firmware)
81-
random_value = randint(0, 5)
82-
if random_value > 3:
83-
log.debug('Dummy fail! Do not panic, just restart and try again the chance of this fail is ~20%')
84-
return False
8580
return checksum_of_received_firmware == checksum

tb_device_mqtt.py

Lines changed: 52 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ def check_tb_paho_mqtt_installed():
6767
FW_CHECKSUM_ALG_ATTR = "fw_checksum_algorithm"
6868
FW_SIZE_ATTR = "fw_size"
6969
FW_STATE_ATTR = "fw_state"
70+
FW_ERROR_ATTR = "fw_error"
7071

7172
REQUIRED_SHARED_KEYS = f"{FW_CHECKSUM_ATTR},{FW_CHECKSUM_ALG_ATTR},{FW_SIZE_ATTR},{FW_TITLE_ATTR},{FW_VERSION_ATTR}"
7273

@@ -106,6 +107,14 @@ class TBSendMethod(Enum):
106107
PUBLISH = 1
107108
UNSUBSCRIBE = 2
108109

110+
class TBFirmwareState(Enum):
111+
IDLE = "IDLE"
112+
DOWNLOADING = "DOWNLOADING"
113+
DOWNLOADED = "DOWNLOADED"
114+
VERIFIED = "VERIFIED"
115+
UPDATING = "UPDATING"
116+
UPDATED = "UPDATED"
117+
FAILED = "FAILED"
109118

110119
class TBPublishInfo:
111120
TB_ERR_AGAIN = -1
@@ -474,7 +483,7 @@ class TBDeviceMqttClient:
474483
def __init__(self, host, port=1883, username=None, password=None, quality_of_service=None, client_id="",
475484
chunk_size=0, messages_rate_limit="DEFAULT_MESSAGES_RATE_LIMIT",
476485
telemetry_rate_limit="DEFAULT_TELEMETRY_RATE_LIMIT",
477-
telemetry_dp_rate_limit="DEFAULT_TELEMETRY_DP_RATE_LIMIT", max_payload_size=8196, **kwargs):
486+
telemetry_dp_rate_limit="DEFAULT_TELEMETRY_DP_RATE_LIMIT", max_payload_size=8196, on_firmware_received=None, **kwargs):
478487
# Added for compatibility with old versions
479488
if kwargs.get('rate_limit') is not None or kwargs.get('dp_rate_limit') is not None:
480489
messages_rate_limit = messages_rate_limit if kwargs.get('rate_limit') == "DEFAULT_RATE_LIMIT" else kwargs.get('rate_limit', messages_rate_limit) # noqa
@@ -523,37 +532,50 @@ def __init__(self, host, port=1883, username=None, password=None, quality_of_ser
523532
self.current_firmware_info = {
524533
"current_" + FW_TITLE_ATTR: "Initial",
525534
"current_" + FW_VERSION_ATTR: "v0",
526-
FW_STATE_ATTR: "IDLE"
535+
FW_STATE_ATTR: TBFirmwareState.IDLE.value,
536+
FW_ERROR_ATTR: ""
527537
}
528538
self.__request_id = 0
529539
self.__firmware_request_id = 0
530540
self.__chunk_size = chunk_size
531541
self.firmware_received = False
542+
self.set_on_firmware_received_function(on_firmware_received)
532543
self.rate_limits_received = False
533544
self.__request_service_configuration_required = False
534545
self.__service_loop = Thread(target=self.__service_loop, name="Service loop", daemon=True)
535546
self.__service_loop.start()
536547
self.__messages_limit_reached_set_time = (0,0)
537548
self.__datapoints_limit_reached_set_time = (0,0)
538549

550+
def update_firmware_info(self, title = None, version = None, state: TBFirmwareState = None, error = None):
551+
if title is not None:
552+
self.current_firmware_info["current_" + FW_TITLE_ATTR] = title
553+
554+
if version is not None:
555+
self.current_firmware_info["current_" + FW_VERSION_ATTR] = version
556+
557+
if state is not None:
558+
self.current_firmware_info[FW_STATE_ATTR] = state.value
559+
if state is TBFirmwareState.FAILED and error is not None:
560+
self.current_firmware_info[FW_ERROR_ATTR] = error
561+
else:
562+
self.current_firmware_info[FW_ERROR_ATTR] = ""
563+
564+
self.send_telemetry(self.current_firmware_info)
565+
566+
def set_on_firmware_received_function(self, on_firmware_received):
567+
if on_firmware_received is not None:
568+
self.__on_firmware_received = on_firmware_received
569+
else:
570+
self.__on_firmware_received = self.__on_firmware_received_default
571+
539572
def __service_loop(self):
540573
while not self.stopped:
541574
if self.__request_service_configuration_required:
542575
self.request_service_configuration(self.service_configuration_callback)
543576
self.__request_service_configuration_required = False
544577
elif self.firmware_received:
545-
self.current_firmware_info[FW_STATE_ATTR] = "UPDATING"
546-
self.send_telemetry(self.current_firmware_info)
547-
sleep(1)
548-
549-
self.__on_firmware_received(self.firmware_info.get(FW_VERSION_ATTR))
550-
551-
self.current_firmware_info = {
552-
"current_" + FW_TITLE_ATTR: self.firmware_info.get(FW_TITLE_ATTR),
553-
"current_" + FW_VERSION_ATTR: self.firmware_info.get(FW_VERSION_ATTR),
554-
FW_STATE_ATTR: "UPDATED"
555-
}
556-
self.send_telemetry(self.current_firmware_info)
578+
self.__on_firmware_received(self, self.firmware_data, self.firmware_info.get(FW_VERSION_ATTR))
557579
self.firmware_received = False
558580
sleep(0.05)
559581

@@ -758,8 +780,7 @@ def _on_decoded_message(self, content, message):
758780
self.firmware_data = b''
759781
self.__current_chunk = 0
760782

761-
self.current_firmware_info[FW_STATE_ATTR] = "DOWNLOADING"
762-
self.send_telemetry(self.current_firmware_info)
783+
self.update_firmware_info(state = TBFirmwareState.DOWNLOADING)
763784
sleep(1)
764785

765786
self.__firmware_request_id = self.__firmware_request_id + 1
@@ -769,22 +790,19 @@ def _on_decoded_message(self, content, message):
769790
self.__get_firmware()
770791

771792
def __process_firmware(self):
772-
self.current_firmware_info[FW_STATE_ATTR] = "DOWNLOADED"
773-
self.send_telemetry(self.current_firmware_info)
793+
self.update_firmware_info(state = TBFirmwareState.DOWNLOADED)
774794
sleep(1)
775795

776796
verification_result = verify_checksum(self.firmware_data, self.firmware_info.get(FW_CHECKSUM_ALG_ATTR),
777797
self.firmware_info.get(FW_CHECKSUM_ATTR))
778798

779799
if verification_result:
780800
log.debug('Checksum verified!')
781-
self.current_firmware_info[FW_STATE_ATTR] = "VERIFIED"
782-
self.send_telemetry(self.current_firmware_info)
801+
self.update_firmware_info(state = TBFirmwareState.VERIFIED)
783802
sleep(1)
784803
else:
785804
log.debug('Checksum verification failed!')
786-
self.current_firmware_info[FW_STATE_ATTR] = "FAILED"
787-
self.send_telemetry(self.current_firmware_info)
805+
self.update_firmware_info(state = TBFirmwareState.FAILED, error = "Checksum verification failed!")
788806
self.__request_firmware_info()
789807
return
790808
self.firmware_received = True
@@ -796,10 +814,19 @@ def __get_firmware(self):
796814
f"v2/fw/request/{self.__firmware_request_id}/chunk/{self.__current_chunk}",
797815
payload=payload, qos=1)
798816

799-
def __on_firmware_received(self, version_to):
817+
def __on_firmware_received_default(self, client, firmware_data, version_to):
818+
self.update_firmware_info(state = TBFirmwareState.UPDATING)
819+
sleep(1)
820+
800821
with open(self.firmware_info.get(FW_TITLE_ATTR), "wb") as firmware_file:
801-
firmware_file.write(self.firmware_data)
802-
log.info('Firmware is updated!\n Current firmware version is: %s' % version_to)
822+
firmware_file.write(firmware_data)
823+
log.warning(f"Firmware was received and stored under {self.firmware_info.get(FW_TITLE_ATTR)},"
824+
"but 'on_firmware_received' callback is not defined. No OTA update applied."
825+
"Call 'set_on_firmware_received_function' to handle properly firmware update.")
826+
827+
self.update_firmware_info(title = self.firmware_info.get(FW_TITLE_ATTR),
828+
version = self.firmware_info.get(FW_VERSION_ATTR),
829+
state = TBFirmwareState.UPDATED)
803830

804831
@staticmethod
805832
def _decode(message):

0 commit comments

Comments
 (0)