Skip to content

Instantly share code, notes, and snippets.

@proffalken
Last active March 13, 2019 10:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save proffalken/1cb0c514577d46f5df263b963ff460a0 to your computer and use it in GitHub Desktop.
Save proffalken/1cb0c514577d46f5df263b963ff460a0 to your computer and use it in GitHub Desktop.
OTA using MQTT

OTA Firmware Updates for ESP-based devices using MQTT

Use the above as a template for your code.

The device will register with the MQTT server on startup under the topic devices/state/<device_type>/<device_mac> with a device type and a client id.

Use these values to publish JSON with the content {"update_available": 1} to devices/control/<device_type>/<device_mac> when you want to update the firmware.

The firmware should be stored on a webserver under /devices/<device_type>/firmware.bin - this needs work as it means you can only store/deploy a single version of firmware at any given time.

// Include the libraries
#include <WiFi.h>
#include <Update.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
// Setup Variables
const char* ssid = "my-network";
const char* password = "my-network-password";
const char* mqtt_server = "ip.address.of.mqttserver";
String fw_host = "ip.address.of.firmwarehost";
String bin = "";
const char* device_type = "my-device";
long lastMsg = 0;
char msg[50];
WiFiClient espClient;
PubSubClient mqttClient(espClient);
const char FW_VERSION[4] = "2.3";
// Variables to validate
// response from S3
int contentLength = 0;
bool isValidContentType = false;
void reconnect() {
// Loop until we're reconnected
while (!mqttClient.connected()) {
Serial.print("Attempting MQTT connection...");
// Create a random client ID
String clientId = getMAC();
String lwt_topic = "devices/state/";
lwt_topic.concat(device_type);
lwt_topic.concat("/");
lwt_topic.concat(getMAC());
char lwt_topic_name[50];
lwt_topic.toCharArray(lwt_topic_name, 50);
// Attempt to connect
if (mqttClient.connect(clientId.c_str(), lwt_topic_name, 2, 0, "{'status': 'offline'}" )) {
Serial.println("connected");
// Once connected, publish an announcement...
StaticJsonBuffer<500> systemDescription;
JsonObject& root = systemDescription.createObject();
root["device_name"] = clientId;
root["device_type"] = device_type;
char bodyString[500];
root.printTo(bodyString, sizeof(bodyString));
root.printTo(Serial);
String pub_topic = "devices/state/";
pub_topic.concat(device_type);
pub_topic.concat("/");
pub_topic.concat(getMAC());
char pub_topic_name[50];
pub_topic.toCharArray(pub_topic_name, 50);
Serial.print("Publishing to: ");
Serial.println(pub_topic_name);
mqttClient.publish(pub_topic_name, bodyString);
// ... and resubscribe
String sub_topic = "devices/control/";
sub_topic.concat(device_type);
sub_topic.concat("/");
sub_topic.concat(getMAC());
char sub_topic_name[50];
sub_topic.toCharArray(sub_topic_name, 50);
Serial.print("Subscribing to: ");
Serial.println(sub_topic_name);
mqttClient.subscribe(sub_topic_name);
} else {
Serial.print("failed, rc=");
Serial.print(mqttClient.state());
Serial.println(" try again in 5 seconds");
// Wait 5 seconds before retrying
delay(5000);
}
}
}
void setup_wifi() {
delay(10);
// We start by connecting to a WiFi network
Serial.println();
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.disconnect();
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
randomSeed(micros());
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
u8x8.drawString(0, 1, "WiFi connected");
}
void fw_update(char* topic, byte* payload, unsigned int length) {
StaticJsonBuffer<500> jsonBuffer;
Serial.print("Message arrived [");
Serial.print(topic);
Serial.println("] ");
char pdata[255];
for (int i = 0; i < length; i++) {
Serial.print((char)payload[i]);
pdata[i] = (char)payload[i];
}
Serial.println();
JsonObject& root = jsonBuffer.parseObject(pdata);
if ( root["update_available"] == 1) {
execOTA();
}
else
{
Serial.println("No Update Available");
}
}
void setup() {
// Setup the Serial Connection
Serial.begin(115200);
Serial.setDebugOutput(true);
setup_wifi();
mqttClient.setServer(mqtt_server, 1883);
mqttClient.setCallback(fw_update);
}
/****** THE MAIN PART OF THE CODE *********/
void loop() {
if (!mqttClient.connected()) {
reconnect();
}
mqttClient.loop();
}
// Utility to extract header value from headers
String getHeaderValue(String header, String headerName) {
return header.substring(strlen(headerName.c_str()));
}
// OTA Logic
void execOTA() {
Serial.println("Connecting to: " + String(fw_host));
bin = "/devices/";
bin.concat(device_type);
bin.concat("/firmware.bin");
// Connect to S3
if (espClient.connect(fw_host.c_str(), 80)) {
// Connection Succeed.
// Fecthing the bin
Serial.println("Fetching Bin: " + String(bin));
// Get the contents of the bin file
espClient.print(String("GET ") + bin + " HTTP/1.1\r\n" +
"Host: " + fw_host + "\r\n" +
"Cache-Control: no-cache\r\n" +
"Connection: close\r\n\r\n");
// Check what is being sent
// Serial.print(String("GET ") + bin + " HTTP/1.1\r\n" +
// "Host: " + host + "\r\n" +
// "Cache-Control: no-cache\r\n" +
// "Connection: close\r\n\r\n");
unsigned long timeout = millis();
while (espClient.available() == 0) {
if (millis() - timeout > 5000) {
Serial.println("Client Timeout !");
espClient.stop();
return;
}
}
// Once the response is available,
// check stuff
/*
Response Structure
HTTP/1.1 200 OK
x-amz-id-2: NVKxnU1aIQMmpGKhSwpCBh8y2JPbak18QLIfE+OiUDOos+7UftZKjtCFqrwsGOZRN5Zee0jpTd0=
x-amz-request-id: 2D56B47560B764EC
Date: Wed, 14 Jun 2017 03:33:59 GMT
Last-Modified: Fri, 02 Jun 2017 14:50:11 GMT
ETag: "d2afebbaaebc38cd669ce36727152af9"
Accept-Ranges: bytes
Content-Type: application/octet-stream
Content-Length: 357280
Server: AmazonS3
{{BIN FILE CONTENTS}}
*/
while (espClient.available()) {
// read line till /n
String line = espClient.readStringUntil('\n');
// remove space, to check if the line is end of headers
line.trim();
// if the the line is empty,
// this is end of headers
// break the while and feed the
// remaining `espClient` to the
// Update.writeStream();
if (!line.length()) {
//headers ended
break; // and get the OTA started
}
// Check if the HTTP Response is 200
// else break and Exit Update
if (line.startsWith("HTTP/1.1")) {
if (line.indexOf("200") < 0) {
Serial.println("Got a non 200 status code from server. Exiting OTA Update.");
break;
}
}
// extract headers here
// Start with content length
if (line.startsWith("Content-Length: ")) {
contentLength = atoi((getHeaderValue(line, "Content-Length: ")).c_str());
Serial.println("Got " + String(contentLength) + " bytes from server");
}
// Next, the content type
if (line.startsWith("Content-Type: ")) {
String contentType = getHeaderValue(line, "Content-Type: ");
Serial.println("Got " + contentType + " payload.");
if (contentType == "application/octet-stream") {
isValidContentType = true;
}
}
}
} else {
// Connect to S3 failed
// May be try?
// Probably a choppy network?
Serial.println("Connection to " + String(fw_host) + " failed. Please check your setup");
// retry??
// execOTA();
}
// Check what is the contentLength and if content type is `application/octet-stream`
Serial.println("contentLength : " + String(contentLength) + ", isValidContentType : " + String(isValidContentType));
// check contentLength and content type
if (contentLength && isValidContentType) {
// Check if there is enough to OTA Update
bool canBegin = Update.begin(contentLength);
// If yes, begin
if (canBegin) {
Serial.println("Begin OTA. This may take 2 - 5 mins to complete. Things might be quite for a while.. Patience!");
// No activity would appear on the Serial monitor
// So be patient. This may take 2 - 5mins to complete
size_t written = Update.writeStream(espClient);
if (written == contentLength) {
Serial.println("Written : " + String(written) + " successfully");
} else {
Serial.println("Written only : " + String(written) + "/" + String(contentLength) + ". Retry?" );
// retry??
// execOTA();
}
if (Update.end()) {
Serial.println("OTA done!");
if (Update.isFinished()) {
Serial.println("Update successfully completed. Rebooting.");
ESP.restart();
} else {
Serial.println("Update not finished? Something went wrong!");
}
} else {
Serial.println("Error Occurred. Error #: " + String(Update.getError()));
}
} else {
// not enough space to begin OTA
// Understand the partitions and
// space availability
Serial.println("Not enough space to begin OTA");
espClient.flush();
}
} else {
Serial.println("There was no content in the response");
espClient.flush();
}
}
String getMAC()
{
String macaddr = String(WiFi.macAddress());
macaddr.replace(":", "");
macaddr.toLowerCase();
return String( macaddr );
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment