Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Trigger firmware update via MQTT. (ESP Only) #953

Merged
merged 1 commit into from
May 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions docs/use/gateway.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,26 @@ If you want the settings to be kept upon gateway restart, you can publish the co
Auto discovery is enable by default on release binaries, on platformio (except for UNO). With Arduino IDE please read the [advanced configuration section](../upload/advanced-configuration#auto-discovery) of the documentation.
:::

# Firmware update from MQTT (ESP only)

The gateway can be updated through an MQTT message by providing a JSON formatted message with a version number, OTA password (optional, see below), and URL to fetch the update from.

To enable this functionality, `MQTT_HTTPS_FW_UPDATE` will need to be defined or the line that defines in in user_config.h will need to be uncommented.

::: tip
If using an unsecure MQTT broker it is **highly recommended** to disable the password checking by setting the macro `MQTT_HTTPS_FW_UPDATE_USE_PASSWORD` to 0 (default is 1 (enabled)), otherwise a clear text password may be sent over the network.
:::

### Example firmware update message:
```
mosquitto_pub -t "home/<gateway_name>/commands/firmware_update" -m
'{
"version": "test",
"password": "OTAPASSWORD",
"url": "https://github.com/1technophile/OpenMQTTGateway/releases/download/v0.9.6/esp32-m5stack-ble-firmware.bin"
}'
```

# State LED usage

The gateway can support up to 3 LED to display its operating state:
Expand Down
25 changes: 25 additions & 0 deletions main/Ota_github.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
const char* _github_cert PROGMEM = R"EOF("
-----BEGIN CERTIFICATE-----
MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL
MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm
+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW
PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM
xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB
Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3
hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg
EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF
MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA
FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec
nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z
eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF
hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2
Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep
+OkuE6N36B9K
-----END CERTIFICATE-----
")EOF";
32 changes: 24 additions & 8 deletions main/User_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,30 @@ const byte subnet[] = {255, 255, 255, 0};

#if defined(ESP8266) || defined(ESP32) // for nodemcu, weemos and esp8266
//# define ESPWifiManualSetup true //uncomment you don't want to use wifimanager for your credential settings on ESP
//# define MQTT_HTTPS_FW_UPDATE //uncomment to enable updating via mqtt message.
#else // for arduino boards
const byte ip[] = {192, 168, 1, 99};
const byte mac[] = {0xDE, 0xED, 0xBA, 0xFE, 0x54, 0x95}; //W5100 ethernet shield mac adress
#endif

#ifdef MQTT_HTTPS_FW_UPDATE
# if defined(ESP8266) || defined(ESP32)
//If used, this should be set to the root CA certificate of the server hosting the firmware.
// The certificate must be in PEM ascii format
const char* https_fw_server_cert PROGMEM = R"EOF("
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
")EOF";
# define NTP_SERVER "pool.ntp.org"
# ifndef MQTT_HTTPS_FW_UPDATE_USE_PASSWORD
# define MQTT_HTTPS_FW_UPDATE_USE_PASSWORD 1 // Set this to 0 if not using TLS connection to MQTT broker to prevent clear text passwords being sent.
# endif
# else
# error "only ESP8266 and ESP32 support MQTT_HTTPS_FW_UPDATE"
# endif
#endif

//#define ESP32_ETHERNET=true // Uncomment to use Ethernet module on OLIMEX ESP32 Ethernet gateway

#if defined(ESPWifiManualSetup) // for nodemcu, weemos and esp8266
Expand Down Expand Up @@ -148,22 +167,18 @@ const byte mac[] = {0xDE, 0xED, 0xBA, 0xFE, 0x54, 0x95}; //W5100 ethernet shield

#ifdef SECURE_CONNECTION
# if defined(ESP8266) || defined(ESP32)
# if defined(ESP32)
# define CERT_ATTRIBUTE
# elif defined(ESP8266)
# define CERT_ATTRIBUTE PROGMEM
# endif

// The root ca certificate used for validating the MQTT broker
// The certificate must be in PEM ascii format
const char* certificate CERT_ATTRIBUTE = R"EOF("
const char* certificate PROGMEM = R"EOF("
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
")EOF";

// specify a NTP server here or else the NTP server from DHCP is used
# ifndef NTP_SERVER
//# define NTP_SERVER "pool.ntp.org"
# endif
# else
# error "only ESP8266 and ESP32 support SECURE_CONNECTION with TLS"
# endif
Expand Down Expand Up @@ -336,6 +351,7 @@ int lowpowermode = DEFAULT_LOW_POWER_MODE;
#define subjectMQTTtoX "/commands/#"
#define subjectMultiGTWKey "toMQTT"
#define subjectGTWSendKey "MQTTto"
#define subjectFWUpdate "firmware_update"

// key used for launching commands to the gateway
#define restartCmd "restart"
Expand Down Expand Up @@ -381,4 +397,4 @@ int lowpowermode = DEFAULT_LOW_POWER_MODE;
# define LOG_LEVEL LOG_LEVEL_NOTICE
#endif

#endif
#endif
136 changes: 135 additions & 1 deletion main/main.ino
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,9 @@ void connectMQTT() {
#endif
#ifdef ZgatewayIR
client.subscribe(subjectMultiGTWIR); // subject on which other OMG will publish, this OMG will store these msg and by the way don't republish them if they have been already published
#endif
#ifdef MQTT_HTTPS_FW_UPDATE
client.subscribe(subjectFWUpdate);
#endif
Log.trace(F("Subscription OK to the subjects %s" CR), topic2);
}
Expand Down Expand Up @@ -539,8 +542,11 @@ void callback(char* topic, byte* payload, unsigned int length) {
// Conversion to a printable string
p[length] = '\0';
//launch the function to treat received data if this data concern OpenMQTTGateway
if ((strstr(topic, subjectMultiGTWKey) != NULL) || (strstr(topic, subjectGTWSendKey) != NULL))
if ((strstr(topic, subjectMultiGTWKey) != NULL) ||
(strstr(topic, subjectGTWSendKey) != NULL) ||
(strstr(topic, subjectFWUpdate) != NULL))
receivingMQTT(topic, (char*)p);

// Free the memory
free(p);
}
Expand Down Expand Up @@ -1576,6 +1582,9 @@ void receivingMQTT(char* topicOri, char* datacallback) {
# ifdef ZgatewayRS232
MQTTtoRS232(topicOri, jsondata);
# endif
# ifdef MQTT_HTTPS_FW_UPDATE
MQTTHttpsFWUpdate(topicOri, jsondata);
# endif
#endif
digitalWrite(LED_SEND, LED_SEND_ON);

Expand Down Expand Up @@ -1613,6 +1622,131 @@ void receivingMQTT(char* topicOri, char* datacallback) {
}
}

#ifdef MQTT_HTTPS_FW_UPDATE
# ifndef NTP_SERVER
# error no NTP_SERVER defined
# endif
# include <WiFiClientSecure.h>
# ifdef ESP32
# include "Ota_github.h"
# include "zzHTTPUpdate.h"
# elif ESP8266
# include <ESP8266httpUpdate.h>
# endif
void MQTTHttpsFWUpdate(char* topicOri, JsonObject& HttpsFwUpdateData) {
if (strstr(topicOri, subjectFWUpdate) != NULL) {
const char* version = HttpsFwUpdateData["version"];
if (version && ((strlen(version) != strlen(OMG_VERSION)) || strcmp(version, OMG_VERSION) != 0)) {
const char* url = HttpsFwUpdateData["url"];
if (url) {
if (!strstr((url + (strlen(url) - 5)), ".bin")) {
Log.error(F("Invalid firmware extension" CR));
return;
}
} else {
Log.error(F("Invalid URL" CR));
return;
}

h2zero marked this conversation as resolved.
Show resolved Hide resolved
# if MQTT_HTTPS_FW_UPDATE_USE_PASSWORD > 0
# ifndef SECURE_CONNECTION
# warning using a password with an unsecure MQTT connection will send it as clear text!!!
# endif
const char* pwd = HttpsFwUpdateData["password"];
if (pwd) {
if (strcmp(pwd, ota_password) != 0) {
Log.error(F("Invalid OTA password" CR));
return;
}
} else {
Log.error(F("No password sent" CR));
return;
}
# endif

Log.warning(F("Starting firmware update" CR));

# if defined(ZgatewayBT) && defined(ESP32)
stopProcessing();
# endif

t_httpUpdate_return result = HTTP_UPDATE_FAILED;
if (strstr(url, "http:")) {
WiFiClient update_client;
# ifdef ESP32
httpUpdate.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
result = httpUpdate.update(update_client, url);
# elif ESP8266
ESPhttpUpdate.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
result = ESPhttpUpdate.update(update_client, url);
# endif

} else {
WiFiClientSecure update_client;
# ifdef SECURE_CONNECTION
client.disconnect();
update_client = eClient;
# else
configTime(0, 0, NTP_SERVER);
time_t now = time(nullptr);
uint8_t count = 0;
Log.trace(F("Waiting for NTP time sync" CR));
while ((now < 8 * 3600 * 2) && count++ < 60) {
vTaskDelay(500);
now = time(nullptr);
}

if (count >= 60) {
Log.error(F("Unable to update - invalid time" CR));
# if defined(ZgatewayBT) && defined(ESP32)
startProcessing();
# endif
return;
}
# endif
# ifdef ESP32
if (strstr(url, "github") != 0) {
update_client.setCACert(_github_cert);
} else {
update_client.setCACert(https_fw_server_cert);
}
update_client.setTimeout(12);
httpUpdate.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
result = httpUpdate.update(update_client, url);
# elif ESP8266
update_client.setInsecure(); // TODO: replace with cert checking
update_client.setTimeout(12000);
ESPhttpUpdate.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
result = ESPhttpUpdate.update(update_client, url);
# endif
}

switch (result) {
case HTTP_UPDATE_FAILED:
# ifdef ESP32
Log.error(F("HTTP_UPDATE_FAILED Error (%d): %s\n" CR), httpUpdate.getLastError(), httpUpdate.getLastErrorString().c_str());
# elif ESP8266
Log.error(F("HTTP_UPDATE_FAILED Error (%d): %s\n" CR), ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str());
# endif
break;

case HTTP_UPDATE_NO_UPDATES:
Log.notice(F("HTTP_UPDATE_NO_UPDATES" CR));
break;

case HTTP_UPDATE_OK:
Log.notice(F("HTTP_UPDATE_OK" CR));
break;
}

# if defined(ZgatewayBT) && defined(ESP32)
startProcessing();
# endif
}
}
}
#endif

void MQTTtoSYS(char* topicOri, JsonObject& SYSdata) { // json object decoding
if (cmpToMainTopic(topicOri, subjectMQTTtoSYSset)) {
Log.trace(F("MQTTtoSYS json" CR));
Expand Down
Loading