#!/usr/bin/env python # -*- coding: utf-8 -*- """ download-settings.py - Upload Tasmota settings file Copyright (C) 2021 Theo Arends This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Requirements: - Python 3.x and Pip: sudo apt-get install python3 python3-pip pip3 install paho-mqtt json Instructions: Edit file and change parameters in User Configuration Section Then execute command download-settings.py """ import paho.mqtt.client as mqtt import time import base64 import hashlib import json # **** Start of User Configuration Section broker = "domus1" # MQTT broker ip address or name broker_port = 1883 # MQTT broker port mypassword = "" # Tasmota MQTT password mytopic = "demo" # Tasmota MQTT topic myfiletype = 2 # Tasmota Settings file type # **** End of User Configuration Section use_base64 = True # Derive fulltopic from broker LWT message mypublish = "cmnd/"+mytopic+"/filedownload" mysubscribe = "stat/"+mytopic+"/FILEDOWNLOAD" # Case sensitive Ack_flag = False Err_flag = False file_name = "" file_id = 0 file_type = 0 file_size = 0 file_md5 = "" # The callback for when mysubscribe message is received def on_message(client, userdata, msg): global Ack_flag global Err_flag global file_name global file_id global file_type global file_size global file_md5 global in_hash_md5 base64_data = "" rcv_id = 0 # try: # print("Received message =",str(msg.payload.decode("utf-8"))) # except: # print("Received message = binary data") try: root = json.loads(msg.payload.decode("utf-8")) if root: if "FileDownload" in root: rcv_code = root["FileDownload"] if "Aborted" in rcv_code: print("Error: Aborted") Err_flag = True return if "Started" in rcv_code: return if "Error" in rcv_code: if "1" in rcv_code: print("Error: Wrong password") else: if "2" in rcv_code: print("Error: Bad chunk size") else: if "3" in rcv_code: print("Error: Invalid file type") else: print("Error: "+rcv_code) Err_flag = True return if "Command" in root: rcv_code = root["Command"] if rcv_code == "Error": print("Error: Command error") Err_flag = True return if "File" in root: file_name = root["File"] if "Id" in root: rcv_id = root["Id"] if "Type" in root: file_type = root["Type"] if "Size" in root: file_size = root["Size"] if "Data" in root: base64_data = root["Data"] if "Md5" in root: file_md5 = root["Md5"] except: pass if file_id == 0 and rcv_id > 0 and file_size > 0 and file_type > 0 and file_name: file_id = rcv_id fi = open(file_name,"wb") fi.close() else: if use_base64 and file_id > 0 and file_id != rcv_id: Err_flag = True return if file_md5 == "" and file_name: if use_base64 and base64_data: base64_decoded_data = base64_data.encode('utf-8') chunk = base64.decodebytes(base64_decoded_data) in_hash_md5.update(chunk) # Update hash fi = open(file_name,"ab") fi.write(chunk) fi.close() if use_base64 == False and 0 == rcv_id: chunk = msg.payload in_hash_md5.update(chunk) # Update hash fi = open(file_name,"ab") fi.write(chunk) fi.close() if file_md5 != "": md5_hash = in_hash_md5.hexdigest() if md5_hash != file_md5: print("Error: MD5 mismatch") Err_flag = True Ack_flag = False def wait_for_ack(): global Err_flag timeout = 500 while Ack_flag and Err_flag == False and timeout > 0: time.sleep(0.01) timeout = timeout -1 if 0 == timeout: print("Error: Timeout") Err_flag = True return Ack_flag client = mqtt.Client() client.on_message = on_message client.connect(broker, broker_port) client.loop_start() # Start loop to process received messages client.subscribe(mysubscribe) time_start = time.time() print("Downloading file from "+mytopic+" ...") in_hash_md5 = hashlib.md5() if use_base64: client.publish(mypublish, "{\"Password\":\""+mypassword+"\",\"Type\":"+str(myfiletype)+"}") else: client.publish(mypublish, "{\"Password\":\""+mypassword+"\",\"Type\":"+str(myfiletype)+",\"Binary\":1}") Ack_flag = True Run_flag = True while Run_flag: if wait_for_ack(): # We use Ack here client.publish(mypublish, "0") # Abort any failed download Run_flag = False else: if file_md5 == "": # Request chunk client.publish(mypublish, "?") Ack_flag = True else: Run_flag = False if Err_flag == False: file_type_name = "Data" if file_type == 2: file_type_name = "Settings" print("Downloaded "+file_type_name+" saved as "+file_name) time_taken = time.time() - time_start print("Done in "+str("%.2f"%time_taken)+" seconds") client.disconnect() # Disconnect client.loop_stop() # Stop loop