From d5ebb9a1651448ec55a9e2475cb4dbfcecb72370 Mon Sep 17 00:00:00 2001 From: Eric Date: Sat, 31 Jul 2021 14:41:35 -0700 Subject: [PATCH] OTA Solution - Build your own OTA platform (2/2, ESP32 OTA Application) --- .../Simple_OTA_Application/ButtonCont.cpp | 25 +++ .../Simple_OTA_Application/ButtonCont.h | 16 ++ .../Simple_OTA_Application/Display.cpp | 138 ++++++++++++++ .../Simple_OTA_Application/Display.h | 21 +++ .../Simple_OTA_Application/FileIO.cpp | 117 ++++++++++++ .../Simple_OTA_Application/FileIO.h | 23 +++ .../Simple_OTA_Application/MyFirmware.h | 16 ++ .../Simple_OTA_Application/Network.cpp | 168 +++++++++++++++++ .../Simple_OTA_Application/Network.h | 21 +++ .../Simple_OTA_Application/SimpleOTA.cpp | 174 ++++++++++++++++++ .../Simple_OTA_Application/SimpleOTA.h | 48 +++++ .../Simple_OTA_Application.ino | 16 ++ .../Simple_OTA_Application/Updater.cpp | 56 ++++++ .../Simple_OTA_Application/Updater.h | 10 + .../Simple_OTA_Application/VersionCont.cpp | 70 +++++++ .../Simple_OTA_Application/VersionCont.h | 21 +++ 16 files changed, 940 insertions(+) create mode 100644 ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/ButtonCont.cpp create mode 100644 ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/ButtonCont.h create mode 100644 ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/Display.cpp create mode 100644 ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/Display.h create mode 100644 ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/FileIO.cpp create mode 100644 ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/FileIO.h create mode 100644 ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/MyFirmware.h create mode 100644 ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/Network.cpp create mode 100644 ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/Network.h create mode 100644 ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/SimpleOTA.cpp create mode 100644 ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/SimpleOTA.h create mode 100644 ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/Simple_OTA_Application.ino create mode 100644 ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/Updater.cpp create mode 100644 ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/Updater.h create mode 100644 ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/VersionCont.cpp create mode 100644 ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/VersionCont.h diff --git a/ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/ButtonCont.cpp b/ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/ButtonCont.cpp new file mode 100644 index 0000000..33d05c2 --- /dev/null +++ b/ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/ButtonCont.cpp @@ -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(); +} \ No newline at end of file diff --git a/ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/ButtonCont.h b/ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/ButtonCont.h new file mode 100644 index 0000000..b3ac3f6 --- /dev/null +++ b/ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/ButtonCont.h @@ -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(); +}; \ No newline at end of file diff --git a/ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/Display.cpp b/ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/Display.cpp new file mode 100644 index 0000000..f377e4c --- /dev/null +++ b/ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/Display.cpp @@ -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); + } +} diff --git a/ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/Display.h b/ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/Display.h new file mode 100644 index 0000000..9b51295 --- /dev/null +++ b/ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/Display.h @@ -0,0 +1,21 @@ +#include + +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); +}; diff --git a/ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/FileIO.cpp b/ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/FileIO.cpp new file mode 100644 index 0000000..089e30f --- /dev/null +++ b/ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/FileIO.cpp @@ -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; +} \ No newline at end of file diff --git a/ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/FileIO.h b/ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/FileIO.h new file mode 100644 index 0000000..2ccf223 --- /dev/null +++ b/ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/FileIO.h @@ -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 \ No newline at end of file diff --git a/ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/MyFirmware.h b/ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/MyFirmware.h new file mode 100644 index 0000000..832d3ee --- /dev/null +++ b/ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/MyFirmware.h @@ -0,0 +1,16 @@ +#ifndef MyFirmware_H_ +#define MyFirmware_H_ +#include + +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 diff --git a/ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/Network.cpp b/ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/Network.cpp new file mode 100644 index 0000000..ff7abfb --- /dev/null +++ b/ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/Network.cpp @@ -0,0 +1,168 @@ +#include "Network.h" +#include +#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(); + firmware.build_num = doc["buildNum"]; + firmware.build_date = doc["buildDate"].as(); + firmware.server_file_path = doc["serverFilePath"].as(); + firmware.file_size = doc["fileSize"]; + firmware.md5_checksum = doc["md5Checksum"].as(); + + } 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; +} diff --git a/ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/Network.h b/ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/Network.h new file mode 100644 index 0000000..76ed3f9 --- /dev/null +++ b/ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/Network.h @@ -0,0 +1,21 @@ +#include +#include +#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); +}; diff --git a/ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/SimpleOTA.cpp b/ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/SimpleOTA.cpp new file mode 100644 index 0000000..f6f24c7 --- /dev/null +++ b/ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/SimpleOTA.cpp @@ -0,0 +1,174 @@ +#include +#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; +} diff --git a/ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/SimpleOTA.h b/ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/SimpleOTA.h new file mode 100644 index 0000000..20c578b --- /dev/null +++ b/ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/SimpleOTA.h @@ -0,0 +1,48 @@ +#include "Display.h" +#include "VersionCont.h" +#include "Network.h" +#include "ButtonCont.h" +#include "Updater.h" +#include + +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(); +}; \ No newline at end of file diff --git a/ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/Simple_OTA_Application.ino b/ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/Simple_OTA_Application.ino new file mode 100644 index 0000000..9bbf51f --- /dev/null +++ b/ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/Simple_OTA_Application.ino @@ -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); +} diff --git a/ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/Updater.cpp b/ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/Updater.cpp new file mode 100644 index 0000000..37b36de --- /dev/null +++ b/ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/Updater.cpp @@ -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; + } +} \ No newline at end of file diff --git a/ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/Updater.h b/ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/Updater.h new file mode 100644 index 0000000..f8400ac --- /dev/null +++ b/ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/Updater.h @@ -0,0 +1,10 @@ +#include "FileIO.h" +#include + +class Updater { +private: + void performUpdate(Stream& updateSource, size_t updateSize); + +public: + bool updateFromFS(FileIO** fileIO); +}; diff --git a/ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/VersionCont.cpp b/ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/VersionCont.cpp new file mode 100644 index 0000000..874f733 --- /dev/null +++ b/ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/VersionCont.cpp @@ -0,0 +1,70 @@ +#include "VersionCont.h" +#include +#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 : ""; +} diff --git a/ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/VersionCont.h b/ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/VersionCont.h new file mode 100644 index 0000000..440df85 --- /dev/null +++ b/ESP32_OTA/Simple_OTA_Solution/Simple_OTA_Application/VersionCont.h @@ -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(); +};