📦 Update firmware service

This commit is contained in:
Rune Harlyk
2024-11-08 17:20:22 +01:00
parent a7eec4f7f2
commit e77de7dbdb
11 changed files with 302 additions and 321 deletions
@@ -1,149 +1,161 @@
<script lang="ts"> <script lang="ts">
import { page } from '$app/stores'; import { page } from '$app/stores';
import { openModal, closeModal, closeAllModals } from 'svelte-modals'; import { openModal, closeModal, closeAllModals } from 'svelte-modals';
import { slide } from 'svelte/transition'; import { slide } from 'svelte/transition';
import { cubicOut } from 'svelte/easing'; import { cubicOut } from 'svelte/easing';
import ConfirmDialog from '$lib/components/ConfirmDialog.svelte'; import ConfirmDialog from '$lib/components/ConfirmDialog.svelte';
import Spinner from '$lib/components/Spinner.svelte'; import Spinner from '$lib/components/Spinner.svelte';
import SettingsCard from '$lib/components/SettingsCard.svelte'; import SettingsCard from '$lib/components/SettingsCard.svelte';
import { compareVersions } from 'compare-versions'; import { compareVersions } from 'compare-versions';
import GithubUpdateDialog from '$lib/components/GithubUpdateDialog.svelte'; import GithubUpdateDialog from '$lib/components/GithubUpdateDialog.svelte';
import InfoDialog from '$lib/components/InfoDialog.svelte'; import InfoDialog from '$lib/components/InfoDialog.svelte';
import { api } from '$lib/api'; import { api } from '$lib/api';
import { useFeatureFlags } from '$lib/stores'; import { useFeatureFlags } from '$lib/stores';
import { Error, Cancel, Check, CloudDown, Github, Prerelease } from '$lib/components/icons'; import { Error, Cancel, Check, CloudDown, Github, Prerelease } from '$lib/components/icons';
const features = useFeatureFlags(); const features = useFeatureFlags();
async function getGithubAPI() { async function getGithubAPI() {
const headers = { const headers = {
accept: 'application/vnd.github+json', accept: 'application/vnd.github+json',
'X-GitHub-Api-Version': '2022-11-28' 'X-GitHub-Api-Version': '2022-11-28'
} };
const result = await api.get(`https://api.github.com/repos/${$page.data.github}/releases`, {headers}) const result = await api.get(`https://api.github.com/repos/${$page.data.github}/releases`, {
headers
});
if (result.isErr()) { if (result.isErr()) {
console.error('Error:', result.inner); console.error('Error:', result.inner);
return return;
} }
return result.inner as any; return result.inner as any;
} }
async function postGithubDownload(url: string) { async function postGithubDownload(url: string) {
const result = await api.post('/api/downloadUpdate', { download_url: url }) const result = await api.post('/api/firmware/download', { download_url: url });
if (result.isErr()) { if (result.isErr()) {
console.error('Error:', result.inner); console.error('Error:', result.inner);
return return;
} }
} }
function confirmGithubUpdate(assets: any) { function confirmGithubUpdate(assets: any) {
let url = ''; let url = '';
// iterate over assets and find the correct one // iterate over assets and find the correct one
for (let i = 0; i < assets.length; i++) { for (let i = 0; i < assets.length; i++) {
// check if the asset is of type *.bin // check if the asset is of type *.bin
if ( if (
assets[i].name.includes('.bin') && assets[i].name.includes('.bin') &&
assets[i].name.includes($features.firmware_built_target) assets[i].name.includes($features.firmware_built_target)
) { ) {
url = assets[i].browser_download_url; url = assets[i].browser_download_url;
} }
} }
if (url === '') { if (url === '') {
// if no asset was found, use the first one // if no asset was found, use the first one
openModal(InfoDialog, { openModal(InfoDialog, {
title: 'No matching firmware found', title: 'No matching firmware found',
message: message:
'No matching firmware was found for the current device. Upload the firmware manually or build from sources.', 'No matching firmware was found for the current device. Upload the firmware manually or build from sources.',
dismiss: { label: 'OK', icon: Check }, dismiss: { label: 'OK', icon: Check },
onDismiss: () => closeModal() onDismiss: () => closeModal()
}); });
return; return;
} }
openModal(ConfirmDialog, { openModal(ConfirmDialog, {
title: 'Confirm flashing new firmware to the device', title: 'Confirm flashing new firmware to the device',
message: 'Are you sure you want to overwrite the existing firmware with a new one?', message: 'Are you sure you want to overwrite the existing firmware with a new one?',
labels: { labels: {
cancel: { label: 'Abort', icon: Cancel }, cancel: { label: 'Abort', icon: Cancel },
confirm: { label: 'Update', icon: CloudDown } confirm: { label: 'Update', icon: CloudDown }
}, },
onConfirm: () => { onConfirm: () => {
postGithubDownload(url); postGithubDownload(url);
openModal(GithubUpdateDialog, { openModal(GithubUpdateDialog, {
onConfirm: () => closeAllModals() onConfirm: () => closeAllModals()
}); });
} }
}); });
} }
</script> </script>
<SettingsCard collapsible={false}> <SettingsCard collapsible={false}>
<Github slot="icon" class="lex-shrink-0 mr-2 h-6 w-6 self-end rounded-full" /> <Github slot="icon" class="lex-shrink-0 mr-2 h-6 w-6 self-end rounded-full" />
<span slot="title">Github Firmware Manager</span> <span slot="title">Github Firmware Manager</span>
{#await getGithubAPI()} {#await getGithubAPI()}
<Spinner /> <Spinner />
{:then githubReleases} {:then githubReleases}
<div class="relative w-full overflow-visible"> <div class="relative w-full overflow-visible">
<div class="overflow-x-auto" transition:slide|local={{ duration: 300, easing: cubicOut }}> <div
<table class="table w-full table-auto"> class="overflow-x-auto"
<thead> transition:slide|local={{ duration: 300, easing: cubicOut }}
<tr class="font-bold"> >
<th align="left">Release</th> <table class="table w-full table-auto">
<th align="center" class="hidden sm:block">Release Date</th> <thead>
<th align="center">Experimental</th> <tr class="font-bold">
<th align="center">Install</th> <th align="left">Release</th>
</tr> <th align="center" class="hidden sm:block">Release Date</th>
</thead> <th align="center">Experimental</th>
<tbody> <th align="center">Install</th>
{#each githubReleases as release} </tr>
<tr </thead>
class={compareVersions($features.firmware_version, release.tag_name) === 0 <tbody>
? 'bg-primary text-primary-content' {#each githubReleases as release}
: 'bg-base-100 h-14'} <tr
> class={(
<td align="left" class="text-base font-semibold"> compareVersions(
<a $features.firmware_version,
href={release.html_url} release.tag_name
class="link link-hover" ) === 0
target="_blank" ) ?
rel="noopener noreferrer">{release.name}</a 'bg-primary text-primary-content'
></td : 'bg-base-100 h-14'}
> >
<td align="center" class="hidden min-h-full align-middle sm:block"> <td align="left" class="text-base font-semibold">
<div class="my-2"> <a
{new Intl.DateTimeFormat('en-GB', { href={release.html_url}
dateStyle: 'medium' class="link link-hover"
}).format(new Date(release.published_at))} target="_blank"
</div> rel="noopener noreferrer">{release.name}</a
</td> ></td
<td align="center"> >
{#if release.prerelease} <td align="center" class="hidden min-h-full align-middle sm:block">
<Prerelease class="text-accent h-5 w-5" /> <div class="my-2">
{/if} {new Intl.DateTimeFormat('en-GB', {
</td> dateStyle: 'medium'
<td align="center"> }).format(new Date(release.published_at))}
{#if compareVersions($features.firmware_version, release.tag_name) != 0} </div>
<button </td>
class="btn btn-ghost btn-circle btn-sm" <td align="center">
on:click={() => { {#if release.prerelease}
confirmGithubUpdate(release.assets); <Prerelease class="text-accent h-5 w-5" />
}} {/if}
> </td>
<CloudDown class="text-secondary h-6 w-6" /> <td align="center">
</button> {#if compareVersions($features.firmware_version, release.tag_name) != 0}
{/if} <button
</td> class="btn btn-ghost btn-circle btn-sm"
</tr> on:click={() => {
{/each} confirmGithubUpdate(release.assets);
</tbody> }}
</table> >
</div> <CloudDown class="text-secondary h-6 w-6" />
</div> </button>
{:catch error} {/if}
<div class="alert alert-error shadow-lg"> </td>
<Error class="h-6 w-6 flex-shrink-0" /> </tr>
<span>Please connect to a network with internet access to perform a firmware update.</span> {/each}
</div> </tbody>
{/await} </table>
</div>
</div>
{:catch error}
<div class="alert alert-error shadow-lg">
<Error class="h-6 w-6 flex-shrink-0" />
<span
>Please connect to a network with internet access to perform a firmware update.</span
>
</div>
{/await}
</SettingsCard> </SettingsCard>
@@ -1,54 +1,53 @@
<script lang="ts"> <script lang="ts">
import { openModal, closeModal } from 'svelte-modals'; import { openModal, closeModal } from 'svelte-modals';
import ConfirmDialog from '$lib/components/ConfirmDialog.svelte'; import ConfirmDialog from '$lib/components/ConfirmDialog.svelte';
import SettingsCard from '$lib/components/SettingsCard.svelte'; import SettingsCard from '$lib/components/SettingsCard.svelte';
import { api } from '$lib/api'; import { api } from '$lib/api';
import { Cancel, OTA, Warning } from '$lib/components/icons'; import { Cancel, OTA, Warning } from '$lib/components/icons';
let files: FileList; let files: FileList;
async function uploadBIN() { async function uploadBIN() {
const formData = new FormData(); const formData = new FormData();
formData.append('file', files[0]); formData.append('file', files[0]);
const result = await api.post('/api/uploadFirmware', formData) const result = await api.post('/api/firmware', formData);
if (result.isErr()) if (result.isErr()) console.error('Error:', result.inner);
console.error('Error:', result.inner); }
}
function confirmBinUpload() { function confirmBinUpload() {
openModal(ConfirmDialog, { openModal(ConfirmDialog, {
title: 'Confirm Flashing the Device', title: 'Confirm Flashing the Device',
message: 'Are you sure you want to overwrite the existing firmware with a new one?', message: 'Are you sure you want to overwrite the existing firmware with a new one?',
labels: { labels: {
cancel: { label: 'Abort', icon: Cancel }, cancel: { label: 'Abort', icon: Cancel },
confirm: { label: 'Upload', icon: OTA } confirm: { label: 'Upload', icon: OTA }
}, },
onConfirm: () => { onConfirm: () => {
closeModal(); closeModal();
uploadBIN(); uploadBIN();
} }
}); });
} }
</script> </script>
<SettingsCard collapsible={false}> <SettingsCard collapsible={false}>
<OTA slot="icon" class="lex-shrink-0 mr-2 h-6 w-6 self-end rounded-full" /> <OTA slot="icon" class="lex-shrink-0 mr-2 h-6 w-6 self-end rounded-full" />
<span slot="title">Upload Firmware</span> <span slot="title">Upload Firmware</span>
<div class="alert alert-warning shadow-lg"> <div class="alert alert-warning shadow-lg">
<Warning class="h-6 w-6 flex-shrink-0" /> <Warning class="h-6 w-6 flex-shrink-0" />
<span <span
>Uploading a new firmware (.bin) file will replace the existing firmware. You may upload a >Uploading a new firmware (.bin) file will replace the existing firmware. You may upload
(.md5) file first to verify the uploaded firmware.</span a (.md5) file first to verify the uploaded firmware.</span
> >
</div> </div>
<input <input
type="file" type="file"
id="binFile" id="binFile"
class="file-input file-input-bordered file-input-secondary mt-4 w-full" class="file-input file-input-bordered file-input-secondary mt-4 w-full"
bind:files bind:files
accept=".bin,.md5" accept=".bin,.md5"
on:change={confirmBinUpload} on:change={confirmBinUpload}
/> />
</SettingsCard> </SettingsCard>
@@ -1,42 +0,0 @@
#pragma once
/**
* ESP32 SvelteKit
*
* A simple, secure and extensible framework for IoT projects for ESP32 platforms
* with responsive Sveltekit front-end built with TailwindCSS and DaisyUI.
* https://github.com/theelims/ESP32-sveltekit
*
* Copyright (C) 2018 - 2023 rjwats
* Copyright (C) 2023 theelims
*
* All Rights Reserved. This software may be modified and distributed under
* the terms of the LGPL v3 license. See the LICENSE file for details.
**/
#include <Arduino.h>
#include <WiFi.h>
#include <ArduinoJson.h>
#include <event_socket.h>
#include <PsychicHttp.h>
#include <HTTPClient.h>
#include <HTTPUpdate.h>
#include <task_manager.h>
// #include <SSLCertBundle.h>
#define GITHUB_FIRMWARE_PATH "/api/downloadUpdate"
#define EVENT_DOWNLOAD_OTA "otastatus"
#define OTA_TASK_STACK_SIZE 9216
class DownloadFirmwareService {
public:
DownloadFirmwareService(PsychicHttpServer *server);
void begin();
private:
PsychicHttpServer *_server;
esp_err_t downloadUpdate(PsychicRequest *request, JsonVariant &json);
};
+16 -8
View File
@@ -15,11 +15,8 @@
#include <ESP32SvelteKit.h> #include <ESP32SvelteKit.h>
ESP32SvelteKit::ESP32SvelteKit(PsychicHttpServer *server, unsigned int numberEndpoints) ESP32SvelteKit::ESP32SvelteKit(PsychicHttpServer *server)
: _numberEndpoints(numberEndpoints), :
#if FT_ENABLED(USE_UPLOAD_FIRMWARE)
_uploadFirmwareService(server),
#endif
#if FT_ENABLED(USE_DOWNLOAD_FIRMWARE) #if FT_ENABLED(USE_DOWNLOAD_FIRMWARE)
_downloadFirmwareService(server), _downloadFirmwareService(server),
#endif #endif
@@ -43,9 +40,6 @@ void ESP32SvelteKit::begin() {
g_taskManager.begin(); g_taskManager.begin();
_wifiService.begin(); _wifiService.begin();
_server->config.max_uri_handlers = _numberEndpoints;
_server->listen(80);
setupServer(); setupServer();
startServices(); startServices();
@@ -57,6 +51,10 @@ void ESP32SvelteKit::begin() {
} }
void ESP32SvelteKit::setupServer() { void ESP32SvelteKit::setupServer() {
_server->config.max_uri_handlers = _numberEndpoints;
_server->maxUploadSize = _maxFileUpload;
_server->listen(_port);
// wifi // wifi
_server->on("/api/wifi/scan", HTTP_GET, _wifiService.handleScan); _server->on("/api/wifi/scan", HTTP_GET, _wifiService.handleScan);
_server->on("/api/wifi/networks", HTTP_GET, _server->on("/api/wifi/networks", HTTP_GET,
@@ -132,6 +130,16 @@ void ESP32SvelteKit::setupServer() {
_server->on("/api/ws/events", socket.getHandler()); _server->on("/api/ws/events", socket.getHandler());
_server->on("/api/features", feature_service::getFeatures); _server->on("/api/features", feature_service::getFeatures);
#if FT_ENABLED(USE_UPLOAD_FIRMWARE)
_server->on("/api/firmware", HTTP_POST, _uploadFirmwareService.getHandler());
#endif
#if FT_ENABLED(USE_DOWNLOAD_FIRMWARE)
_server->on("/api/firmware/download", HTTP_POST, [this](PsychicRequest *r, JsonVariant &json) {
return _downloadFirmwareService.handleDownloadUpdate(r, json);
});
#endif
#ifdef EMBED_WWW #ifdef EMBED_WWW
ESP_LOGV("ESP32SvelteKit", "Registering routes from PROGMEM static resources"); ESP_LOGV("ESP32SvelteKit", "Registering routes from PROGMEM static resources");
WWWData::registerRoutes([&](const String &uri, const String &contentType, const uint8_t *content, size_t len) { WWWData::registerRoutes([&](const String &uri, const String &contentType, const uint8_t *content, size_t len) {
+8 -5
View File
@@ -21,7 +21,8 @@
#include <analytics_service.h> #include <analytics_service.h>
#include <BatteryService.h> #include <BatteryService.h>
#include <filesystem.h> #include <filesystem.h>
#include <DownloadFirmwareService.h> #include <firmware_download_service.h>
#include <firmware_upload_service.h>
#include <Peripherals.h> #include <Peripherals.h>
#include <ServoController.h> #include <ServoController.h>
#include <ESPmDNS.h> #include <ESPmDNS.h>
@@ -34,7 +35,6 @@
#include <CameraSettingsService.h> #include <CameraSettingsService.h>
#include <PsychicHttp.h> #include <PsychicHttp.h>
#include <task_manager.h> #include <task_manager.h>
#include <UploadFirmwareService.h>
#include <WiFi.h> #include <WiFi.h>
#include <wifi_service.h> #include <wifi_service.h>
#include <ap_service.h> #include <ap_service.h>
@@ -62,7 +62,7 @@
class ESP32SvelteKit { class ESP32SvelteKit {
public: public:
ESP32SvelteKit(PsychicHttpServer *server, unsigned int numberEndpoints = 115); ESP32SvelteKit(PsychicHttpServer *server);
void begin(); void begin();
@@ -99,7 +99,6 @@ class ESP32SvelteKit {
private: private:
PsychicHttpServer *_server; PsychicHttpServer *_server;
unsigned int _numberEndpoints;
WiFiService _wifiService; WiFiService _wifiService;
APService _apService; APService _apService;
EventSocket _socket; EventSocket _socket;
@@ -107,7 +106,7 @@ class ESP32SvelteKit {
NTPService _ntpService; NTPService _ntpService;
#endif #endif
#if FT_ENABLED(USE_UPLOAD_FIRMWARE) #if FT_ENABLED(USE_UPLOAD_FIRMWARE)
UploadFirmwareService _uploadFirmwareService; FirmwareUploadService _uploadFirmwareService;
#endif #endif
#if FT_ENABLED(USE_DOWNLOAD_FIRMWARE) #if FT_ENABLED(USE_DOWNLOAD_FIRMWARE)
DownloadFirmwareService _downloadFirmwareService; DownloadFirmwareService _downloadFirmwareService;
@@ -133,6 +132,10 @@ class ESP32SvelteKit {
String _appName = APP_NAME; String _appName = APP_NAME;
const u_int16_t _numberEndpoints = 115;
const u_int32_t _maxFileUpload = 2300000; // 2.3 MB
const uint16_t _port = 80;
protected: protected:
static void _loopImpl(void *_this) { static_cast<ESP32SvelteKit *>(_this)->loop(); } static void _loopImpl(void *_this) { static_cast<ESP32SvelteKit *>(_this)->loop(); }
void setupServer(); void setupServer();
@@ -1,46 +0,0 @@
#ifndef UploadFirmwareService_h
#define UploadFirmwareService_h
/**
* ESP32 SvelteKit
*
* A simple, secure and extensible framework for IoT projects for ESP32 platforms
* with responsive Sveltekit front-end built with TailwindCSS and DaisyUI.
* https://github.com/theelims/ESP32-sveltekit
*
* Copyright (C) 2018 - 2023 rjwats
* Copyright (C) 2023 theelims
*
* All Rights Reserved. This software may be modified and distributed under
* the terms of the LGPL v3 license. See the LICENSE file for details.
**/
#include <Arduino.h>
#include <Update.h>
#include <WiFi.h>
#include <PsychicHttp.h>
#include <system_service.h>
#define UPLOAD_FIRMWARE_PATH "/api/uploadFirmware"
enum FileType { ft_none = 0, ft_firmware = 1, ft_md5 = 2 };
class UploadFirmwareService {
public:
UploadFirmwareService(PsychicHttpServer *server);
void begin();
private:
PsychicHttpServer *_server;
esp_err_t handleUpload(PsychicRequest *request, const String &filename, uint64_t index, uint8_t *data, size_t len,
bool final);
esp_err_t uploadComplete(PsychicRequest *request);
esp_err_t handleError(PsychicRequest *request, int code);
esp_err_t handleEarlyDisconnect();
};
#endif // end UploadFirmwareService_h
@@ -11,7 +11,7 @@
* the terms of the LGPL v3 license. See the LICENSE file for details. * the terms of the LGPL v3 license. See the LICENSE file for details.
**/ **/
#include <DownloadFirmwareService.h> #include <firmware_download_service.h>
extern const uint8_t rootca_crt_bundle_start[] asm("_binary_src_certs_x509_crt_bundle_bin_start"); extern const uint8_t rootca_crt_bundle_start[] asm("_binary_src_certs_x509_crt_bundle_bin_start");
@@ -89,14 +89,7 @@ void updateTask(void *param) {
vTaskDelete(NULL); vTaskDelete(NULL);
} }
DownloadFirmwareService::DownloadFirmwareService(PsychicHttpServer *server) : _server(server) {} DownloadFirmwareService::DownloadFirmwareService() {}
void DownloadFirmwareService::begin() {
_server->on(GITHUB_FIRMWARE_PATH, HTTP_POST,
[this](PsychicRequest *request, JsonVariant &json) { return downloadUpdate(request, json); });
ESP_LOGV("DownloadFirmwareService", "Registered POST endpoint: %s", GITHUB_FIRMWARE_PATH);
}
esp_err_t DownloadFirmwareService::downloadUpdate(PsychicRequest *request, JsonVariant &json) { esp_err_t DownloadFirmwareService::downloadUpdate(PsychicRequest *request, JsonVariant &json) {
if (!json.is<JsonObject>()) { if (!json.is<JsonObject>()) {
@@ -0,0 +1,24 @@
#pragma once
#include <Arduino.h>
#include <WiFi.h>
#include <ArduinoJson.h>
#include <event_socket.h>
#include <PsychicHttp.h>
#include <HTTPClient.h>
#include <HTTPUpdate.h>
#include <task_manager.h>
#define EVENT_DOWNLOAD_OTA "otastatus"
#define OTA_TASK_STACK_SIZE 9216
class DownloadFirmwareService {
public:
DownloadFirmwareService();
esp_err_t downloadUpdate(PsychicRequest *request, JsonVariant &json);
private:
};
@@ -1,45 +1,25 @@
/** #include <firmware_upload_service.h>
* ESP32 SvelteKit
*
* A simple, secure and extensible framework for IoT projects for ESP32 platforms
* with responsive Sveltekit front-end built with TailwindCSS and DaisyUI.
* https://github.com/theelims/ESP32-sveltekit
*
* Copyright (C) 2018 - 2023 rjwats
* Copyright (C) 2023 theelims
*
* All Rights Reserved. This software may be modified and distributed under
* the terms of the LGPL v3 license. See the LICENSE file for details.
**/
#include <UploadFirmwareService.h>
#include <esp_ota_ops.h>
#include <esp_app_format.h> #include <esp_app_format.h>
#include <esp_ota_ops.h>
static const char *TAG = "FirmwareUploadService";
using namespace std::placeholders; using namespace std::placeholders;
static char md5[33] = "\0"; static char md5[33] = "\0";
static size_t fsize = 0;
static FileType fileType = ft_none; static FileType fileType = ft_none;
UploadFirmwareService::UploadFirmwareService(PsychicHttpServer *server) : _server(server) {} FirmwareUploadService::FirmwareUploadService() {}
void UploadFirmwareService::begin() { void FirmwareUploadService::begin() {
_server->maxUploadSize = 2300000; // 2.3 MB uploadHandler.onUpload(std::bind(&FirmwareUploadService::handleUpload, this, _1, _2, _3, _4, _5, _6));
uploadHandler.onRequest(std::bind(&FirmwareUploadService::uploadComplete, this, _1));
PsychicUploadHandler *uploadHandler = new PsychicUploadHandler(); uploadHandler.onClose(std::bind(&FirmwareUploadService::handleEarlyDisconnect, this));
uploadHandler->onUpload(std::bind(&UploadFirmwareService::handleUpload, this, _1, _2, _3, _4, _5, _6));
uploadHandler->onRequest(
std::bind(&UploadFirmwareService::uploadComplete, this, _1)); // gets called after upload has been handled
uploadHandler->onClose(
std::bind(&UploadFirmwareService::handleEarlyDisconnect, this)); // gets called if client disconnects
_server->on(UPLOAD_FIRMWARE_PATH, HTTP_POST, uploadHandler);
ESP_LOGV("UploadFirmwareService", "Registered POST endpoint: %s", UPLOAD_FIRMWARE_PATH);
} }
esp_err_t UploadFirmwareService::handleUpload(PsychicRequest *request, const String &filename, uint64_t index, esp_err_t FirmwareUploadService::handleUpload(PsychicRequest *request, const String &filename, uint64_t index,
uint8_t *data, size_t len, bool final) { uint8_t *data, size_t len, bool final) {
// at init // at init
if (!index) { if (!index) {
@@ -47,7 +27,7 @@ esp_err_t UploadFirmwareService::handleUpload(PsychicRequest *request, const Str
std::string fname(filename.c_str()); std::string fname(filename.c_str());
auto position = fname.find_last_of("."); auto position = fname.find_last_of(".");
std::string extension = fname.substr(position + 1); std::string extension = fname.substr(position + 1);
size_t fsize = request->contentLength(); fsize = request->contentLength();
fileType = ft_none; fileType = ft_none;
if ((extension == "bin") && (fsize > 1000000)) { if ((extension == "bin") && (fsize > 1000000)) {
@@ -61,11 +41,13 @@ esp_err_t UploadFirmwareService::handleUpload(PsychicRequest *request, const Str
return ESP_OK; return ESP_OK;
} else { } else {
md5[0] = '\0'; md5[0] = '\0';
return handleError(request, 406); // Not Acceptable - unsupported file type return handleError(request,
406); // Not Acceptable - unsupported file type
} }
if (fileType == ft_firmware) { if (fileType == ft_firmware) {
// Check firmware header, 0xE9 magic offset 0 indicates esp bin, chip offset 12: esp32:0, S2:2, C3:5 // Check firmware header, 0xE9 magic offset 0 indicates esp bin, chip
// offset 12: esp32:0, S2:2, C3:5
#if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4 #if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4
if (len > 12 && (data[0] != 0xE9 || data[12] != 0)) { if (len > 12 && (data[0] != 0xE9 || data[12] != 0)) {
return handleError(request, 503); // service unavailable return handleError(request, 503); // service unavailable
@@ -85,12 +67,15 @@ esp_err_t UploadFirmwareService::handleUpload(PsychicRequest *request, const Str
#endif #endif
// it's firmware - initialize the ArduinoOTA updater // it's firmware - initialize the ArduinoOTA updater
if (Update.begin(fsize - sizeof(esp_image_header_t))) { if (Update.begin(fsize - sizeof(esp_image_header_t))) {
ESP_LOGI(TAG, "Starting update");
if (strlen(md5) == 32) { if (strlen(md5) == 32) {
Update.setMD5(md5); Update.setMD5(md5);
md5[0] = '\0'; md5[0] = '\0';
ESP_LOGI(TAG, "Setting MD5 hash");
} }
} else { } else {
return handleError(request, 507); // failed to begin, send an error response Insufficient Storage return handleError(request, 507); // failed to begin, send an error
// response Insufficient Storage
} }
} }
} }
@@ -99,10 +84,19 @@ esp_err_t UploadFirmwareService::handleUpload(PsychicRequest *request, const Str
if (!request->_tempObject) { if (!request->_tempObject) {
if (Update.write(data, len) != len) { if (Update.write(data, len) != len) {
handleError(request, 500); handleError(request, 500);
} else {
char buffer[64];
snprintf(buffer, sizeof(buffer), "{\"status\":\"progress\",\"progress\":%.1f}",
(float)Update.progress() / (float)fsize * 100.f);
socket.emit("otastatus", buffer);
delay(20);
} }
if (final) { if (final) {
if (!Update.end(true)) { if (!Update.end(true)) {
handleError(request, 500); handleError(request, 500);
} else {
socket.emit("otastatus", "{\"status\":\"finished\",\"progress\":100}");
ESP_LOGI(TAG, "Finish writing update");
} }
} }
} }
@@ -110,7 +104,7 @@ esp_err_t UploadFirmwareService::handleUpload(PsychicRequest *request, const Str
return ESP_OK; return ESP_OK;
} }
esp_err_t UploadFirmwareService::uploadComplete(PsychicRequest *request) { esp_err_t FirmwareUploadService::uploadComplete(PsychicRequest *request) {
// if we completed uploading a md5 file create a JSON response // if we completed uploading a md5 file create a JSON response
if (fileType == ft_md5) { if (fileType == ft_md5) {
if (strlen(md5) == 32) { if (strlen(md5) == 32) {
@@ -124,7 +118,7 @@ esp_err_t UploadFirmwareService::uploadComplete(PsychicRequest *request) {
// if no error, send the success response // if no error, send the success response
if (!request->_tempObject) { if (!request->_tempObject) {
request->reply(200); ESP_LOGI(TAG, "Finish updating");
system_service::restart(); system_service::restart();
return ESP_OK; return ESP_OK;
} }
@@ -139,18 +133,22 @@ esp_err_t UploadFirmwareService::uploadComplete(PsychicRequest *request) {
return ESP_OK; return ESP_OK;
} }
esp_err_t UploadFirmwareService::handleError(PsychicRequest *request, int code) { esp_err_t FirmwareUploadService::handleError(PsychicRequest *request, int code) {
char buffer[64];
snprintf(buffer, sizeof(buffer), "{\"status\":\"error\",\"error\":\"%d\"}", Update.getError());
socket.emit("otastatus", buffer);
// if we have had an error already, do nothing // if we have had an error already, do nothing
if (request->_tempObject) { if (request->_tempObject) {
return ESP_OK; return ESP_OK;
} }
// send the error code to the client and record the error code in the temp object // send the error code to the client and record the error code in the temp
// object
request->_tempObject = new int(code); request->_tempObject = new int(code);
return request->reply(code); return request->reply(code);
} }
esp_err_t UploadFirmwareService::handleEarlyDisconnect() { esp_err_t FirmwareUploadService::handleEarlyDisconnect() {
// if updated has not ended on connection close, abort it // if updated has not ended on connection close, abort it
if (!Update.end(true)) { if (!Update.end(true)) {
Update.printError(Serial); Update.printError(Serial);
@@ -158,4 +156,4 @@ esp_err_t UploadFirmwareService::handleEarlyDisconnect() {
return ESP_OK; return ESP_OK;
} }
return ESP_OK; return ESP_OK;
} }
@@ -0,0 +1,32 @@
#ifndef FirmwareUploadService_h
#define FirmwareUploadService_h
#include <Arduino.h>
#include <Update.h>
#include <WiFi.h>
#include <PsychicHttp.h>
#include <system_service.h>
#include <event_socket.h>
enum FileType { ft_none = 0, ft_firmware = 1, ft_md5 = 2 };
class FirmwareUploadService {
public:
FirmwareUploadService();
void begin();
PsychicUploadHandler *getHandler() { return &uploadHandler; }
private:
PsychicUploadHandler uploadHandler;
esp_err_t handleUpload(PsychicRequest *request, const String &filename, uint64_t index, uint8_t *data, size_t len,
bool final);
esp_err_t uploadComplete(PsychicRequest *request);
esp_err_t handleError(PsychicRequest *request, int code);
esp_err_t handleEarlyDisconnect();
};
#endif // end FirmwareUploadService_h
+1 -1
View File
@@ -5,7 +5,7 @@
DRAM_ATTR PsychicHttpServer server; DRAM_ATTR PsychicHttpServer server;
DRAM_ATTR ESP32SvelteKit spot(&server, 130); DRAM_ATTR ESP32SvelteKit spot(&server);
void setup() { void setup() {
Serial.begin(SERIAL_BAUD_RATE); Serial.begin(SERIAL_BAUD_RATE);