mirror of
https://github.com/0015/ThatProject.git
synced 2026-01-12 09:17:42 +03:00
OTA Solution - Build your own OTA platform (2/2, ESP32 OTA Application)
This commit is contained in:
@@ -0,0 +1,25 @@
|
|||||||
|
#include "ButtonCont.h"
|
||||||
|
#define RIGHT_BUTTON_PIN 35
|
||||||
|
|
||||||
|
static ButtonCont* instance = NULL;
|
||||||
|
|
||||||
|
void released(Button2& btn) {
|
||||||
|
instance->released_cb();
|
||||||
|
}
|
||||||
|
|
||||||
|
ButtonCont::ButtonCont() {}
|
||||||
|
|
||||||
|
ButtonCont::ButtonCont(FuncPtrVoid f) {
|
||||||
|
instance = this;
|
||||||
|
button = new Button2(RIGHT_BUTTON_PIN);
|
||||||
|
button->setReleasedHandler(released);
|
||||||
|
released_cb = f;
|
||||||
|
}
|
||||||
|
|
||||||
|
ButtonCont::~ButtonCont() {
|
||||||
|
delete button;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ButtonCont::loop() {
|
||||||
|
button->loop();
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
#include "Button2.h"
|
||||||
|
class ButtonCont {
|
||||||
|
private:
|
||||||
|
Button2* button;
|
||||||
|
friend void receiveCallback(Button2& btn);
|
||||||
|
typedef void (*FuncPtrVoid)(void);
|
||||||
|
|
||||||
|
public:
|
||||||
|
FuncPtrVoid released_cb;
|
||||||
|
ButtonCont(FuncPtrVoid f);
|
||||||
|
ButtonCont();
|
||||||
|
~ButtonCont();
|
||||||
|
|
||||||
|
void loop();
|
||||||
|
void eventTrigger();
|
||||||
|
};
|
||||||
138
ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/Display.cpp
Normal file
138
ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/Display.cpp
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
#include "Display.h"
|
||||||
|
|
||||||
|
Display::Display() {
|
||||||
|
tft = new TFT_eSPI();
|
||||||
|
}
|
||||||
|
|
||||||
|
Display::~Display() {
|
||||||
|
delete tft;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Display::initTFT() {
|
||||||
|
tft->init();
|
||||||
|
tft->fillScreen(TFT_BLACK);
|
||||||
|
tft->setRotation(1);
|
||||||
|
tft->setTextColor(TFT_WHITE, TFT_BLACK);
|
||||||
|
tft->setTextDatum(MC_DATUM);
|
||||||
|
tft->setFreeFont(&Orbitron_Light_24);
|
||||||
|
tft->drawString("Waiting for WIFI-", tft->width() / 2, 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Display::fillBlackScreen() {
|
||||||
|
tft->fillScreen(TFT_BLACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Display::showVersion(int buildNum) {
|
||||||
|
String text = "Build#: ";
|
||||||
|
text += buildNum;
|
||||||
|
tft->drawString(text, tft->width() / 2, 80);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Display::timeUpdate(String date, String time) {
|
||||||
|
tft->setTextColor(TFT_WHITE, TFT_BLACK);
|
||||||
|
tft->setTextDatum(MC_DATUM);
|
||||||
|
//tft->setFreeFont(&Orbitron_Light_24);
|
||||||
|
tft->setFreeFont(&Satisfy_24);
|
||||||
|
tft->setTextPadding(0);
|
||||||
|
tft->drawString(date, tft->width() / 2, 50);
|
||||||
|
tft->setTextPadding(tft->width() - 20);
|
||||||
|
tft->drawString(time, tft->width() / 2, 80);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Display::showVersionBelow(int buildNum) {
|
||||||
|
tft->setFreeFont(&FreeSansBold9pt7b);
|
||||||
|
tft->setTextColor(TFT_YELLOW, TFT_BLACK);
|
||||||
|
tft->setTextDatum(BL_DATUM);
|
||||||
|
String text = "Current Build#: ";
|
||||||
|
text += buildNum;
|
||||||
|
tft->drawString(text, 0, tft->height());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Display::newMessage(String msg) {
|
||||||
|
tft->setFreeFont(&FreeSansBold9pt7b);
|
||||||
|
tft->setTextColor(TFT_GREEN, TFT_BLACK);
|
||||||
|
tft->setTextDatum(TR_DATUM);
|
||||||
|
tft->drawString(msg, tft->width(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Display::downloadScreen(int percent) {
|
||||||
|
if (percent == 0) {
|
||||||
|
this->fillBlackScreen();
|
||||||
|
tft->setTextColor(TFT_WHITE, TFT_BLACK);
|
||||||
|
tft->setFreeFont(&Orbitron_Light_24);
|
||||||
|
tft->setTextDatum(MC_DATUM);
|
||||||
|
tft->drawString("Downloading...", tft->width() / 2, 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->drawProgressBar(20, tft->height() / 2 + 20, tft->width() - 40, 20, percent, TFT_RED, TFT_YELLOW);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Display::drawProgressBar(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint8_t percent, uint16_t frameColor, uint16_t barColor) {
|
||||||
|
if (percent == 0) {
|
||||||
|
tft->fillRoundRect(x, y, w, h, 3, TFT_BLACK);
|
||||||
|
}
|
||||||
|
uint8_t margin = 2;
|
||||||
|
uint16_t barHeight = h - 2 * margin;
|
||||||
|
uint16_t barWidth = w - 2 * margin;
|
||||||
|
tft->drawRoundRect(x, y, w, h, 3, frameColor);
|
||||||
|
tft->fillRect(x + margin, y + margin, barWidth * percent / 100.0, barHeight, barColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Display::downloadFailure(String cpName) {
|
||||||
|
this->fillBlackScreen();
|
||||||
|
tft->setTextColor(TFT_WHITE, TFT_BLACK);
|
||||||
|
tft->setFreeFont(&Orbitron_Light_24);
|
||||||
|
tft->setTextDatum(MC_DATUM);
|
||||||
|
tft->drawString("MD5 checksum", tft->width() / 2, 10);
|
||||||
|
tft->drawString("Wrong!", tft->width() / 2, 40);
|
||||||
|
tft->drawString("Contact to ", tft->width() / 2, 70);
|
||||||
|
tft->drawString(cpName, tft->width() / 2, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Display::downloadSuccess() {
|
||||||
|
this->fillBlackScreen();
|
||||||
|
tft->setTextColor(TFT_WHITE, TFT_BLACK);
|
||||||
|
tft->setFreeFont(&Orbitron_Light_24);
|
||||||
|
tft->setTextDatum(MC_DATUM);
|
||||||
|
tft->drawString("MD5 checksum", tft->width() / 2, 10);
|
||||||
|
tft->drawString("Correct!", tft->width() / 2, 40);
|
||||||
|
tft->drawString("Countdown", tft->width() / 2, 70);
|
||||||
|
|
||||||
|
tft->setTextColor(TFT_RED, TFT_BLACK);
|
||||||
|
tft->setTextPadding(tft->width() - 20);
|
||||||
|
for (int i = 3; i > -1; --i) {
|
||||||
|
tft->drawString(String(i), tft->width() / 2, 100);
|
||||||
|
delay(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Display::firmwareScreen(bool isStart, bool isDone) {
|
||||||
|
this->fillBlackScreen();
|
||||||
|
tft->setTextColor(TFT_WHITE, TFT_BLACK);
|
||||||
|
tft->setFreeFont(&Orbitron_Light_24);
|
||||||
|
tft->setTextDatum(MC_DATUM);
|
||||||
|
|
||||||
|
if (isStart) {
|
||||||
|
tft->drawString("Updating", tft->width() / 2, 40);
|
||||||
|
tft->drawString("Firmware!", tft->width() / 2, 70);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDone) {
|
||||||
|
tft->drawString("OTA Done!", tft->width() / 2, 10);
|
||||||
|
tft->drawString("Rebooting", tft->width() / 2, 40);
|
||||||
|
tft->drawString("Countdown", tft->width() / 2, 70);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
tft->drawString("Sorry.", tft->width() / 2, 10);
|
||||||
|
tft->drawString("OTA Failure!", tft->width() / 2, 40);
|
||||||
|
tft->drawString("Try it again.", tft->width() / 2, 70);
|
||||||
|
}
|
||||||
|
|
||||||
|
tft->setTextColor(TFT_RED, TFT_BLACK);
|
||||||
|
tft->setTextPadding(tft->width() - 20);
|
||||||
|
for (int i = 3; i > -1; --i) {
|
||||||
|
tft->drawString(String(i), tft->width() / 2, 100);
|
||||||
|
delay(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#include <TFT_eSPI.h>
|
||||||
|
|
||||||
|
class Display {
|
||||||
|
private:
|
||||||
|
TFT_eSPI* tft;
|
||||||
|
void drawProgressBar(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint8_t percent, uint16_t frameColor, uint16_t barColor);
|
||||||
|
|
||||||
|
public:
|
||||||
|
Display();
|
||||||
|
~Display();
|
||||||
|
void initTFT();
|
||||||
|
void showVersion(int buildNum);
|
||||||
|
void showVersionBelow(int buildNum);
|
||||||
|
void fillBlackScreen();
|
||||||
|
void timeUpdate(String date, String time);
|
||||||
|
void newMessage(String msg);
|
||||||
|
void downloadScreen(int percent);
|
||||||
|
void downloadFailure(String cpName);
|
||||||
|
void downloadSuccess();
|
||||||
|
void firmwareScreen(bool isStart, bool isDone);
|
||||||
|
};
|
||||||
117
ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/FileIO.cpp
Normal file
117
ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/FileIO.cpp
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
#include "FileIO.h"
|
||||||
|
|
||||||
|
FileIO::FileIO() {
|
||||||
|
if (!SPIFFS.begin()) {
|
||||||
|
//if (!SPIFFS.begin(true)) {
|
||||||
|
Serial.println("SPIFFS initialisation failed!");
|
||||||
|
while (1) yield();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileIO::format() {
|
||||||
|
Serial.println("SPIFFS Format!");
|
||||||
|
SPIFFS.format();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileIO::listSPIFFS() {
|
||||||
|
Serial.println(F("\r\nListing SPIFFS files:"));
|
||||||
|
static const char line[] PROGMEM = "=================================================";
|
||||||
|
|
||||||
|
Serial.println(FPSTR(line));
|
||||||
|
Serial.println(F(" File name Size"));
|
||||||
|
Serial.println(FPSTR(line));
|
||||||
|
|
||||||
|
fs::File root = SPIFFS.open("/");
|
||||||
|
if (!root) {
|
||||||
|
Serial.println(F("Failed to open directory"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!root.isDirectory()) {
|
||||||
|
Serial.println(F("Not a directory"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::File file = root.openNextFile();
|
||||||
|
while (file) {
|
||||||
|
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
Serial.print("DIR : ");
|
||||||
|
String fileName = file.name();
|
||||||
|
Serial.print(fileName);
|
||||||
|
} else {
|
||||||
|
String fileName = file.name();
|
||||||
|
Serial.print(" " + fileName);
|
||||||
|
// File path can be 31 characters maximum in SPIFFS
|
||||||
|
int spaces = 33 - fileName.length(); // Tabulate nicely
|
||||||
|
if (spaces < 1) spaces = 1;
|
||||||
|
while (spaces--) Serial.print(" ");
|
||||||
|
String fileSize = (String)file.size();
|
||||||
|
spaces = 10 - fileSize.length(); // Tabulate nicely
|
||||||
|
if (spaces < 1) spaces = 1;
|
||||||
|
while (spaces--) Serial.print(" ");
|
||||||
|
Serial.println(fileSize + " bytes");
|
||||||
|
}
|
||||||
|
|
||||||
|
file = root.openNextFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.println(FPSTR(line));
|
||||||
|
Serial.println();
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::File FileIO::openFile(String fileName, bool isReadOnly) {
|
||||||
|
if (isReadOnly) {
|
||||||
|
return SPIFFS.open(fileName, "r");
|
||||||
|
} else {
|
||||||
|
this->removeFile(fileName);
|
||||||
|
return SPIFFS.open(fileName, "w");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileIO::closeFile(fs::File file) {
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileIO::removeFile(String fileName) {
|
||||||
|
if (SPIFFS.exists(fileName)) {
|
||||||
|
SPIFFS.remove(fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int FileIO::getFileSize(String fileName) {
|
||||||
|
fs::File file = SPIFFS.open(fileName, "r");
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fileSize = file.size();
|
||||||
|
file.close();
|
||||||
|
return fileSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileIO::mdContextInit() {
|
||||||
|
mbedtls_md_type_t md_type = MBEDTLS_MD_MD5;
|
||||||
|
mbedtls_md_init(&ctx);
|
||||||
|
mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(md_type), 0);
|
||||||
|
mbedtls_md_starts(&ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileIO::mdContextUpdate(const unsigned char* buff, int c) {
|
||||||
|
mbedtls_md_update(&ctx, buff, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
String FileIO::md5Result() {
|
||||||
|
byte md5Result[16];
|
||||||
|
mbedtls_md_finish(&ctx, md5Result);
|
||||||
|
mbedtls_md_free(&ctx);
|
||||||
|
|
||||||
|
String checksum = "";
|
||||||
|
for (int i = 0; i < sizeof(md5Result); i++) {
|
||||||
|
char str[3];
|
||||||
|
|
||||||
|
sprintf(str, "%02x", (int)md5Result[i]);
|
||||||
|
checksum += str;
|
||||||
|
}
|
||||||
|
return checksum;
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
#ifndef FileIO_H_
|
||||||
|
#define FileIO_H_
|
||||||
|
|
||||||
|
#include "SPIFFS.h"
|
||||||
|
#include "mbedtls/md.h"
|
||||||
|
class FileIO {
|
||||||
|
private:
|
||||||
|
mbedtls_md_context_t ctx;
|
||||||
|
|
||||||
|
public:
|
||||||
|
static constexpr const char* TEMP_BIN_FILE = "/download_firmware.bin";
|
||||||
|
FileIO();
|
||||||
|
void format();
|
||||||
|
void listSPIFFS();
|
||||||
|
fs::File openFile(String fileName, bool isReadOnly);
|
||||||
|
void closeFile(fs::File file);
|
||||||
|
void removeFile(String fileName);
|
||||||
|
int getFileSize(String fileName);
|
||||||
|
void mdContextInit();
|
||||||
|
void mdContextUpdate(const unsigned char* buff, int c);
|
||||||
|
String md5Result();
|
||||||
|
};
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
#ifndef MyFirmware_H_
|
||||||
|
#define MyFirmware_H_
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
struct firmware_t {
|
||||||
|
String company;
|
||||||
|
int build_num;
|
||||||
|
String build_date;
|
||||||
|
String server_file_path;
|
||||||
|
int file_size;
|
||||||
|
String md5_checksum;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct firmware_t Firmware;
|
||||||
|
|
||||||
|
#endif
|
||||||
168
ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/Network.cpp
Normal file
168
ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/Network.cpp
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
#include "Network.h"
|
||||||
|
#include <WiFi.h>
|
||||||
|
#define WIFI_SSID "ThatProject"
|
||||||
|
#define WIFI_PASS "California"
|
||||||
|
#define BASE_URL "http://192.168.0.2:9001/api"
|
||||||
|
|
||||||
|
const String API_KEY = "THIS_IS_MY_OWN_API_KEY";
|
||||||
|
|
||||||
|
Network::Network() {
|
||||||
|
localServerTime = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Network::WiFiBegin() {
|
||||||
|
WiFi.disconnect();
|
||||||
|
WiFi.begin(WIFI_SSID, WIFI_PASS);
|
||||||
|
|
||||||
|
while (WiFi.status() != WL_CONNECTED) {
|
||||||
|
delay(1000);
|
||||||
|
Serial.println("Connecting to WiFi..");
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.println("Connected to the WiFi network");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Network::fetchLocalServerTime() {
|
||||||
|
if ((WiFi.status() == WL_CONNECTED)) {
|
||||||
|
String targetURL = BASE_URL;
|
||||||
|
targetURL += "/get/time";
|
||||||
|
|
||||||
|
http.begin(targetURL);
|
||||||
|
|
||||||
|
if (http.GET() == HTTP_CODE_OK) {
|
||||||
|
String payload = http.getString();
|
||||||
|
Serial.println(payload);
|
||||||
|
|
||||||
|
DeserializationError error = deserializeJson(doc, payload);
|
||||||
|
if (error) {
|
||||||
|
Serial.print(F("deserializeJson() failed: "));
|
||||||
|
Serial.println(error.f_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String time = doc["timestamp"];
|
||||||
|
Serial.println(time);
|
||||||
|
Serial.println(time.toInt());
|
||||||
|
localServerTime = time.toInt();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
Serial.println("Error on HTTP request");
|
||||||
|
}
|
||||||
|
|
||||||
|
http.end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
long Network::getLocalServerTime() {
|
||||||
|
return localServerTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
Firmware Network::checkVersion() {
|
||||||
|
|
||||||
|
Firmware firmware;
|
||||||
|
firmware.build_num = -1;
|
||||||
|
|
||||||
|
if ((WiFi.status() == WL_CONNECTED)) {
|
||||||
|
|
||||||
|
String targetURL = BASE_URL;
|
||||||
|
targetURL += "/get/version";
|
||||||
|
http.begin(targetURL);
|
||||||
|
|
||||||
|
if (http.GET() == HTTP_CODE_OK) {
|
||||||
|
String payload = http.getString();
|
||||||
|
Serial.println(payload);
|
||||||
|
|
||||||
|
DeserializationError error = deserializeJson(doc, payload);
|
||||||
|
if (error) {
|
||||||
|
Serial.print(F("deserializeJson() failed: "));
|
||||||
|
Serial.println(error.f_str());
|
||||||
|
return firmware;
|
||||||
|
}
|
||||||
|
|
||||||
|
firmware.company = doc["companyName"].as<String>();
|
||||||
|
firmware.build_num = doc["buildNum"];
|
||||||
|
firmware.build_date = doc["buildDate"].as<String>();
|
||||||
|
firmware.server_file_path = doc["serverFilePath"].as<String>();
|
||||||
|
firmware.file_size = doc["fileSize"];
|
||||||
|
firmware.md5_checksum = doc["md5Checksum"].as<String>();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
Serial.println("Error on HTTP request");
|
||||||
|
}
|
||||||
|
|
||||||
|
http.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
return firmware;
|
||||||
|
}
|
||||||
|
|
||||||
|
String Network::fileDownload(FuncPtrInt callback, FileIO** fileIO, String target_path) {
|
||||||
|
|
||||||
|
String md5CheckSum = "wrong";
|
||||||
|
|
||||||
|
if ((WiFi.status() == WL_CONNECTED)) {
|
||||||
|
fs::File file = (*fileIO)->openFile(FileIO::TEMP_BIN_FILE, false);
|
||||||
|
|
||||||
|
String targetURL = BASE_URL;
|
||||||
|
targetURL += "/post/update";
|
||||||
|
|
||||||
|
http.begin(targetURL);
|
||||||
|
http.addHeader("Content-Type", "application/x-www-form-urlencoded");
|
||||||
|
String httpRequestData = "api_key=" + API_KEY + "&target_path=" + target_path;
|
||||||
|
|
||||||
|
if (file && http.POST(httpRequestData) == HTTP_CODE_OK) {
|
||||||
|
callback(0);
|
||||||
|
(*fileIO)->mdContextInit();
|
||||||
|
|
||||||
|
int fileSize = http.getSize();
|
||||||
|
Serial.print("File Length: ");
|
||||||
|
Serial.println(fileSize);
|
||||||
|
|
||||||
|
int unDownloadSize = fileSize;
|
||||||
|
int downloadSize = 0;
|
||||||
|
int preDownloadedPercent = 0;
|
||||||
|
|
||||||
|
uint8_t buff[128] = { 0 };
|
||||||
|
|
||||||
|
WiFiClient* stream = http.getStreamPtr();
|
||||||
|
while (http.connected() && (fileSize > 0 || fileSize == -1)) {
|
||||||
|
size_t size = stream->available();
|
||||||
|
if (size) {
|
||||||
|
int c = stream->readBytes(buff, ((size > sizeof(buff)) ? sizeof(buff) : size));
|
||||||
|
file.write(buff, c);
|
||||||
|
(*fileIO)->mdContextUpdate(buff, c);
|
||||||
|
|
||||||
|
if (fileSize > 0) fileSize -= c;
|
||||||
|
|
||||||
|
downloadSize += c;
|
||||||
|
int downloadedPercent = int((downloadSize * 100 / unDownloadSize));
|
||||||
|
|
||||||
|
if (preDownloadedPercent != downloadedPercent) {
|
||||||
|
callback(downloadedPercent);
|
||||||
|
preDownloadedPercent = downloadedPercent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delay(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.println();
|
||||||
|
Serial.print("[HTTP] connection closed or file end.\n");
|
||||||
|
|
||||||
|
|
||||||
|
(*fileIO)->closeFile(file);
|
||||||
|
(*fileIO)->listSPIFFS();
|
||||||
|
Serial.println("======MD5 CHECKSUM");
|
||||||
|
md5CheckSum = (*fileIO)->md5Result();
|
||||||
|
Serial.println(md5CheckSum);
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
Serial.println("Error on HTTP request");
|
||||||
|
}
|
||||||
|
|
||||||
|
http.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
return md5CheckSum;
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#include <ArduinoJson.h>
|
||||||
|
#include <HTTPClient.h>
|
||||||
|
#include "FileIO.h"
|
||||||
|
#include "MyFirmware.h"
|
||||||
|
|
||||||
|
class Network {
|
||||||
|
private:
|
||||||
|
HTTPClient http;
|
||||||
|
StaticJsonDocument<300> doc;
|
||||||
|
long localServerTime;
|
||||||
|
typedef void (*FuncPtrInt)(int);
|
||||||
|
|
||||||
|
|
||||||
|
public:
|
||||||
|
Network();
|
||||||
|
void WiFiBegin();
|
||||||
|
void fetchLocalServerTime();
|
||||||
|
long getLocalServerTime();
|
||||||
|
Firmware checkVersion();
|
||||||
|
String fileDownload(FuncPtrInt callback, FileIO** fileIO, String target_path);
|
||||||
|
};
|
||||||
@@ -0,0 +1,174 @@
|
|||||||
|
#include <Arduino.h>
|
||||||
|
#include "SimpleOTA.h"
|
||||||
|
|
||||||
|
static SimpleOTA *instance = NULL;
|
||||||
|
|
||||||
|
SimpleOTA::SimpleOTA() {
|
||||||
|
t1 = 0;
|
||||||
|
t2 = 0;
|
||||||
|
currentState = NONE;
|
||||||
|
instance = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
SimpleOTA::~SimpleOTA() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void buttonCallbackEvent() {
|
||||||
|
Serial.println("buttonCallbackEvent");
|
||||||
|
if (instance->currentState == SERVER_FOUND && instance->version->hasNewUpdate()) {
|
||||||
|
instance->startDownload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void networkDownloadEvent(int percent) {
|
||||||
|
Serial.print("networkDownloadEvent downloadPercent: ");
|
||||||
|
if (percent == 0) {
|
||||||
|
instance->currentState = FIRMWARE_DOWNLOAD_START;
|
||||||
|
}
|
||||||
|
Serial.println(percent);
|
||||||
|
instance->display->downloadScreen(percent);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SimpleOTA::begin() {
|
||||||
|
this->initDisplay();
|
||||||
|
this->initVersion();
|
||||||
|
this->initNetwork();
|
||||||
|
this->initFileIO();
|
||||||
|
this->initButton();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SimpleOTA::loop() {
|
||||||
|
|
||||||
|
switch (currentState) {
|
||||||
|
case NETWORK_CONNECTED:
|
||||||
|
|
||||||
|
if (millis() - t1 >= 1000) {
|
||||||
|
t1 = millis();
|
||||||
|
this->requestLocalServerTime();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SERVER_FOUND:
|
||||||
|
if (millis() - t1 >= 1000) {
|
||||||
|
t1 = millis();
|
||||||
|
this->updateTime();
|
||||||
|
display->showVersionBelow(version->getCurrentVersion());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (millis() - t2 >= 1000 * 10) {
|
||||||
|
t2 = millis();
|
||||||
|
this->serverFirmwareCheck();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
buttonCont->loop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SimpleOTA::initDisplay() {
|
||||||
|
Serial.println("initDisplay");
|
||||||
|
display = new Display();
|
||||||
|
display->initTFT();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SimpleOTA::initVersion() {
|
||||||
|
Serial.println("initVersion");
|
||||||
|
version = new VersionCont();
|
||||||
|
display->showVersion(version->getCurrentVersion());
|
||||||
|
}
|
||||||
|
|
||||||
|
void SimpleOTA::initNetwork() {
|
||||||
|
Serial.println("initNetwork");
|
||||||
|
network = new Network();
|
||||||
|
currentState = NETWORK_BEGIN;
|
||||||
|
network->WiFiBegin();
|
||||||
|
currentState = NETWORK_CONNECTED;
|
||||||
|
this->requestLocalServerTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SimpleOTA::initFileIO() {
|
||||||
|
Serial.println("initFileIO");
|
||||||
|
fileIO = new FileIO();
|
||||||
|
//fileIO->format();
|
||||||
|
fileIO->listSPIFFS();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SimpleOTA::initButton() {
|
||||||
|
Serial.println("initButton");
|
||||||
|
|
||||||
|
void (*ptr)(void) = &buttonCallbackEvent;
|
||||||
|
buttonCont = new ButtonCont(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SimpleOTA::requestLocalServerTime() {
|
||||||
|
network->fetchLocalServerTime();
|
||||||
|
|
||||||
|
if (network->getLocalServerTime() != 0) {
|
||||||
|
rtc.setTime(network->getLocalServerTime());
|
||||||
|
display->fillBlackScreen();
|
||||||
|
currentState = SERVER_FOUND;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SimpleOTA::updateTime() {
|
||||||
|
struct tm timeinfo = rtc.getTimeStruct();
|
||||||
|
display->timeUpdate(rtc.getDate(), rtc.getTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
void SimpleOTA::serverFirmwareCheck() {
|
||||||
|
version->setNewFirmware(network->checkVersion());
|
||||||
|
if (version->newFirmwareVersion() == -1) {
|
||||||
|
display->newMessage("Server Not Responding");
|
||||||
|
} else {
|
||||||
|
if (version->hasNewUpdate()) {
|
||||||
|
display->newMessage("New Build Available! ->");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SimpleOTA::startDownload() {
|
||||||
|
|
||||||
|
void (*ptr)(int) = &networkDownloadEvent;
|
||||||
|
|
||||||
|
bool compareMD5Checksum = version->md5CompareTo(network->fileDownload(ptr, &fileIO, version->getFirmwareServerPath()));
|
||||||
|
bool compareFileSize = version->fileSizeCompareTo(fileIO->getFileSize(FileIO::TEMP_BIN_FILE));
|
||||||
|
|
||||||
|
Serial.println("======compareMD5Checksum");
|
||||||
|
Serial.println(compareMD5Checksum);
|
||||||
|
Serial.println("======Downloaded File SIze");
|
||||||
|
Serial.println(compareFileSize);
|
||||||
|
|
||||||
|
if (compareMD5Checksum && compareFileSize) {
|
||||||
|
display->downloadSuccess();
|
||||||
|
this->updateFirmware();
|
||||||
|
} else {
|
||||||
|
display->downloadFailure(version->getCPName());
|
||||||
|
delay(5000);
|
||||||
|
display->fillBlackScreen();
|
||||||
|
currentState = SERVER_FOUND;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SimpleOTA::updateFirmware() {
|
||||||
|
currentState = FIRMWARE_DOWNLOAD_START;
|
||||||
|
display->firmwareScreen(true, false);
|
||||||
|
Updater *updater = new Updater();
|
||||||
|
if (updater->updateFromFS(&fileIO)) {
|
||||||
|
Serial.println("UPDATE DONE");
|
||||||
|
|
||||||
|
currentState = FIRMWARE_UPDATED;
|
||||||
|
version->saveVersion(version->newFirmwareVersion());
|
||||||
|
display->firmwareScreen(false, true);
|
||||||
|
|
||||||
|
ESP.restart();
|
||||||
|
} else {
|
||||||
|
Serial.println("UPDATE FAILURE");
|
||||||
|
display->firmwareScreen(false, false);
|
||||||
|
delay(3000);
|
||||||
|
display->fillBlackScreen();
|
||||||
|
currentState = SERVER_FOUND;
|
||||||
|
}
|
||||||
|
delete updater;
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
#include "Display.h"
|
||||||
|
#include "VersionCont.h"
|
||||||
|
#include "Network.h"
|
||||||
|
#include "ButtonCont.h"
|
||||||
|
#include "Updater.h"
|
||||||
|
#include <ESP32Time.h>
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
NONE,
|
||||||
|
NETWORK_BEGIN,
|
||||||
|
NETWORK_CONNECTED,
|
||||||
|
SERVER_FOUND,
|
||||||
|
FIRMWARE_DOWNLOAD_START,
|
||||||
|
FIRMWARE_UPDATED
|
||||||
|
} SimpleOTA_State_t;
|
||||||
|
|
||||||
|
class SimpleOTA {
|
||||||
|
private:
|
||||||
|
Display *display;
|
||||||
|
VersionCont *version;
|
||||||
|
Network *network;
|
||||||
|
FileIO *fileIO;
|
||||||
|
ButtonCont *buttonCont;
|
||||||
|
ESP32Time rtc;
|
||||||
|
|
||||||
|
long t1, t2;
|
||||||
|
|
||||||
|
void initDisplay();
|
||||||
|
void initVersion();
|
||||||
|
void initNetwork();
|
||||||
|
void initFileIO();
|
||||||
|
void initButton();
|
||||||
|
void requestLocalServerTime();
|
||||||
|
void updateTime();
|
||||||
|
void serverFirmwareCheck();
|
||||||
|
void startDownload();
|
||||||
|
void updateFirmware();
|
||||||
|
friend void buttonCallbackEvent();
|
||||||
|
friend void networkDownloadEvent(int percent);
|
||||||
|
|
||||||
|
SimpleOTA_State_t currentState;
|
||||||
|
|
||||||
|
public:
|
||||||
|
SimpleOTA();
|
||||||
|
~SimpleOTA();
|
||||||
|
void begin();
|
||||||
|
void loop();
|
||||||
|
};
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
#include "SimpleOTA.h"
|
||||||
|
|
||||||
|
SimpleOTA *simpleOTA;
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
// put your setup code here, to run once:
|
||||||
|
Serial.begin(115200);
|
||||||
|
simpleOTA = new SimpleOTA();
|
||||||
|
simpleOTA->begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
// put your main code here, to run repeatedly:
|
||||||
|
simpleOTA->loop();
|
||||||
|
delay(10);
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
#include "Updater.h"
|
||||||
|
|
||||||
|
void Updater::performUpdate(Stream& updateSource, size_t updateSize) {
|
||||||
|
if (Update.begin(updateSize)) {
|
||||||
|
size_t written = Update.writeStream(updateSource);
|
||||||
|
if (written == updateSize) {
|
||||||
|
Serial.println("Written : " + String(written) + " successfully");
|
||||||
|
} else {
|
||||||
|
Serial.println("Written only : " + String(written) + "/" + String(updateSize) + ". Retry?");
|
||||||
|
}
|
||||||
|
if (Update.end()) {
|
||||||
|
Serial.println("OTA done!");
|
||||||
|
if (Update.isFinished()) {
|
||||||
|
Serial.println("Update successfully completed. Rebooting.");
|
||||||
|
} else {
|
||||||
|
Serial.println("Update not finished? Something went wrong!");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Serial.println("Error Occurred. Error #: " + String(Update.getError()));
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
Serial.println("Not enough space to begin OTA");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check given FS for valid update.bin and perform update if available
|
||||||
|
bool Updater::updateFromFS(FileIO** fileIO) {
|
||||||
|
|
||||||
|
fs::File updateBin = (*fileIO)->openFile(FileIO::TEMP_BIN_FILE, true);
|
||||||
|
|
||||||
|
if (updateBin) {
|
||||||
|
if (updateBin.isDirectory()) {
|
||||||
|
Serial.println("Error, update.bin is not a file");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool updateResult = false;
|
||||||
|
size_t updateSize = updateBin.size();
|
||||||
|
|
||||||
|
if (updateSize > 0) {
|
||||||
|
Serial.println("Try to start update");
|
||||||
|
this->performUpdate(updateBin, updateSize);
|
||||||
|
updateResult = true;
|
||||||
|
} else {
|
||||||
|
Serial.println("Error, file is empty");
|
||||||
|
updateResult = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
(*fileIO)->closeFile(updateBin);
|
||||||
|
(*fileIO)->removeFile(FileIO::TEMP_BIN_FILE);
|
||||||
|
return updateResult;
|
||||||
|
} else {
|
||||||
|
Serial.println("Could not load update.bin from SPIFFS");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
#include "FileIO.h"
|
||||||
|
#include <Update.h>
|
||||||
|
|
||||||
|
class Updater {
|
||||||
|
private:
|
||||||
|
void performUpdate(Stream& updateSource, size_t updateSize);
|
||||||
|
|
||||||
|
public:
|
||||||
|
bool updateFromFS(FileIO** fileIO);
|
||||||
|
};
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
#include "VersionCont.h"
|
||||||
|
#include <EEPROM.h>
|
||||||
|
#define EEPROM_SIZE 1
|
||||||
|
|
||||||
|
VersionCont::VersionCont() {
|
||||||
|
this->loadVersion();
|
||||||
|
hasNewFirmware = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int VersionCont::getCurrentVersion() {
|
||||||
|
return firmwareVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VersionCont::loadVersion() {
|
||||||
|
EEPROM.begin(EEPROM_SIZE);
|
||||||
|
firmwareVersion = EEPROM.read(0);
|
||||||
|
|
||||||
|
if (firmwareVersion >= 255 || firmwareVersion == 0) {
|
||||||
|
firmwareVersion = 1;
|
||||||
|
this->saveVersion(firmwareVersion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VersionCont::saveVersion(int buildNum) {
|
||||||
|
if (buildNum > 255) buildNum = 255;
|
||||||
|
|
||||||
|
EEPROM.write(0, buildNum);
|
||||||
|
EEPROM.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VersionCont::setNewFirmware(Firmware firmware) {
|
||||||
|
newFirmware = firmware;
|
||||||
|
|
||||||
|
if (newFirmware.build_num != -1) {
|
||||||
|
Serial.printf("buildNum: %d\n", newFirmware.build_num);
|
||||||
|
Serial.printf("fileSize: %d\n", newFirmware.file_size);
|
||||||
|
Serial.printf("MD5Checksum: %s\n", newFirmware.md5_checksum.c_str());
|
||||||
|
Serial.printf("build_date: %s\n", newFirmware.build_date.c_str());
|
||||||
|
Serial.printf("company: %s\n", newFirmware.company.c_str());
|
||||||
|
Serial.printf("server_file_path: %s\n", newFirmware.server_file_path.c_str());
|
||||||
|
|
||||||
|
if (firmwareVersion < newFirmware.build_num) {
|
||||||
|
hasNewFirmware = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VersionCont::hasNewUpdate() {
|
||||||
|
return hasNewFirmware;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VersionCont::md5CompareTo(String checksum) {
|
||||||
|
return newFirmware.md5_checksum == checksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VersionCont::fileSizeCompareTo(int fileSize) {
|
||||||
|
return newFirmware.file_size == fileSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
int VersionCont::newFirmwareVersion() {
|
||||||
|
return newFirmware.build_num;
|
||||||
|
}
|
||||||
|
|
||||||
|
String VersionCont::getCPName() {
|
||||||
|
return newFirmware.company != NULL ? newFirmware.company : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
String VersionCont::getFirmwareServerPath() {
|
||||||
|
return newFirmware.server_file_path != NULL ? newFirmware.server_file_path : "";
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
|
||||||
|
#include "MyFirmware.h"
|
||||||
|
class VersionCont {
|
||||||
|
private:
|
||||||
|
int firmwareVersion;
|
||||||
|
void loadVersion();
|
||||||
|
Firmware newFirmware;
|
||||||
|
bool hasNewFirmware;
|
||||||
|
|
||||||
|
public:
|
||||||
|
VersionCont();
|
||||||
|
int getCurrentVersion();
|
||||||
|
void saveVersion(int buildNum);
|
||||||
|
void setNewFirmware(Firmware firmware);
|
||||||
|
bool hasNewUpdate();
|
||||||
|
bool md5CompareTo(String checksum);
|
||||||
|
bool fileSizeCompareTo(int fileSize);
|
||||||
|
int newFirmwareVersion();
|
||||||
|
String getCPName();
|
||||||
|
String getFirmwareServerPath();
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user