📦 Update firmware service
This commit is contained in:
@@ -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);
|
|
||||||
};
|
|
||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
|
|
||||||
+2
-9
@@ -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:
|
||||||
|
};
|
||||||
+39
-41
@@ -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
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user