|
// 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 ); |
|
} |