mirror of
https://github.com/MultiMote/niimblue
synced 2026-01-19 19:37:11 +03:00
Firmware flasher
* More compact printer menu
This commit is contained in:
@@ -18,7 +18,7 @@
|
||||
"@byteowls/capacitor-filesharer": "^6.0.0",
|
||||
"@capacitor/core": "^6.1.2",
|
||||
"@fontsource-variable/noto-sans": "^5.0.5",
|
||||
"@mmote/niimbluelib": "0.0.1-alpha.21",
|
||||
"@mmote/niimbluelib": "0.0.1-alpha.22",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"bootstrap": "5.3.3",
|
||||
"d3-dsv": "^3.0.1",
|
||||
|
||||
@@ -2,6 +2,7 @@ import "./style.scss";
|
||||
import "@popperjs/core";
|
||||
import "toastify-js/src/toastify.css";
|
||||
import "bootstrap/js/dist/dropdown";
|
||||
import "bootstrap/js/dist/collapse";
|
||||
import App from "./App.svelte";
|
||||
import { config as FabricConfig } from "fabric";
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
FirmwareProgressEvent,
|
||||
NiimbotCapacitorBleClient,
|
||||
SoundSettingsItemType,
|
||||
Utils,
|
||||
@@ -24,10 +25,13 @@
|
||||
import { onMount } from "svelte";
|
||||
import { LocalStoragePersistence } from "../utils/persistence";
|
||||
import type { MaterialIcon } from "material-icons";
|
||||
import { FileUtils } from "../utils/file_utils";
|
||||
|
||||
let connectionType: ConnectionType = "bluetooth";
|
||||
let rfidInfo: RfidInfo | undefined = undefined;
|
||||
let featureSupport: AvailableTransports = { webBluetooth: false, webSerial: false, capacitorBle: false };
|
||||
let fwVersion: string = "";
|
||||
let fwProgress: string = "";
|
||||
|
||||
const onConnectClicked = async () => {
|
||||
initClient(connectionType);
|
||||
@@ -76,6 +80,34 @@
|
||||
await $printerClient.abstraction.printerReset();
|
||||
};
|
||||
|
||||
const upgradeFw = async () => {
|
||||
if (!confirm("Flashing wrong firmware can make your printer dead. Are you sure?")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await FileUtils.pickAndReadBinaryFile("bin");
|
||||
|
||||
const listener = (e: FirmwareProgressEvent) => {
|
||||
fwProgress = `${e.currentChunk}/${e.totalChunks}`;
|
||||
};
|
||||
|
||||
$printerClient.stopHeartbeat();
|
||||
|
||||
try {
|
||||
$printerClient.on("firmwareprogress", listener);
|
||||
fwProgress = "...";
|
||||
await $printerClient.abstraction.firmwareUpgrade(data, fwVersion);
|
||||
$printerClient.off("firmwareprogress", listener);
|
||||
await $printerClient.disconnect();
|
||||
|
||||
Toasts.message("Flashing is finished, the printer will shut down now");
|
||||
} catch (e) {
|
||||
Toasts.error(e);
|
||||
$printerClient.startHeartbeat();
|
||||
$printerClient.off("firmwareprogress", listener);
|
||||
}
|
||||
};
|
||||
|
||||
const switchConnectionType = (c: ConnectionType) => {
|
||||
LocalStoragePersistence.saveLastConnectionType(c);
|
||||
connectionType = c;
|
||||
@@ -133,8 +165,15 @@
|
||||
{/if}
|
||||
|
||||
{#if $printerMeta}
|
||||
<div>
|
||||
Model metadata:
|
||||
<button
|
||||
class="btn btn-sm btn-outline-secondary d-block w-100 mt-1"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#modelMeta">
|
||||
Model metadata <MdIcon icon="expand_more" />
|
||||
</button>
|
||||
|
||||
<div class="collapse" id="modelMeta">
|
||||
<ul>
|
||||
{#each Object.entries($printerMeta) as [k, v]}
|
||||
<li>{k}: <strong>{v ?? "-"}</strong></li>
|
||||
@@ -144,8 +183,15 @@
|
||||
{/if}
|
||||
|
||||
{#if rfidInfo}
|
||||
<div>
|
||||
Rfid info:
|
||||
<button
|
||||
class="btn btn-sm btn-outline-secondary d-block w-100 mt-1"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#rfidInfo">
|
||||
Rfid info <MdIcon icon="expand_more" />
|
||||
</button>
|
||||
|
||||
<div class="collapse" id="rfidInfo">
|
||||
<ul>
|
||||
{#each Object.entries(rfidInfo) as [k, v]}
|
||||
<li>{k}: <strong>{v ?? "-"}</strong></li>
|
||||
@@ -155,8 +201,15 @@
|
||||
{/if}
|
||||
|
||||
{#if $heartbeatData}
|
||||
<div>
|
||||
Heartbeat data:
|
||||
<button
|
||||
class="btn btn-sm btn-outline-secondary d-block w-100 mt-1"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#heartbeatData">
|
||||
Heartbeat data <MdIcon icon="expand_more" />
|
||||
</button>
|
||||
|
||||
<div class="collapse" id="heartbeatData">
|
||||
<ul>
|
||||
{#each Object.entries($heartbeatData) as [k, v]}
|
||||
<li>{k}: <strong>{v ?? "-"}</strong></li>
|
||||
@@ -165,15 +218,35 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div>Tests</div>
|
||||
<div class="input-group input-group-sm mt-1">
|
||||
{#if fwProgress}
|
||||
<span class="input-group-text">Uploading {fwProgress}</span>
|
||||
{:else}
|
||||
<button class="btn btn-sm btn-primary" on:click={upgradeFw} disabled={!!fwProgress}>Upgrade FW</button>
|
||||
<span class="input-group-text">To version</span>
|
||||
<input class="form-control" placeholder="x.x" type="text" size="6" bind:value={fwVersion} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<button class="btn btn-sm btn-primary" on:click={getRfidInfo}>Rfid</button>
|
||||
<button class="btn btn-sm btn-primary" on:click={startHeartbeat}>Heartbeat on</button>
|
||||
<button class="btn btn-sm btn-primary" on:click={stopHeartbeat}>Heartbeat off</button>
|
||||
<button class="btn btn-sm btn-primary" on:click={soundOn}>Sound on</button>
|
||||
<button class="btn btn-sm btn-primary" on:click={soundOff}>Sound off</button>
|
||||
<button class="btn btn-sm btn-primary" on:click={fetchInfo}>Fetch info again</button>
|
||||
<button class="btn btn-sm btn-primary" on:click={reset}>Reset</button>
|
||||
<button
|
||||
class="btn btn-sm btn-outline-secondary d-block w-100 mt-1"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#tests">
|
||||
Tests <MdIcon icon="expand_more" />
|
||||
</button>
|
||||
|
||||
<div class="collapse" id="tests">
|
||||
<div class="d-flex flex-wrap gap-1 mt-1">
|
||||
<button class="btn btn-sm btn-primary" on:click={getRfidInfo}>Rfid</button>
|
||||
<button class="btn btn-sm btn-primary" on:click={startHeartbeat}>Heartbeat on</button>
|
||||
<button class="btn btn-sm btn-primary" on:click={stopHeartbeat}>Heartbeat off</button>
|
||||
<button class="btn btn-sm btn-primary" on:click={soundOn}>Sound on</button>
|
||||
<button class="btn btn-sm btn-primary" on:click={soundOff}>Sound off</button>
|
||||
<button class="btn btn-sm btn-primary" on:click={fetchInfo}>Fetch info again</button>
|
||||
<button class="btn btn-sm btn-primary" on:click={reset}>Reset</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="input-group-text {$heartbeatFails > 0 ? 'text-warning' : ''}">
|
||||
{$printerMeta?.model ?? $connectedPrinterName}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
@import "bootstrap/scss/close";
|
||||
@import "bootstrap/scss/progress";
|
||||
@import "bootstrap/scss/alert";
|
||||
@import "bootstrap/scss/transitions";
|
||||
@import "font";
|
||||
|
||||
body {
|
||||
@@ -22,3 +23,9 @@ body {
|
||||
border: 1px solid var(--bs-danger-border-subtle);
|
||||
color: var(--bs-danger-text-emphasis);
|
||||
}
|
||||
|
||||
.toastify.toast-info {
|
||||
background: var(--bs-success-bg-subtle);
|
||||
border: 1px solid var(--bs-success-border-subtle);
|
||||
color: var(--bs-success-text-emphasis);
|
||||
}
|
||||
|
||||
@@ -137,6 +137,44 @@ export class FileUtils {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Open file picker and return file contents
|
||||
*
|
||||
* fixme: never ends if dialog closed
|
||||
*
|
||||
* */
|
||||
static async pickAndReadBinaryFile(acceptExtension: string): Promise<Uint8Array> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const input: HTMLInputElement = document.createElement("input");
|
||||
const reader = new FileReader();
|
||||
|
||||
input.type = "file";
|
||||
input.accept = `.${acceptExtension}`;
|
||||
|
||||
input.onchange = (e: Event) => {
|
||||
const target = e.target as HTMLInputElement;
|
||||
if (target.files !== null) {
|
||||
const file: File = target.files[0];
|
||||
const ext = file.name.split(".").pop();
|
||||
|
||||
if (ext === acceptExtension) {
|
||||
reader.readAsArrayBuffer(file);
|
||||
reader.onload = (readerEvt: ProgressEvent<FileReader>) => {
|
||||
if (readerEvt?.target?.result) {
|
||||
resolve(new Uint8Array(readerEvt.target.result as ArrayBuffer));
|
||||
}
|
||||
};
|
||||
reader.onerror = (readerEvt: ProgressEvent<FileReader>) => {
|
||||
console.error(readerEvt);
|
||||
reject(new Error("Unable to load file"));
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
input.click();
|
||||
});
|
||||
}
|
||||
|
||||
static async loadCanvasState(canvas: fabric.Canvas, state: FabricJson): Promise<void> {
|
||||
await canvas.loadFromJSON(state, (_, obj) => {
|
||||
if ("set" in obj) obj.set({ snapAngle: OBJECT_DEFAULTS.snapAngle });
|
||||
|
||||
@@ -13,6 +13,15 @@ export class Toasts {
|
||||
}).showToast();
|
||||
}
|
||||
|
||||
static message(text: string) {
|
||||
Toastify({
|
||||
text,
|
||||
gravity: "bottom",
|
||||
duration: 5000,
|
||||
className: "toast-info",
|
||||
}).showToast();
|
||||
}
|
||||
|
||||
static zodErrors(e: any, prefix: string) {
|
||||
if (e instanceof z.ZodError) {
|
||||
e.issues.forEach((i) => {
|
||||
|
||||
14
yarn.lock
14
yarn.lock
@@ -183,14 +183,15 @@
|
||||
"@jridgewell/resolve-uri" "^3.1.0"
|
||||
"@jridgewell/sourcemap-codec" "^1.4.14"
|
||||
|
||||
"@mmote/niimbluelib@0.0.1-alpha.21":
|
||||
version "0.0.1-alpha.21"
|
||||
resolved "https://registry.yarnpkg.com/@mmote/niimbluelib/-/niimbluelib-0.0.1-alpha.21.tgz#35b2c31094070c69aec4663521ce64475662ebfb"
|
||||
integrity sha512-wSJ1d7VgLwl6G71DOmKITX7XX1OypZaPNim+Aa2Dpa6nyifo1v8WrYOr4aKGGJzZt1oSxQ+spWv2thsQrX2Baw==
|
||||
"@mmote/niimbluelib@0.0.1-alpha.22":
|
||||
version "0.0.1-alpha.22"
|
||||
resolved "https://registry.yarnpkg.com/@mmote/niimbluelib/-/niimbluelib-0.0.1-alpha.22.tgz#34ee209616dcf3988a063eb154d83251315928d6"
|
||||
integrity sha512-XsD9DyyD7RaNlZwqZinLopFy6knjov1JU4nnrA9iqJ4Tdv5sDu6WFHdUKR8Ip8+fkJ7sBF6UHi7y+Ucc4A5wIg==
|
||||
dependencies:
|
||||
"@capacitor-community/bluetooth-le" "^6.0.2"
|
||||
"@capacitor/core" "^6.0.0"
|
||||
async-mutex "^0.5.0"
|
||||
crc-32 "^1.2.2"
|
||||
eventemitter3 "^5.0.1"
|
||||
|
||||
"@popperjs/core@^2.11.8", "@popperjs/core@^2.9.2":
|
||||
@@ -507,6 +508,11 @@ concat-map@0.0.1:
|
||||
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
||||
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
|
||||
|
||||
crc-32@^1.2.2:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.2.tgz#3cad35a934b8bf71f25ca524b6da51fb7eace2ff"
|
||||
integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==
|
||||
|
||||
css-tree@^2.3.1:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.3.1.tgz#10264ce1e5442e8572fc82fbe490644ff54b5c20"
|
||||
|
||||
Reference in New Issue
Block a user