From 817c7ce0a1be39fb6ad561c9693c7460d860c850 Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Wed, 12 May 2021 17:10:50 +0200 Subject: [PATCH] Prep for MQTT binary file transfer --- tasmota/support_command.ino | 31 +- tasmota/xdrv_02_mqtt_1_file.ino | 290 ++++++++++++++++++ ...rv_02_mqtt.ino => xdrv_02_mqtt_9_impl.ino} | 277 +---------------- 3 files changed, 325 insertions(+), 273 deletions(-) create mode 100644 tasmota/xdrv_02_mqtt_1_file.ino rename tasmota/{xdrv_02_mqtt.ino => xdrv_02_mqtt_9_impl.ino} (87%) diff --git a/tasmota/support_command.ino b/tasmota/support_command.ino index bce9243d6..3e26a98d3 100644 --- a/tasmota/support_command.ino +++ b/tasmota/support_command.ino @@ -192,11 +192,6 @@ void CommandHandler(char* topicBuf, char* dataBuf, uint32_t data_len) ShowFreeMem(PSTR("CommandHandler")); #endif - while (*dataBuf && isspace(*dataBuf)) { - dataBuf++; // Skip leading spaces in data - data_len--; - } - bool grpflg = false; uint32_t real_index = SET_MQTT_GRP_TOPIC; for (uint32_t i = 0; i < MAX_GROUP_TOPICS; i++) { @@ -237,20 +232,32 @@ void CommandHandler(char* topicBuf, char* dataBuf, uint32_t data_len) type[i] = '\0'; } - AddLog_P(LOG_LEVEL_DEBUG, PSTR("CMD: " D_GROUP " %d, " D_INDEX " %d, " D_COMMAND " \"%s\", " D_DATA " \"%s\""), grpflg, index, type, dataBuf); + bool binary_data = (index > 199); // Suppose binary data on topic index > 199 + if (!binary_data) { + while (*dataBuf && isspace(*dataBuf)) { + dataBuf++; // Skip leading spaces in data + data_len--; + } + } + + AddLog_P(LOG_LEVEL_DEBUG, PSTR("CMD: " D_GROUP " %d, " D_INDEX " %d, " D_COMMAND " \"%s\", " D_DATA " \"%s\""), + grpflg, index, type, (binary_data) ? PSTR("Binary") : dataBuf); if (type != nullptr) { Response_P(PSTR("{\"" D_JSON_COMMAND "\":\"" D_JSON_ERROR "\"}")); if (Settings.ledstate &0x02) { TasmotaGlobal.blinks++; } - if (!strcmp(dataBuf,"?")) { data_len = 0; } + int32_t payload = -99; + if (!binary_data) { + if (!strcmp(dataBuf,"?")) { data_len = 0; } - char *p; - int32_t payload = strtol(dataBuf, &p, 0); // decimal, octal (0) or hex (0x) - if (p == dataBuf) { payload = -99; } - int temp_payload = GetStateNumber(dataBuf); - if (temp_payload > -1) { payload = temp_payload; } + char *p; + payload = strtol(dataBuf, &p, 0); // decimal, octal (0) or hex (0x) + if (p == dataBuf) { payload = -99; } + int temp_payload = GetStateNumber(dataBuf); + if (temp_payload > -1) { payload = temp_payload; } + } DEBUG_CORE_LOG(PSTR("CMD: Payload %d"), payload); diff --git a/tasmota/xdrv_02_mqtt_1_file.ino b/tasmota/xdrv_02_mqtt_1_file.ino new file mode 100644 index 000000000..36f0591a6 --- /dev/null +++ b/tasmota/xdrv_02_mqtt_1_file.ino @@ -0,0 +1,290 @@ +/* + xdrv_02_mqtt_1_file.ino - mqtt file support for Tasmota + + 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 . +*/ + +#define USE_MQTT_FILE + +#ifdef USE_MQTT_FILE +/*********************************************************************************************\ + * MQTT file transfer + * + * Supports base64 encoded binary data transfer +\*********************************************************************************************/ + +#include + +struct FMQTT { + uint32_t file_pos = 0; // MQTT file position during upload/download + uint32_t file_size = 0; // MQTT total file size + uint32_t file_type = 0; // MQTT File type (See UploadTypes) + uint8_t* file_buffer = nullptr; // MQTT file buffer + MD5Builder md5; // MQTT md5 + String file_md5; // MQTT received file md5 (32 chars) + uint16_t topic_size; // MQTT topic length with terminating + uint8_t file_id = 0; // MQTT unique file id during upload/download +} FMqtt; + +void MqttTopicSize(uint32_t topic_size) { + FMqtt.topic_size = topic_size +1; +} + +/* + The download chunk size is the data size before it is encoded to base64. + It is smaller than the upload chunksize as it is bound by MESSZ + The download buffer with length MESSZ (1042) contains + - Payload ({"Id":117,"Data":""}) +*/ +const uint32_t FileTransferHeaderSize = 21; // {"Id":116,"Data":""} +const uint32_t mqtt_file_chuck_size = (((MESSZ - FileTransferHeaderSize) / 4) * 3) -2; + +uint32_t FileUploadChunckSize(void) { +/* + The upload chunk size is the data size before it is encoded to base64. + It can be larger than the download chunksize which is bound by MESSZ + The PubSubClient upload buffer with length MQTT_MAX_PACKET_SIZE (1200) contains + - Header of 5 bytes (MQTT_MAX_HEADER_SIZE) + - Topic string terminated with a zero (stat/demo/FILEUPLOAD) + - Payload ({"Id":116,"Data":""}) +*/ + const uint32_t PubSubClientHeaderSize = 5; // MQTT_MAX_HEADER_SIZE + + return (((MQTT_MAX_PACKET_SIZE - PubSubClientHeaderSize - FMqtt.topic_size - FileTransferHeaderSize) / 4) * 3) -2; +} + +void CmndFileUpload(void) { +/* + Upload (binary) max 700 bytes chunks of data base64 encoded with MD5 hash over base64 decoded data + FileUpload 0 - Abort current upload + FileUpload {"File":"Config_wemos10_9.4.0.3.dmp","Id":116,"Type":2,"Size":4096} + FileUpload {"Id":116,"Data":"CRJcTQ9fYGF ... OT1BRUlNUVVZXWFk="} + FileUpload {"Id":116,"Data":" ... "} + FileUpload {"Id":116,"Md5":"496fcbb433bbca89833063174d2c5747"} +*/ + const char* base64_data = nullptr; + uint32_t rcv_id = 0; + + char* dataBuf = (char*)XdrvMailbox.data; + if (strlen(dataBuf) > 8) { // Workaround exception if empty JSON like {} - Needs checks + JsonParser parser((char*) dataBuf); + JsonParserObject root = parser.getRootObject(); + if (root) { + JsonParserToken val = root[PSTR("ID")]; + if (val) { rcv_id = val.getUInt(); } + val = root[PSTR("TYPE")]; + if (val) { FMqtt.file_type = val.getUInt(); } + val = root[PSTR("SIZE")]; + if (val) { FMqtt.file_size = val.getUInt(); } + val = root[PSTR("MD5")]; + if (val) { FMqtt.file_md5 = val.getStr(); } + val = root[PSTR("DATA")]; + if (val) { base64_data = val.getStr(); } + } + } + + if ((0 == FMqtt.file_id) && (rcv_id > 0) && (FMqtt.file_size > 0) && (FMqtt.file_type > 0)) { + // Init upload buffer + FMqtt.file_buffer = nullptr; + + if (UPL_TASMOTA == FMqtt.file_type) { + if (Update.begin(FMqtt.file_size)) { + FMqtt.file_buffer = &FMqtt.file_id; // Dummy buffer + } + } + else if (UPL_SETTINGS == FMqtt.file_type) { + if (SettingsConfigBackup()) { + FMqtt.file_buffer = settings_buffer; + } + } + + if (!FMqtt.file_buffer) { + ResponseCmndChar(PSTR(D_JSON_INVALID_FILE_TYPE)); + } else { + FMqtt.file_id = rcv_id; + FMqtt.file_pos = 0; + + FMqtt.md5 = MD5Builder(); + FMqtt.md5.begin(); + + ResponseCmndChar(PSTR(D_JSON_STARTED)); + MqttPublishPrefixTopic_P(STAT, XdrvMailbox.command); // Enforce stat/wemos10/FILEUPLOAD + } + } + else if ((FMqtt.file_id > 0) && (FMqtt.file_id != rcv_id)) { + // Error receiving data + + if (UPL_TASMOTA == FMqtt.file_type) { + Update.end(true); + } + else if (UPL_SETTINGS == FMqtt.file_type) { + SettingsBufferFree(); + } + + FMqtt.file_buffer = nullptr; + ResponseCmndChar(PSTR(D_JSON_ABORTED)); + } + + if (FMqtt.file_buffer) { + if ((FMqtt.file_pos < FMqtt.file_size) && base64_data) { + // Save upload into buffer - Handle possible buffer overflows + uint32_t rcvd_bytes = decode_base64_length((unsigned char*)base64_data); + unsigned char decode_output[rcvd_bytes]; + decode_base64((unsigned char*)base64_data, (unsigned char*)decode_output); + + uint32_t bytes_left = FMqtt.file_size - FMqtt.file_pos; + uint32_t read_bytes = (bytes_left < rcvd_bytes) ? bytes_left : rcvd_bytes; + FMqtt.md5.add(decode_output, read_bytes); + + if (UPL_TASMOTA == FMqtt.file_type) { + Update.write(decode_output, read_bytes); + } else { + uint8_t* buffer = FMqtt.file_buffer + FMqtt.file_pos; + memcpy(buffer, decode_output, read_bytes); + } + + FMqtt.file_pos += read_bytes; + + if ((FMqtt.file_pos > rcvd_bytes) && ((FMqtt.file_pos % 102400) <= rcvd_bytes)) { + TasmotaGlobal.masterlog_level = LOG_LEVEL_NONE; // Enable logging + AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_UPLOAD "Progress %d kB"), FMqtt.file_pos / 1024); + TasmotaGlobal.masterlog_level = LOG_LEVEL_DEBUG_MORE; // Hide upload data logging + } + } + + if ((FMqtt.file_pos < FMqtt.file_size) || (FMqtt.file_md5.length() != 32)) { + TasmotaGlobal.masterlog_level = LOG_LEVEL_DEBUG_MORE; // Hide upload data logging + + // {"Id":116,"MaxSize":"765"} + Response_P(PSTR("{\"Id\":%d,\"MaxSize\":%d}"), FMqtt.file_id, FileUploadChunckSize()); + } else { + FMqtt.md5.calculate(); + if (strcasecmp(FMqtt.file_md5.c_str(), FMqtt.md5.toString().c_str())) { + ResponseCmndChar(PSTR(D_JSON_MD5_MISMATCH)); + } else { + // Process upload data en free buffer + ResponseCmndDone(); + + if (UPL_TASMOTA == FMqtt.file_type) { + if (!Update.end(true)) { + ResponseCmndFailed(); + } else { + TasmotaGlobal.restart_flag = 2; // Always restart to re-enable disabled features during update + } + } + else if (UPL_SETTINGS == FMqtt.file_type) { + if (!SettingsConfigRestore()) { + ResponseCmndFailed(); + } else { + TasmotaGlobal.restart_flag = 2; // Always restart to re-enable disabled features during update + } + } + + } + FMqtt.file_buffer = nullptr; + } + } + + if (!FMqtt.file_buffer) { + TasmotaGlobal.masterlog_level = LOG_LEVEL_NONE; // Enable logging + FMqtt.file_id = 0; + FMqtt.file_size = 0; + FMqtt.file_type = 0; + FMqtt.file_md5 = (const char*) nullptr; // Force deallocation of the String internal memory + } + MqttPublishPrefixTopic_P(STAT, XdrvMailbox.command); // Enforce stat/wemos10/FILEUPLOAD + ResponseClear(); +} + +void CmndFileDownload(void) { +/* + Download (binary) max 700 bytes chunks of data base64 encoded with MD5 hash over base64 decoded data + Currently supports Settings (file type 2) + Filedownload 0 - Abort current download + FileDownload 2 - Start download of settings file + FileDownload - Continue downloading data until reception of MD5 hash +*/ + + if (FMqtt.file_id && FMqtt.file_buffer) { + bool finished = false; + + if (0 == XdrvMailbox.payload) { // Abort file download + ResponseCmndChar(PSTR(D_JSON_ABORTED)); + finished = true; + } + else if (FMqtt.file_pos < FMqtt.file_size) { + uint32_t bytes_left = FMqtt.file_size - FMqtt.file_pos; + uint32_t write_bytes = (bytes_left < mqtt_file_chuck_size) ? bytes_left : mqtt_file_chuck_size; + + uint8_t* buffer = FMqtt.file_buffer + FMqtt.file_pos; + FMqtt.md5.add(buffer, write_bytes); + + // {"Id":117,"Data":"CRJcTQ9fYGF ... OT1BRUlNUVVZXWFk="} + Response_P(PSTR("{\"Id\":%d,\"Data\":\""), FMqtt.file_id); // FileTransferHeaderSize + char base64_data[encode_base64_length(write_bytes)]; + encode_base64((unsigned char*)buffer, write_bytes, (unsigned char*)base64_data); + ResponseAppend_P(base64_data); + ResponseAppend_P("\"}"); + + FMqtt.file_pos += write_bytes; + } else { + FMqtt.md5.calculate(); + + // {"Id":117,"Md5":"496fcbb433bbca89833063174d2c5747"} + Response_P(PSTR("{\"Id\":%d,\"Md5\":\"%s\"}"), FMqtt.file_id, FMqtt.md5.toString().c_str()); + finished = true; + } + + if (finished) { + if (UPL_SETTINGS == FMqtt.file_type) { + SettingsBufferFree(); + } + + FMqtt.file_id = 0; + } + } + else if (XdrvMailbox.data_len) { + FMqtt.file_buffer = nullptr; + FMqtt.file_id = (UtcTime() & 0xFE) +1; // Odd id between 1 and 255 + + if (UPL_SETTINGS == XdrvMailbox.payload) { + uint32_t len = SettingsConfigBackup(); + if (len) { + FMqtt.file_type = UPL_SETTINGS; + FMqtt.file_buffer = settings_buffer; + FMqtt.file_size = len; + + // {"File":"Config_wemos10_9.4.0.3.dmp","Id":117,"Type":2,"Size":4096} + Response_P(PSTR("{\"File\":\"%s\",\"Id\":%d,\"Type\":%d,\"Size\":%d}"), + SettingsConfigFilename().c_str(), FMqtt.file_id, FMqtt.file_type, len); + } + } + + if (FMqtt.file_buffer) { + FMqtt.file_pos = 0; + + FMqtt.md5 = MD5Builder(); + FMqtt.md5.begin(); + } else { + FMqtt.file_id = 0; + ResponseCmndFailed(); + } + } + MqttPublishPrefixTopic_P(STAT, XdrvMailbox.command); + ResponseClear(); +} + +#endif // USE_MQTT_FILE diff --git a/tasmota/xdrv_02_mqtt.ino b/tasmota/xdrv_02_mqtt_9_impl.ino similarity index 87% rename from tasmota/xdrv_02_mqtt.ino rename to tasmota/xdrv_02_mqtt_9_impl.ino index 1adbfb674..6e55b927c 100644 --- a/tasmota/xdrv_02_mqtt.ino +++ b/tasmota/xdrv_02_mqtt_9_impl.ino @@ -23,8 +23,6 @@ #define MQTT_WIFI_CLIENT_TIMEOUT 200 // Wifi TCP connection timeout (default is 5000 mSec) #endif -#include - #define USE_MQTT_NEW_PUBSUBCLIENT // #define DEBUG_DUMP_TLS // allow dumping of TLS Flash keys @@ -45,6 +43,7 @@ WiFiClient EspClient; // Wifi Client - non-TLS HTTPClient httpsClient; int httpsClientReturn; #endif // USE_MQTT_AZURE_DPS_SCOPEID + #include #include #include #endif // USE_MQTT_AZURE_IOT @@ -64,10 +63,13 @@ const char kMqttCommands[] PROGMEM = "|" // No prefix #if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT) D_CMND_TLSKEY "|" #endif +#ifdef USE_MQTT_FILE + D_CMND_FILEUPLOAD "|" D_CMND_FILEDOWNLOAD "|" +#endif // USE_MQTT_FILE D_CMND_MQTTHOST "|" D_CMND_MQTTPORT "|" D_CMND_MQTTRETRY "|" D_CMND_STATETEXT "|" D_CMND_MQTTCLIENT "|" D_CMND_FULLTOPIC "|" D_CMND_PREFIX "|" D_CMND_GROUPTOPIC "|" D_CMND_TOPIC "|" D_CMND_PUBLISH "|" D_CMND_MQTTLOG "|" D_CMND_BUTTONTOPIC "|" D_CMND_SWITCHTOPIC "|" D_CMND_BUTTONRETAIN "|" D_CMND_SWITCHRETAIN "|" D_CMND_POWERRETAIN "|" - D_CMND_SENSORRETAIN "|" D_CMND_INFORETAIN "|" D_CMND_STATERETAIN "|" D_CMND_FILEUPLOAD "|" D_CMND_FILEDOWNLOAD ; + D_CMND_SENSORRETAIN "|" D_CMND_INFORETAIN "|" D_CMND_STATERETAIN ; SO_SYNONYMS(kMqttSynonyms, 90, @@ -90,24 +92,19 @@ void (* const MqttCommand[])(void) PROGMEM = { #if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT) &CmndTlsKey, #endif +#ifdef USE_MQTT_FILE + &CmndFileUpload, &CmndFileDownload, +#endif // USE_MQTT_FILE &CmndMqttHost, &CmndMqttPort, &CmndMqttRetry, &CmndStateText, &CmndMqttClient, &CmndFullTopic, &CmndPrefix, &CmndGroupTopic, &CmndTopic, &CmndPublish, &CmndMqttlog, &CmndButtonTopic, &CmndSwitchTopic, &CmndButtonRetain, &CmndSwitchRetain, &CmndPowerRetain, &CmndSensorRetain, - &CmndInfoRetain, &CmndStateRetain, &CmndFileUpload, &CmndFileDownload }; + &CmndInfoRetain, &CmndStateRetain }; struct MQTT { - uint32_t file_pos = 0; // MQTT file position during upload/download - uint32_t file_size = 0; // MQTT total file size - uint32_t file_type = 0; // MQTT File type (See UploadTypes) - uint8_t* file_buffer = nullptr; // MQTT file buffer - MD5Builder md5; // MQTT md5 - String file_md5; // MQTT received file md5 (32 chars) uint16_t connect_count = 0; // MQTT re-connect count uint16_t retry_counter = 1; // MQTT connection retry counter uint16_t retry_counter_delay = 1; // MQTT retry counter multiplier - uint16_t topic_size; // MQTT topic length with terminating uint8_t initial_connection_state = 2; // MQTT connection messages state - uint8_t file_id = 0; // MQTT unique file id during upload/download bool connected = false; // MQTT virtual connection status bool allowed = false; // MQTT enabled and parameters valid bool mqtt_tls = false; // MQTT TLS is enabled @@ -117,7 +114,7 @@ struct MQTT { // This part of code is necessary to store Private Key and Cert in Flash #ifdef USE_MQTT_AWS_IOT -//#include +#include const br_ec_private_key *AWS_IoT_Private_Key = nullptr; const br_x509_certificate *AWS_IoT_Client_Certificate = nullptr; @@ -547,7 +544,9 @@ void MqttDataHandler(char* mqtt_topic, uint8_t* mqtt_data, unsigned int data_len #else strlcpy(topic, mqtt_topic, sizeof(topic)); #endif // USE_MQTT_AZURE_IOT - Mqtt.topic_size = strlen(topic) + 1; +#ifdef USE_MQTT_FILE + MqttTopicSize(strlen(topic)); +#endif // USE_MQTT_FILE mqtt_data[data_len] = 0; char data[data_len +1]; memcpy(data, mqtt_data, sizeof(data)); @@ -1416,250 +1415,6 @@ void CmndStateRetain(void) { ResponseCmndStateText(Settings.flag5.mqtt_state_retain); // CMND_STATERETAIN } -/* - The download chunk size is the data size before it is encoded to base64. - It is smaller than the upload chunksize as it is bound by MESSZ - The download buffer with length MESSZ (1042) contains - - Payload ({"Id":117,"Data":""}) -*/ -const uint32_t FileTransferHeaderSize = 21; // {"Id":116,"Data":""} -const uint32_t mqtt_file_chuck_size = (((MESSZ - FileTransferHeaderSize) / 4) * 3) -2; - -uint32_t FileUploadChunckSize(void) { -/* - The upload chunk size is the data size before it is encoded to base64. - It can be larger than the download chunksize which is bound by MESSZ - The PubSubClient upload buffer with length MQTT_MAX_PACKET_SIZE (1200) contains - - Header of 5 bytes (MQTT_MAX_HEADER_SIZE) - - Topic string terminated with a zero (stat/demo/FILEUPLOAD) - - Payload ({"Id":116,"Data":""}) -*/ - const uint32_t PubSubClientHeaderSize = 5; // MQTT_MAX_HEADER_SIZE - - return (((MQTT_MAX_PACKET_SIZE - PubSubClientHeaderSize - Mqtt.topic_size - FileTransferHeaderSize) / 4) * 3) -2; -} - -void CmndFileUpload(void) { -/* - Upload (binary) max 700 bytes chunks of data base64 encoded with MD5 hash over base64 decoded data - FileUpload 0 - Abort current upload - FileUpload {"File":"Config_wemos10_9.4.0.3.dmp","Id":116,"Type":2,"Size":4096} - FileUpload {"Id":116,"Data":"CRJcTQ9fYGF ... OT1BRUlNUVVZXWFk="} - FileUpload {"Id":116,"Data":" ... "} - FileUpload {"Id":116,"Md5":"496fcbb433bbca89833063174d2c5747"} -*/ - const char* base64_data = nullptr; - uint32_t rcv_id = 0; - - char* dataBuf = (char*)XdrvMailbox.data; - if (strlen(dataBuf) > 8) { // Workaround exception if empty JSON like {} - Needs checks - JsonParser parser((char*) dataBuf); - JsonParserObject root = parser.getRootObject(); - if (root) { - JsonParserToken val = root[PSTR("ID")]; - if (val) { rcv_id = val.getUInt(); } - val = root[PSTR("TYPE")]; - if (val) { Mqtt.file_type = val.getUInt(); } - val = root[PSTR("SIZE")]; - if (val) { Mqtt.file_size = val.getUInt(); } - val = root[PSTR("MD5")]; - if (val) { Mqtt.file_md5 = val.getStr(); } - val = root[PSTR("DATA")]; - if (val) { base64_data = val.getStr(); } - } - } - - if ((0 == Mqtt.file_id) && (rcv_id > 0) && (Mqtt.file_size > 0) && (Mqtt.file_type > 0)) { - // Init upload buffer - Mqtt.file_buffer = nullptr; - - if (UPL_TASMOTA == Mqtt.file_type) { - if (Update.begin(Mqtt.file_size)) { - Mqtt.file_buffer = &Mqtt.file_id; // Dummy buffer - } - } - else if (UPL_SETTINGS == Mqtt.file_type) { - if (SettingsConfigBackup()) { - Mqtt.file_buffer = settings_buffer; - } - } - - if (!Mqtt.file_buffer) { - ResponseCmndChar(PSTR(D_JSON_INVALID_FILE_TYPE)); - } else { - Mqtt.file_id = rcv_id; - Mqtt.file_pos = 0; - - Mqtt.md5 = MD5Builder(); - Mqtt.md5.begin(); - - ResponseCmndChar(PSTR(D_JSON_STARTED)); - MqttPublishPrefixTopic_P(STAT, XdrvMailbox.command); // Enforce stat/wemos10/FILEUPLOAD - } - } - else if ((Mqtt.file_id > 0) && (Mqtt.file_id != rcv_id)) { - // Error receiving data - - if (UPL_TASMOTA == Mqtt.file_type) { - Update.end(true); - } - else if (UPL_SETTINGS == Mqtt.file_type) { - SettingsBufferFree(); - } - - Mqtt.file_buffer = nullptr; - ResponseCmndChar(PSTR(D_JSON_ABORTED)); - } - - if (Mqtt.file_buffer) { - if ((Mqtt.file_pos < Mqtt.file_size) && base64_data) { - // Save upload into buffer - Handle possible buffer overflows - uint32_t rcvd_bytes = decode_base64_length((unsigned char*)base64_data); - unsigned char decode_output[rcvd_bytes]; - decode_base64((unsigned char*)base64_data, (unsigned char*)decode_output); - - uint32_t bytes_left = Mqtt.file_size - Mqtt.file_pos; - uint32_t read_bytes = (bytes_left < rcvd_bytes) ? bytes_left : rcvd_bytes; - Mqtt.md5.add(decode_output, read_bytes); - - if (UPL_TASMOTA == Mqtt.file_type) { - Update.write(decode_output, read_bytes); - } else { - uint8_t* buffer = Mqtt.file_buffer + Mqtt.file_pos; - memcpy(buffer, decode_output, read_bytes); - } - - Mqtt.file_pos += read_bytes; - - if ((Mqtt.file_pos > rcvd_bytes) && ((Mqtt.file_pos % 102400) <= rcvd_bytes)) { - TasmotaGlobal.masterlog_level = LOG_LEVEL_NONE; // Enable logging - AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_UPLOAD "Progress %d kB"), Mqtt.file_pos / 1024); - TasmotaGlobal.masterlog_level = LOG_LEVEL_DEBUG_MORE; // Hide upload data logging - } - } - - if ((Mqtt.file_pos < Mqtt.file_size) || (Mqtt.file_md5.length() != 32)) { - TasmotaGlobal.masterlog_level = LOG_LEVEL_DEBUG_MORE; // Hide upload data logging - - // {"Id":116,"MaxSize":"765"} - Response_P(PSTR("{\"Id\":%d,\"MaxSize\":%d}"), Mqtt.file_id, FileUploadChunckSize()); - } else { - Mqtt.md5.calculate(); - if (strcasecmp(Mqtt.file_md5.c_str(), Mqtt.md5.toString().c_str())) { - ResponseCmndChar(PSTR(D_JSON_MD5_MISMATCH)); - } else { - // Process upload data en free buffer - ResponseCmndDone(); - - if (UPL_TASMOTA == Mqtt.file_type) { - if (!Update.end(true)) { - ResponseCmndFailed(); - } else { - TasmotaGlobal.restart_flag = 2; // Always restart to re-enable disabled features during update - } - } - else if (UPL_SETTINGS == Mqtt.file_type) { - if (!SettingsConfigRestore()) { - ResponseCmndFailed(); - } else { - TasmotaGlobal.restart_flag = 2; // Always restart to re-enable disabled features during update - } - } - - } - Mqtt.file_buffer = nullptr; - } - } - - if (!Mqtt.file_buffer) { - TasmotaGlobal.masterlog_level = LOG_LEVEL_NONE; // Enable logging - Mqtt.file_id = 0; - Mqtt.file_size = 0; - Mqtt.file_type = 0; - Mqtt.file_md5 = (const char*) nullptr; // Force deallocation of the String internal memory - } - MqttPublishPrefixTopic_P(STAT, XdrvMailbox.command); // Enforce stat/wemos10/FILEUPLOAD - ResponseClear(); -} - -void CmndFileDownload(void) { -/* - Download (binary) max 700 bytes chunks of data base64 encoded with MD5 hash over base64 decoded data - Currently supports Settings (file type 2) - Filedownload 0 - Abort current download - FileDownload 2 - Start download of settings file - FileDownload - Continue downloading data until reception of MD5 hash -*/ - - if (Mqtt.file_id && Mqtt.file_buffer) { - bool finished = false; - - if (0 == XdrvMailbox.payload) { // Abort file download - ResponseCmndChar(PSTR(D_JSON_ABORTED)); - finished = true; - } - else if (Mqtt.file_pos < Mqtt.file_size) { - uint32_t bytes_left = Mqtt.file_size - Mqtt.file_pos; - uint32_t write_bytes = (bytes_left < mqtt_file_chuck_size) ? bytes_left : mqtt_file_chuck_size; - - uint8_t* buffer = Mqtt.file_buffer + Mqtt.file_pos; - Mqtt.md5.add(buffer, write_bytes); - - // {"Id":117,"Data":"CRJcTQ9fYGF ... OT1BRUlNUVVZXWFk="} - Response_P(PSTR("{\"Id\":%d,\"Data\":\""), Mqtt.file_id); // FileTransferHeaderSize - char base64_data[encode_base64_length(write_bytes)]; - encode_base64((unsigned char*)buffer, write_bytes, (unsigned char*)base64_data); - ResponseAppend_P(base64_data); - ResponseAppend_P("\"}"); - - Mqtt.file_pos += write_bytes; - } else { - Mqtt.md5.calculate(); - - // {"Id":117,"Md5":"496fcbb433bbca89833063174d2c5747"} - Response_P(PSTR("{\"Id\":%d,\"Md5\":\"%s\"}"), Mqtt.file_id, Mqtt.md5.toString().c_str()); - finished = true; - } - - if (finished) { - if (UPL_SETTINGS == Mqtt.file_type) { - SettingsBufferFree(); - } - - Mqtt.file_id = 0; - } - } - else if (XdrvMailbox.data_len) { - Mqtt.file_buffer = nullptr; - Mqtt.file_id = (UtcTime() & 0xFE) +1; // Odd id between 1 and 255 - - if (UPL_SETTINGS == XdrvMailbox.payload) { - uint32_t len = SettingsConfigBackup(); - if (len) { - Mqtt.file_type = UPL_SETTINGS; - Mqtt.file_buffer = settings_buffer; - Mqtt.file_size = len; - - // {"File":"Config_wemos10_9.4.0.3.dmp","Id":117,"Type":2,"Size":4096} - Response_P(PSTR("{\"File\":\"%s\",\"Id\":%d,\"Type\":%d,\"Size\":%d}"), - SettingsConfigFilename().c_str(), Mqtt.file_id, Mqtt.file_type, len); - } - } - - if (Mqtt.file_buffer) { - Mqtt.file_pos = 0; - - Mqtt.md5 = MD5Builder(); - Mqtt.md5.begin(); - } else { - Mqtt.file_id = 0; - ResponseCmndFailed(); - } - } - MqttPublishPrefixTopic_P(STAT, XdrvMailbox.command); - ResponseClear(); -} - /*********************************************************************************************\ * TLS private key and certificate - store into Flash \*********************************************************************************************/ @@ -1960,9 +1715,6 @@ bool Xdrv02(uint8_t function) if (Settings.flag.mqtt_enabled) { // SetOption3 - Enable MQTT switch (function) { - case FUNC_PRE_INIT: - MqttInit(); - break; case FUNC_EVERY_50_MSECOND: // https://github.com/knolleary/pubsubclient/issues/556 MqttClient.loop(); break; @@ -1977,6 +1729,9 @@ bool Xdrv02(uint8_t function) case FUNC_COMMAND: result = DecodeCommand(kMqttCommands, MqttCommand, kMqttSynonyms); break; + case FUNC_PRE_INIT: + MqttInit(); + break; } } return result;