mirror of
https://github.com/CharaChorder/DeviceManager.git
synced 2025-12-11 05:16:16 +00:00
ota update flow
This commit is contained in:
@@ -440,46 +440,80 @@ export class CharaDevice {
|
||||
return Number(await this.send(1, "RAM").then(([bytes]) => bytes));
|
||||
}
|
||||
|
||||
async updateFirmware(file: File): Promise<void> {
|
||||
const size = file.size;
|
||||
// use separate serial connection
|
||||
await this.port.open({ baudRate: this.baudRate });
|
||||
const decoderStream = new TextDecoderStream();
|
||||
this.port.readable!.pipeTo(decoderStream.writable);
|
||||
|
||||
const reader = decoderStream
|
||||
.readable!.pipeThrough(new TransformStream(new LineBreakTransformer()))
|
||||
.getReader();
|
||||
serialLog.update((it) => {
|
||||
it.push({
|
||||
type: "system",
|
||||
value: "Starting firmware update",
|
||||
});
|
||||
return it;
|
||||
});
|
||||
|
||||
const writer = this.port.writable!.getWriter();
|
||||
try {
|
||||
await writer.write(new TextEncoder().encode(`RST OTA\r\n`));
|
||||
} finally {
|
||||
writer.releaseLock();
|
||||
async updateFirmware(file: File | Blob): Promise<void> {
|
||||
while (this.lock) {
|
||||
await this.lock;
|
||||
}
|
||||
|
||||
console.log((await reader.read()).value);
|
||||
|
||||
await file.stream().pipeTo(this.port.writable!);
|
||||
|
||||
console.log((await reader.read()).value);
|
||||
|
||||
await reader.cancel();
|
||||
reader.releaseLock();
|
||||
await this.port.close();
|
||||
serialLog.update((it) => {
|
||||
it.push({
|
||||
type: "system",
|
||||
value: "Success?",
|
||||
});
|
||||
return it;
|
||||
let resolveLock: (result: true) => void;
|
||||
this.lock = new Promise<true>((resolve) => {
|
||||
resolveLock = resolve;
|
||||
});
|
||||
try {
|
||||
if (this.suspendDebounceId) {
|
||||
clearTimeout(this.suspendDebounceId);
|
||||
} else {
|
||||
await this.wake();
|
||||
}
|
||||
|
||||
serialLog.update((it) => {
|
||||
it.push({
|
||||
type: "system",
|
||||
value: "OTA Update",
|
||||
});
|
||||
return it;
|
||||
});
|
||||
|
||||
const writer = this.port.writable!.getWriter();
|
||||
try {
|
||||
await writer.write(new TextEncoder().encode(`RST OTA\r\n`));
|
||||
serialLog.update((it) => {
|
||||
it.push({
|
||||
type: "input",
|
||||
value: "RST OTA",
|
||||
});
|
||||
return it;
|
||||
});
|
||||
} finally {
|
||||
writer.releaseLock();
|
||||
}
|
||||
|
||||
// Wait for the device to be ready
|
||||
const signal = await this.reader.read();
|
||||
serialLog.update((it) => {
|
||||
it.push({
|
||||
type: "output",
|
||||
value: signal.value!.trim(),
|
||||
});
|
||||
return it;
|
||||
});
|
||||
|
||||
await file.stream().pipeTo(this.port.writable!);
|
||||
|
||||
serialLog.update((it) => {
|
||||
it.push({
|
||||
type: "input",
|
||||
value: `...${file.size} bytes`,
|
||||
});
|
||||
return it;
|
||||
});
|
||||
|
||||
const result = (await this.reader.read()).value!.trim();
|
||||
serialLog.update((it) => {
|
||||
it.push({
|
||||
type: "output",
|
||||
value: result!,
|
||||
});
|
||||
return it;
|
||||
});
|
||||
|
||||
await this.suspend();
|
||||
|
||||
if (result !== "OTA OK") {
|
||||
throw new Error(result);
|
||||
}
|
||||
} finally {
|
||||
delete this.lock;
|
||||
resolveLock!(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
0
src/routes/(app)/ota-update/OTA.svelte
Normal file
0
src/routes/(app)/ota-update/OTA.svelte
Normal file
@@ -4,6 +4,29 @@
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
let working = $state(false);
|
||||
let success = $state(false);
|
||||
let error = $state<Error | undefined>(undefined);
|
||||
|
||||
async function update() {
|
||||
working = true;
|
||||
error = undefined;
|
||||
success = false;
|
||||
const port = $serialPort!;
|
||||
$serialPort = undefined;
|
||||
try {
|
||||
const file = await fetch(otaUrl!).then((it) => it.blob());
|
||||
|
||||
await port.updateFirmware(file);
|
||||
|
||||
success = true;
|
||||
} catch (e) {
|
||||
error = e as Error;
|
||||
} finally {
|
||||
working = false;
|
||||
}
|
||||
}
|
||||
|
||||
let currentDevice = $derived(
|
||||
$serialPort
|
||||
? `${$serialPort.device.toLowerCase()}_${$serialPort.chipset.toLowerCase()}`
|
||||
@@ -78,7 +101,29 @@
|
||||
<section>
|
||||
<h3>OTA Upate</h3>
|
||||
{#if data.ota}
|
||||
<p>OTA update</p>
|
||||
<button
|
||||
class:working
|
||||
class:primary={!error}
|
||||
class:error={error || (!success && isCorrectDevice === false)}
|
||||
disabled={working || $serialPort === undefined || !isCorrectDevice}
|
||||
onclick={update}>Apply Update</button
|
||||
>
|
||||
{#if $serialPort}
|
||||
<div transition:slide>
|
||||
Your device is ready and compatible. Click the button to perform the
|
||||
update.
|
||||
</div>
|
||||
{:else if success}
|
||||
<div class="primary" transition:slide>Update successful</div>
|
||||
{:else if error}
|
||||
<div class="error" transition:slide>{error.message}</div>
|
||||
{:else if working}
|
||||
<div class="primary" transition:slide>Updating your device...</div>
|
||||
{:else}
|
||||
<div class="primary" transition:slide>
|
||||
Connect your device to continue
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<em>There are no OTA files for this device.</em>
|
||||
{/if}
|
||||
@@ -101,6 +146,7 @@
|
||||
<h4>Via Serial</h4>
|
||||
<p>WIP</p>
|
||||
</section>
|
||||
ading 0 Chordmaps.
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@@ -109,6 +155,84 @@
|
||||
transition: color 200ms ease;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: var(--md-sys-color-error);
|
||||
}
|
||||
|
||||
.primary {
|
||||
color: var(--md-sys-color-primary);
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
0% {
|
||||
transform: rotate(120deg);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
20% {
|
||||
transform: rotate(120deg);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
60% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(270deg);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
height: 42px;
|
||||
|
||||
border: 2px solid currentcolor;
|
||||
border-radius: 8px;
|
||||
|
||||
outline: 2px dashed currentcolor;
|
||||
outline-offset: 4px;
|
||||
|
||||
background: var(--md-sys-color-background);
|
||||
transition:
|
||||
border 200ms ease,
|
||||
color 200ms ease;
|
||||
|
||||
margin: 6px;
|
||||
margin-block: 16px;
|
||||
|
||||
&.primary {
|
||||
color: var(--md-sys-color-primary);
|
||||
background: none;
|
||||
}
|
||||
|
||||
&.working {
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
&.working::before {
|
||||
z-index: -1;
|
||||
position: absolute;
|
||||
background: var(--md-sys-color-background);
|
||||
width: calc(100% - 4px);
|
||||
height: calc(100% - 4px);
|
||||
border-radius: 8px;
|
||||
content: "";
|
||||
}
|
||||
|
||||
&.working::after {
|
||||
z-index: -2;
|
||||
position: absolute;
|
||||
content: "";
|
||||
background: var(--md-sys-color-primary);
|
||||
animation: rotate 1s ease-out forwards infinite;
|
||||
height: 30%;
|
||||
width: 120%;
|
||||
}
|
||||
}
|
||||
|
||||
hr {
|
||||
color: var(--md-sys-color-outline);
|
||||
margin-block: 3em;
|
||||
|
||||
Reference in New Issue
Block a user