btclock_v3/src/lib/ota.cpp

442 lines
10 KiB
C++
Raw Normal View History

#include "ota.hpp"
TaskHandle_t taskOtaHandle = NULL;
2024-06-29 00:19:25 +00:00
bool isOtaUpdating = false;
2024-09-11 15:40:44 +00:00
QueueHandle_t otaQueue;
2024-09-11 15:40:44 +00:00
void setupOTA()
{
if (preferences.getBool("otaEnabled", DEFAULT_OTA_ENABLED))
{
ArduinoOTA.onStart(onOTAStart);
ArduinoOTA.onProgress(onOTAProgress);
2023-11-13 00:02:01 +00:00
ArduinoOTA.onError(onOTAError);
ArduinoOTA.onEnd(onOTAComplete);
ArduinoOTA.setHostname(getMyHostname().c_str());
ArduinoOTA.setMdnsEnabled(false);
ArduinoOTA.setRebootOnSuccess(false);
ArduinoOTA.begin();
2023-11-30 21:38:01 +00:00
// downloadUpdate();
2024-09-11 15:40:44 +00:00
otaQueue = xQueueCreate(1, sizeof(UpdateMessage));
2024-09-11 15:40:44 +00:00
xTaskCreate(handleOTATask, "handleOTA", 8192, NULL, 20,
2023-11-30 21:38:01 +00:00
&taskOtaHandle);
}
}
2024-09-11 15:40:44 +00:00
void onOTAProgress(unsigned int progress, unsigned int total)
{
uint percentage = progress / (total / 100);
pixels.fill(pixels.Color(0, 255, 0));
2024-09-11 15:40:44 +00:00
if (percentage < 100)
{
pixels.setPixelColor(0, pixels.Color(0, 0, 0));
}
2024-09-11 15:40:44 +00:00
if (percentage < 75)
{
pixels.setPixelColor(1, pixels.Color(0, 0, 0));
}
2024-09-11 15:40:44 +00:00
if (percentage < 50)
{
pixels.setPixelColor(2, pixels.Color(0, 0, 0));
}
2024-09-11 15:40:44 +00:00
if (percentage < 25)
{
pixels.setPixelColor(3, pixels.Color(0, 0, 0));
}
pixels.show();
}
2024-09-11 15:40:44 +00:00
void onOTAStart()
{
forceFullRefresh();
2023-11-30 21:38:01 +00:00
std::array<String, NUM_SCREENS> epdContent = {"U", "P", "D", "A",
"T", "E", "!"};
setEpdContent(epdContent);
// Stop all timers
esp_timer_stop(screenRotateTimer);
esp_timer_stop(minuteTimer);
2024-06-29 00:19:25 +00:00
isOtaUpdating = true;
// Stop or suspend all tasks
// vTaskSuspend(priceUpdateTaskHandle);
// vTaskSuspend(blockUpdateTaskHandle);
vTaskSuspend(workerTaskHandle);
vTaskSuspend(taskScreenRotateTaskHandle);
// vTaskSuspend(ledTaskHandle);
vTaskSuspend(buttonTaskHandle);
2024-09-11 15:40:44 +00:00
// stopWebServer();
stopBlockNotify();
stopPriceNotify();
}
2024-09-11 15:40:44 +00:00
void handleOTATask(void *parameter)
{
UpdateMessage msg;
for (;;)
{
if (xQueueReceive(otaQueue, &msg, 0) == pdTRUE)
{
if (msg.updateType == UPDATE_ALL) {
queueLedEffect(LED_FLASH_UPDATE);
int resultWebUi = downloadUpdateHandler(UPDATE_WEBUI);
queueLedEffect(LED_FLASH_UPDATE);
int resultFw = downloadUpdateHandler(UPDATE_FIRMWARE);
if (resultWebUi == 0 && resultFw == 0) {
ESP.restart();
} else {
queueLedEffect(LED_FLASH_ERROR);
vTaskDelay(pdMS_TO_TICKS(3000));
ESP.restart();
}
}
2024-09-11 15:40:44 +00:00
}
ArduinoOTA.handle(); // Allow OTA updates to occur
2024-06-29 00:19:25 +00:00
vTaskDelay(pdMS_TO_TICKS(2000));
}
}
ReleaseInfo getLatestRelease(const String &fileToDownload)
2024-09-11 15:40:44 +00:00
{
String releaseUrl = "https://api.github.com/repos/btclock/btclock_v3/releases/latest";
WiFiClientSecure client;
client.setCACert(github_root_ca);
HTTPClient http;
http.begin(client, releaseUrl);
http.setUserAgent(USER_AGENT);
int httpCode = http.GET();
ReleaseInfo info = {"", ""};
2024-09-11 15:40:44 +00:00
if (httpCode > 0)
{
String payload = http.getString();
JsonDocument doc;
deserializeJson(doc, payload);
JsonArray assets = doc["assets"];
for (JsonObject asset : assets)
{
String assetName = asset["name"].as<String>();
if (assetName == fileToDownload)
{
info.fileUrl = asset["browser_download_url"].as<String>();
}
else if (assetName == fileToDownload + ".sha256")
{
info.checksumUrl = asset["browser_download_url"].as<String>();
}
if (!info.fileUrl.isEmpty() && !info.checksumUrl.isEmpty())
2024-09-11 15:40:44 +00:00
{
break;
}
}
Serial.printf("Latest release URL: %s\r\n", info.fileUrl.c_str());
Serial.printf("Checksum URL: %s\r\n", info.checksumUrl.c_str());
2024-09-11 15:40:44 +00:00
}
http.end();
return info;
2024-09-11 15:40:44 +00:00
}
int downloadUpdateHandler(char updateType)
{
WiFiClientSecure client;
client.setCACert(github_root_ca);
HTTPClient http;
http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
ReleaseInfo latestRelease;
2024-09-11 15:40:44 +00:00
switch (updateType)
{
case UPDATE_FIRMWARE:
{
latestRelease = getLatestRelease(getFirmwareFilename());
}
break;
case UPDATE_WEBUI:
{
latestRelease = getLatestRelease("littlefs.bin");
// updateWebUi(latestRelease.fileUrl, U_SPIFFS);
// return 0;
2024-09-11 15:40:44 +00:00
}
break;
}
2024-09-11 15:40:44 +00:00
// First, download the expected SHA256
String expectedSHA256 = downloadSHA256(latestRelease.checksumUrl);
2024-09-11 15:40:44 +00:00
if (expectedSHA256.isEmpty())
{
Serial.println("Failed to get SHA256 checksum. Aborting update.");
return false;
}
http.begin(client, latestRelease.fileUrl);
2024-09-11 15:40:44 +00:00
http.setUserAgent(USER_AGENT);
int httpCode = http.GET();
if (httpCode == HTTP_CODE_OK)
{
int contentLength = http.getSize();
if (contentLength > 0)
{
// Allocate memory to store the firmware
uint8_t *firmware = (uint8_t *)malloc(contentLength);
if (!firmware)
{
Serial.println(F("Not enough memory to store firmware"));
return false;
}
WiFiClient *stream = http.getStreamPtr();
size_t bytesRead = 0;
while (bytesRead < contentLength)
{
size_t available = stream->available();
if (available)
{
size_t readBytes = stream->readBytes(firmware + bytesRead, available);
bytesRead += readBytes;
}
yield(); // Allow background tasks to run
}
if (bytesRead != contentLength)
{
Serial.println("Failed to read entire firmware");
free(firmware);
return false;
}
// Calculate SHA256
String calculated_sha256 = calculateSHA256(firmware, contentLength);
Serial.print("Calculated checksum: ");
Serial.println(calculated_sha256);
Serial.print("Expected checksum: ");
Serial.println(expectedSHA256);
if (calculated_sha256 != expectedSHA256)
{
Serial.println("Checksum mismatch. Aborting update.");
free(firmware);
return false;
}
Update.onProgress(onOTAProgress);
if (Update.begin(contentLength, updateType))
{
onOTAStart();
size_t written = Update.write(firmware, contentLength);
2024-09-11 15:40:44 +00:00
if (written == contentLength)
{
Serial.println("Written : " + String(written) + " successfully");
free(firmware);
2024-09-11 15:40:44 +00:00
}
else
{
Serial.println("Written only : " + String(written) + "/" + String(contentLength) + ". Retry?");
free(firmware);
return 503;
2024-09-11 15:40:44 +00:00
}
if (Update.end())
{
Serial.println("OTA done!");
if (Update.isFinished())
{
Serial.println("Update successfully completed. Rebooting.");
// ESP.restart();
2024-09-11 15:40:44 +00:00
}
else
{
Serial.println("Update not finished? Something went wrong!");
free(firmware);
return 503;
2024-09-11 15:40:44 +00:00
}
}
else
{
Serial.println("Error Occurred. Error #: " + String(Update.getError()));
free(firmware);
return 503;
2024-09-11 15:40:44 +00:00
}
}
else
{
Serial.println("Not enough space to begin OTA");
free(firmware);
return 503;
2024-09-11 15:40:44 +00:00
}
}
else
{
Serial.println("Invalid content length");
return 503;
2024-09-11 15:40:44 +00:00
}
}
else
{
Serial.printf("HTTP error: %d\n", httpCode);
return 503;
}
http.end();
return 0;
2024-09-11 15:40:44 +00:00
}
void updateWebUi(String latestRelease, int command)
{
WiFiClientSecure client;
client.setCACert(github_root_ca);
HTTPClient http;
http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
http.begin(client, latestRelease);
http.setUserAgent(USER_AGENT);
int httpCode = http.GET();
if (httpCode == HTTP_CODE_OK)
{
int contentLength = http.getSize();
if (contentLength > 0)
{
uint8_t *buffer = (uint8_t *)malloc(contentLength);
if (buffer)
{
WiFiClient *stream = http.getStreamPtr();
size_t written = stream->readBytes(buffer, contentLength);
if (written == contentLength)
{
String expectedSHA256 = "";
if (command == U_FLASH)
{
expectedSHA256 = downloadSHA256(getFirmwareFilename());
Serial.print("Expected checksum: ");
Serial.println(expectedSHA256);
}
String calculated_sha256 = calculateSHA256(buffer, contentLength);
Serial.print("Checksum is ");
Serial.println(calculated_sha256);
if ((command == U_FLASH && expectedSHA256.equals(calculated_sha256)) || command == U_SPIFFS)
{
Serial.println("Checksum verified. Proceeding with update.");
Update.onProgress(onOTAProgress);
if (Update.begin(contentLength, command))
{
onOTAStart();
Update.write(buffer, contentLength);
if (Update.end())
{
Serial.println("Update complete. Rebooting.");
ESP.restart();
}
else
{
Serial.println("Error in update process.");
}
}
else
{
Serial.println("Not enough space to begin OTA");
}
}
else
{
Serial.println("Checksum mismatch. Aborting update.");
}
}
else
{
Serial.println("Error downloading firmware");
}
free(buffer);
}
else
{
Serial.println("Not enough memory to allocate buffer");
}
}
else
{
Serial.println("Invalid content length");
}
}
else
{
Serial.print(httpCode);
Serial.println("Error on HTTP request");
}
}
void onOTAError(ota_error_t error)
{
2024-04-11 22:17:40 +00:00
Serial.println(F("\nOTA update error, restarting"));
2023-11-13 00:02:01 +00:00
Wire.end();
SPI.end();
2024-06-29 00:19:25 +00:00
isOtaUpdating = false;
2023-11-13 00:02:01 +00:00
delay(1000);
2023-11-13 19:02:58 +00:00
ESP.restart();
2023-11-13 00:02:01 +00:00
}
2024-09-11 15:40:44 +00:00
void onOTAComplete()
{
2024-04-11 22:17:40 +00:00
Serial.println(F("\nOTA update finished"));
Wire.end();
SPI.end();
delay(1000);
ESP.restart();
}
2024-06-29 00:19:25 +00:00
2024-09-11 15:40:44 +00:00
bool getIsOTAUpdating()
{
2024-06-29 00:19:25 +00:00
return isOtaUpdating;
2024-09-11 15:40:44 +00:00
}
String downloadSHA256(const String &sha256Url)
2024-09-11 15:40:44 +00:00
{
if (sha256Url.isEmpty())
{
Serial.println("Failed to get SHA256 file URL");
return "";
}
WiFiClientSecure client;
client.setCACert(github_root_ca);
HTTPClient http;
http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
http.begin(client, sha256Url);
http.setUserAgent(USER_AGENT);
int httpCode = http.GET();
if (httpCode == HTTP_CODE_OK)
{
String sha256 = http.getString();
sha256.trim(); // Remove any whitespace or newline characters
return sha256;
}
else
{
Serial.printf("Failed to download SHA256 file. HTTP error: %d\n", httpCode);
return "";
}
2024-06-29 00:19:25 +00:00
}