13 Commits

Author SHA1 Message Date
a3bf9ac32b 2.2.3 2025-01-15 11:31:47 +01:00
David Villafaña
5bd3245084 fix: typographical error (#156)
Co-authored-by: David Rog Desktop <dvillafanaiv@proton.me>
2025-01-15 11:30:46 +01:00
1cd2ec318a 2.2.2 2025-01-14 13:35:53 +01:00
6c8bfa0272 fix: ota update 2025-01-14 13:31:22 +01:00
f69be14b5e 2.2.1 2025-01-06 19:34:28 +01:00
dce554fc66 fix: set pnpm version in github actions correctly 2025-01-06 19:32:43 +01:00
f152dbdcf5 fix: set node/pnpm versions correctly 2025-01-06 19:31:04 +01:00
6a29e6a2fc 2.2.0 2025-01-06 19:25:45 +01:00
9bf3801fef Mark factory flash as wip 2025-01-06 19:25:27 +01:00
d2accfb838 Squash merge fix-vocabulary-export into master 2024-12-09 18:41:26 +01:00
b8a376b93b feat: update m4g 2024-12-09 18:35:05 +01:00
588719df91 feat: support factory flashing 2024-11-23 19:02:35 +01:00
6a0dad9dad feat: android support 2024-11-23 15:07:35 +01:00
14 changed files with 316 additions and 73 deletions

View File

@@ -20,7 +20,7 @@ jobs:
- name: Install pnpm - name: Install pnpm
uses: pnpm/action-setup@v4 uses: pnpm/action-setup@v4
with: with:
version: 8 version: 9
- name: 🐉 Use Node.js 22.4.x - name: 🐉 Use Node.js 22.4.x
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:

View File

@@ -110,6 +110,9 @@ const config = {
"experiment", "experiment",
"code", "code",
"dictionary", "dictionary",
"developer_board",
"developer_board_off",
"memory",
], ],
codePoints: { codePoints: {
speed: "e9e4", speed: "e9e4",

View File

@@ -1,11 +1,11 @@
{ {
"name": "charachorder-device-manager", "name": "charachorder-device-manager",
"version": "2.1.0", "version": "2.2.3",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"private": true, "private": true,
"engines": { "engines": {
"node": ">=18.16", "node": ">=22.4",
"pnpm": ">=8.6" "pnpm": ">=9.4"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@@ -62,6 +62,7 @@
"codemirror": "^6.0.1", "codemirror": "^6.0.1",
"cypress": "^13.13.2", "cypress": "^13.13.2",
"d3": "^7.9.0", "d3": "^7.9.0",
"esptool-js": "^0.4.7",
"flexsearch": "^0.7.43", "flexsearch": "^0.7.43",
"fontkit": "^2.0.4", "fontkit": "^2.0.4",
"glob": "^11.0.0", "glob": "^11.0.0",
@@ -89,6 +90,7 @@
"vite-plugin-mkcert": "^1.17.6", "vite-plugin-mkcert": "^1.17.6",
"vite-plugin-pwa": "^0.20.5", "vite-plugin-pwa": "^0.20.5",
"vitest": "^2.1.4", "vitest": "^2.1.4",
"web-serial-polyfill": "^1.0.15",
"workbox-window": "^7.3.0" "workbox-window": "^7.3.0"
}, },
"type": "module" "type": "module"

30
pnpm-lock.yaml generated
View File

@@ -92,6 +92,9 @@ importers:
d3: d3:
specifier: ^7.9.0 specifier: ^7.9.0
version: 7.9.0 version: 7.9.0
esptool-js:
specifier: ^0.4.7
version: 0.4.7
flexsearch: flexsearch:
specifier: ^0.7.43 specifier: ^0.7.43
version: 0.7.43 version: 0.7.43
@@ -173,6 +176,9 @@ importers:
vitest: vitest:
specifier: ^2.1.4 specifier: ^2.1.4
version: 2.1.4(@types/node@20.14.10)(jsdom@25.0.1)(sass@1.80.6)(terser@5.31.1) version: 2.1.4(@types/node@20.14.10)(jsdom@25.0.1)(sass@1.80.6)(terser@5.31.1)
web-serial-polyfill:
specifier: ^1.0.15
version: 1.0.15
workbox-window: workbox-window:
specifier: ^7.3.0 specifier: ^7.3.0
version: 7.3.0 version: 7.3.0
@@ -1645,6 +1651,9 @@ packages:
resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==}
engines: {node: '>= 4.0.0'} engines: {node: '>= 4.0.0'}
atob-lite@2.0.0:
resolution: {integrity: sha512-LEeSAWeh2Gfa2FtlQE1shxQ8zi5F9GHarrGKz08TMdODD5T4eH6BMsvtnhbWZ+XQn+Gb6om/917ucvRu7l7ukw==}
autoprefixer@10.4.20: autoprefixer@10.4.20:
resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==} resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==}
engines: {node: ^10 || ^12 || >=14} engines: {node: ^10 || ^12 || >=14}
@@ -2250,6 +2259,9 @@ packages:
esm-env@1.0.0: esm-env@1.0.0:
resolution: {integrity: sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA==} resolution: {integrity: sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA==}
esptool-js@0.4.7:
resolution: {integrity: sha512-xVwtSVDRsvjXSEvNFrorgJfB71RFFkZkL+hs7O7gW5hgPrKGywZxo2U5LJddzkJ6eE31QinNVyywc0OaSntZCw==}
esrap@1.2.2: esrap@1.2.2:
resolution: {integrity: sha512-F2pSJklxx1BlQIQgooczXCPHmcWpn6EsP5oo73LQfonG9fIlIENQ8vMmfGXeojP9MrkzUNAfyU5vdFlR9shHAw==} resolution: {integrity: sha512-F2pSJklxx1BlQIQgooczXCPHmcWpn6EsP5oo73LQfonG9fIlIENQ8vMmfGXeojP9MrkzUNAfyU5vdFlR9shHAw==}
@@ -3084,6 +3096,9 @@ packages:
pako@0.2.9: pako@0.2.9:
resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==} resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==}
pako@2.1.0:
resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==}
parent-module@1.0.1: parent-module@1.0.1:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'} engines: {node: '>=6'}
@@ -4060,6 +4075,9 @@ packages:
resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==}
engines: {node: '>=18'} engines: {node: '>=18'}
web-serial-polyfill@1.0.15:
resolution: {integrity: sha512-usZN7kGRkEWr8DzRWxW+og55L1fHo4hNIwxCSCfWKpM+i0L+2AwzupMvkDFxnJNqUFOhLaD3PlgAOJxUOUrAoA==}
webidl-conversions@4.0.2: webidl-conversions@4.0.2:
resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==}
@@ -5796,6 +5814,8 @@ snapshots:
at-least-node@1.0.0: {} at-least-node@1.0.0: {}
atob-lite@2.0.0: {}
autoprefixer@10.4.20(postcss@8.4.39): autoprefixer@10.4.20(postcss@8.4.39):
dependencies: dependencies:
browserslist: 4.24.2 browserslist: 4.24.2
@@ -6519,6 +6539,12 @@ snapshots:
esm-env@1.0.0: {} esm-env@1.0.0: {}
esptool-js@0.4.7:
dependencies:
atob-lite: 2.0.0
pako: 2.1.0
tslib: 2.6.3
esrap@1.2.2: esrap@1.2.2:
dependencies: dependencies:
'@jridgewell/sourcemap-codec': 1.5.0 '@jridgewell/sourcemap-codec': 1.5.0
@@ -7336,6 +7362,8 @@ snapshots:
pako@0.2.9: {} pako@0.2.9: {}
pako@2.1.0: {}
parent-module@1.0.1: parent-module@1.0.1:
dependencies: dependencies:
callsites: 3.1.0 callsites: 3.1.0
@@ -8336,6 +8364,8 @@ snapshots:
dependencies: dependencies:
xml-name-validator: 5.0.0 xml-name-validator: 5.0.0
web-serial-polyfill@1.0.15: {}
webidl-conversions@4.0.2: {} webidl-conversions@4.0.2: {}
webidl-conversions@7.0.0: {} webidl-conversions@7.0.0: {}

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "app" name = "app"
version = "2.1.0" version = "2.2.3"
description = "A Tauri App" description = "A Tauri App"
authors = ["Thea Schöbl <dev@theaninova.de>"] authors = ["Thea Schöbl <dev@theaninova.de>"]
license = "AGPL-3" license = "AGPL-3"

View File

@@ -6,7 +6,7 @@
"devPath": "http://localhost:5173", "devPath": "http://localhost:5173",
"distDir": "../build" "distDir": "../build"
}, },
"package": { "productName": "amacc1ng", "version": "2.1.0" }, "package": { "productName": "amacc1ng", "version": "2.2.3" },
"tauri": { "tauri": {
"allowlist": { "all": false }, "allowlist": { "all": false },
"bundle": { "bundle": {

View File

@@ -5,20 +5,33 @@ col:
row: row:
- switch: { e: 26, n: 27, w: 28, s: 29 } - switch: { e: 26, n: 27, w: 28, s: 29 }
- switch: { e: 21, n: 22, w: 23, s: 24 } - switch: { e: 21, n: 22, w: 23, s: 24 }
- offset: [4, 0]
switch: { w: 66, n: 67, e: 68, s: 69 }
- switch: { w: 71, n: 72, e: 73, s: 74 }
- offset: [2, 0] - offset: [2, 0]
row: row:
- switch: { e: 41, n: 42, w: 43, s: 44 } - switch: { e: 41, n: 42, w: 43, s: 44 }
- switch: { e: 36, n: 37, w: 38, s: 39 } - switch: { e: 36, n: 37, w: 38, s: 39 }
- offset: [4, 0]
switch: { w: 81, n: 82, e: 83, s: 84 }
- switch: { w: 86, n: 87, e: 88, s: 89 }
# Pinkie / Index # Pinkie / Index
- offset: [0, -3] - offset: [0, -3]
row: row:
- switch: { e: 31, n: 32, w: 33, s: 34 } - switch: { e: 31, n: 32, w: 33, s: 34 }
- offset: [4, 0] - offset: [4, 0]
switch: { e: 16, n: 17, w: 18, s: 19 } switch: { e: 16, n: 17, w: 18, s: 19 }
- switch: { w: 61, n: 62, e: 63, s: 64 }
- offset: [4, 0]
switch: { w: 76, n: 77, e: 78, s: 79 }
# Thumbs # Thumbs
- row: - row:
- offset: [5.5, 0.5] - offset: [5.5, 0.5]
switch: { e: 11, n: 12, w: 13, s: 14 } switch: { e: 11, n: 12, w: 13, s: 14 }
- offset: [1, 0.5]
switch: { w: 56, n: 57, e: 58, s: 59 }
- row: - row:
- offset: [4.5, -0.25] - offset: [4.5, -0.25]
switch: { e: 6, n: 7, w: 8, s: 9 } switch: { e: 6, n: 7, w: 8, s: 9 }
- offset: [3, -0.25]
switch: { w: 51, n: 52, e: 53, s: 54 }

View File

@@ -1,24 +1,37 @@
name: M4GR name: M4G
col: col:
# Ring / Middle # Ring / Middle
- offset: [2, 0] - offset: [2, 0]
row: row:
- switch: { e: 23, n: 22, w: 21, s: 24 } - switch: { e: 26, n: 27, w: 28, s: 29 }
- switch: { e: 28, n: 27, w: 26, s: 29 } - switch: { e: 21, n: 22, w: 23, s: 24 }
- offset: [4, 0]
switch: { w: 66, n: 67, e: 68, s: 69 }
- switch: { w: 71, n: 72, e: 73, s: 74 }
- offset: [2, 0] - offset: [2, 0]
row: row:
- switch: { e: 38, n: 37, w: 36, s: 39 } - switch: { e: 41, n: 42, w: 43, s: 44 }
- switch: { e: 43, n: 42, w: 41, s: 44 } - switch: { e: 36, n: 37, w: 38, s: 39 }
- offset: [4, 0]
switch: { w: 81, n: 82, e: 83, s: 84 }
- switch: { w: 86, n: 87, e: 88, s: 89 }
# Pinkie / Index # Pinkie / Index
- offset: [0, -3] - offset: [0, -3]
row: row:
- switch: { e: 18, n: 17, w: 16, s: 19 } - switch: { e: 31, n: 32, w: 33, s: 34 }
- offset: [4, 0] - offset: [4, 0]
switch: { e: 33, n: 32, w: 31, s: 34 } switch: { e: 16, n: 17, w: 18, s: 19 }
- switch: { w: 61, n: 62, e: 63, s: 64 }
- offset: [4, 0]
switch: { w: 76, n: 77, e: 78, s: 79 }
# Thumbs # Thumbs
- row: - row:
- offset: [0.5, 0.5] - offset: [5.5, 0.5]
switch: { e: 13, n: 12, w: 11, s: 14 } switch: { e: 11, n: 12, w: 13, s: 14 }
- offset: [1, 0.5]
switch: { w: 56, n: 57, e: 58, s: 59 }
- row: - row:
- offset: [1.5, -0.25] - offset: [4.5, -0.25]
switch: { e: 8, n: 7, w: 6, s: 9 } switch: { e: 6, n: 7, w: 8, s: 9 }
- offset: [3, -0.25]
switch: { w: 51, n: 52, e: 53, s: 54 }

View File

@@ -13,7 +13,7 @@
"GTM stands for Generative Text Menu and can be used to change your device's settings anywhere", "GTM stands for Generative Text Menu and can be used to change your device's settings anywhere",
"Ambidextrous Throwover (aka Mirror Mode) is a mode designed for one-handed use", "Ambidextrous Throwover (aka Mirror Mode) is a mode designed for one-handed use",
"Chentry stands for character entry, or typing letter by letter on a chording enabled device", "Chentry stands for character entry, or typing letter by letter on a chording enabled device",
"Chord modifiers are hard-coded (as of now) and can be used in the English language to add conjucations and more", "Chord modifiers are hard-coded (as of now) and can be used in the English language to add conjugations and more",
"You can use 'cursor warping' by adding arrow key actions to a chord, for example to chord '()' with the cursor in the middle of the brackets", "You can use 'cursor warping' by adding arrow key actions to a chord, for example to chord '()' with the cursor in the middle of the brackets",
"An arpeggiate is a single key press that modifies the preceding chord. Modifiers can be arpeggiated", "An arpeggiate is a single key press that modifies the preceding chord. Modifiers can be arpeggiated",
"Some actions are marked as a 'macro', which means the output is generated by a key sequence rather than a pure key press. Be cautious with those, as they can affect other keys when held together!", "Some actions are marked as a 'macro', which means the output is generated by a key sequence rather than a pure key press. Be cautious with those, as they can affect other keys when held together!",

View File

@@ -25,8 +25,8 @@ const KEY_COUNTS = {
TWO: 90, TWO: 90,
LITE: 67, LITE: 67,
X: 256, X: 256,
M4G: 64, M4G: 90,
M4GR: 64, M4GR: 90,
} as const; } as const;
if ( if (
@@ -37,6 +37,13 @@ if (
await import("./tauri-serial"); await import("./tauri-serial");
} }
if (browser && navigator.serial === undefined && navigator.usb !== undefined) {
// @ts-expect-error polyfill
navigator.serial = await import("web-serial-polyfill").then(
({ serial }) => serial,
);
}
export async function getViablePorts(): Promise<SerialPort[]> { export async function getViablePorts(): Promise<SerialPort[]> {
return navigator.serial.getPorts().then((ports) => return navigator.serial.getPorts().then((ports) =>
ports.filter((it) => { ports.filter((it) => {

View File

@@ -2,6 +2,7 @@
import { downloadBackup } from "$lib/backup/backup"; import { downloadBackup } from "$lib/backup/backup";
import { initSerial, serialPort } from "$lib/serial/connection"; import { initSerial, serialPort } from "$lib/serial/connection";
import { fade, slide } from "svelte/transition"; import { fade, slide } from "svelte/transition";
import type { LoaderOptions, ESPLoader } from "esptool-js";
let { data } = $props(); let { data } = $props();
@@ -9,7 +10,12 @@
let success = $state(false); let success = $state(false);
let error = $state<Error | undefined>(undefined); let error = $state<Error | undefined>(undefined);
let terminalOutput = $state("");
let step = $state(0); let step = $state(0);
let eraseAll = $state(false);
let espLoader;
async function update() { async function update() {
working = true; working = true;
@@ -18,7 +24,9 @@
const port = $serialPort!; const port = $serialPort!;
$serialPort = undefined; $serialPort = undefined;
try { try {
const file = await fetch(otaUrl!).then((it) => it.blob()); const file = await fetch(
`${data.meta.path}/${data.meta.update.ota?.name}`,
).then((it) => it.blob());
await port.updateFirmware(file); await port.updateFirmware(file);
@@ -36,18 +44,7 @@
: undefined, : undefined,
); );
let isCorrectDevice = $derived( let isCorrectDevice = $derived(
currentDevice ? currentDevice === data.device : undefined, currentDevice ? currentDevice === data.meta.target : undefined,
);
let uf2Url = $derived(
data.uf2
? `${import.meta.env.VITE_FIRMWARE_URL}${data.device}/${data.version}/${data.uf2.name}`
: undefined,
);
let otaUrl = $derived(
data.ota
? `${import.meta.env.VITE_FIRMWARE_URL}${data.device}/${data.version}/${data.ota.name}`
: undefined,
); );
/** /**
@@ -84,10 +81,12 @@
} }
async function getFileSystem() { async function getFileSystem() {
if (!uf2Url) return; if (!data.meta.update.uf2) return;
const uf2Promise = fetch(uf2Url).then((it) => it.blob()); const uf2Promise = fetch(
`${data.meta.path}/${data.meta.update.uf2.name}`,
).then((it) => it.blob());
const handle = await window.showSaveFilePicker({ const handle = await window.showSaveFilePicker({
id: `${data.device}-update`, id: `${data.meta.target}-update`,
suggestedName: "CURRENT.UF2", suggestedName: "CURRENT.UF2",
excludeAcceptAllOption: true, excludeAcceptAllOption: true,
types: [ types: [
@@ -102,21 +101,104 @@
await uf2.stream().pipeTo(writable); await uf2.stream().pipeTo(writable);
step = 4; step = 4;
} }
async function espBootloader() {
$serialPort?.forget();
const port = await navigator.serial.requestPort();
port.open({ baudRate: 1200 });
}
async function connectEsp(port: SerialPort): Promise<ESPLoader> {
const esptool = data.meta.update.esptool!;
const { Transport, ESPLoader } = await import("esptool-js");
const espLoader = new ESPLoader({
transport: new Transport(port),
baudrate: 9600, // Number(esptool.baud),
romBaudrate: 9600, // Number(esptool.baud),
debugLogging: true,
terminal: {
clean: () => {
terminalOutput = "";
},
writeLine: (data) => {
terminalOutput += data + "\n";
},
write: (data) => {
terminalOutput += data;
},
},
} satisfies LoaderOptions);
await espLoader.detectChip(esptool.before);
if (!espLoader.IS_STUB) {
await espLoader.runStub();
}
return espLoader;
}
async function flashImages() {
const port = await navigator.serial.requestPort();
try {
const esptool = data.meta.update.esptool!;
espLoader = await connectEsp(port);
const fileArray = await Promise.all(
Object.entries(esptool.files).map(([offset, name]) =>
fetch(`${data.meta.path}/${name}`)
.then((it) => it.blob())
.then((it) => it.text())
.then((it) => ({
address: Number(offset),
data: it,
})),
),
);
await espLoader.writeFlash({
flashSize: esptool.flash_size,
flashMode: esptool.flash_mode,
flashFreq: esptool.flash_freq,
compress: true,
eraseAll,
fileArray,
});
} finally {
port.close();
}
}
async function eraseSPI() {
const port = await navigator.serial.requestPort();
try {
console.log(data.meta);
const spiFlash = data.meta.spi_flash!;
espLoader = await connectEsp(port);
/*espLoader.flashSpiAttach(
(spiFlash.connection.clk << 0) |
(spiFlash.connection.q << 8) |
(spiFlash.connection.d << 16) |
(spiFlash.connection.cs << 24),
);
espLoader.flashId();*/
} finally {
port.close();
}
}
</script> </script>
<div class="container"> <div class="container">
<h2> <h2>
<a class="inline-link" href="/ccos">CCOS</a> / <a class="inline-link" href="/ccos">CCOS</a> /
<a <a
href="/ccos/{data.device}" href="/ccos/{data.meta.target}"
class="device inline-link" class="device inline-link"
class:correct-device={isCorrectDevice === true} class:correct-device={isCorrectDevice === true}
class:incorrect-device={isCorrectDevice === false}>{data.device}</a class:incorrect-device={isCorrectDevice === false}>{data.meta.target}</a
> >
/ <em class="version">{data.version}</em> / <em class="version">{data.meta.version}</em>
</h2> </h2>
{#if data.ota && !data.device.endsWith("m0")} {#if data.meta.update.ota && !data.meta.target.endsWith("m0")}
{@const buttonError = error || (!success && isCorrectDevice === false)} {@const buttonError = error || (!success && isCorrectDevice === false)}
<section> <section>
<button <button
@@ -136,7 +218,7 @@
{$serialPort.chipset}</b {$serialPort.chipset}</b
> >
will be updated from <b class="version">{$serialPort.version}</b> to will be updated from <b class="version">{$serialPort.version}</b> to
<b class="version">{data.version}</b> <b class="version">{data.meta.version}</b>
</div> </div>
{:else if $serialPort && isCorrectDevice === false} {:else if $serialPort && isCorrectDevice === false}
<div class="error" transition:slide> <div class="error" transition:slide>
@@ -158,27 +240,6 @@
<h3>Manual Update</h3> <h3>Manual Update</h3>
{/if} {/if}
<ul class="files">
{#if data.uf2}
<li>
<a target="_blank" download href={uf2Url}
>{data.uf2.name} <span class="icon">download</span><span class="size"
>{toByteUnit(data.uf2.size)}</span
></a
>
</li>
{/if}
{#if data.ota}
<li>
<a target="_blank" download href={otaUrl}
>{data.ota.name} <span class="icon">download</span><span class="size"
>{toByteUnit(data.uf2.size)}</span
></a
>
</li>
{/if}
</ul>
{#if isCorrectDevice === false} {#if isCorrectDevice === false}
<div transition:slide class="incorrect-device"> <div transition:slide class="incorrect-device">
These files are incompatible with your device These files are incompatible with your device
@@ -186,7 +247,6 @@
{/if} {/if}
<section> <section>
<h4>UF2 Instructions</h4>
<ol> <ol>
<li> <li>
<button class="inline-button" onclick={connect} <button class="inline-button" onclick={connect}
@@ -227,6 +287,37 @@
</li> </li>
</ol> </ol>
</section> </section>
{#if data.meta.update.esptool}
<section>
<h3>Factory Flash (WIP)</h3>
<p>
If everything else fails, you can go through the same process that is
being used in the factory.
</p>
<p>
This will temporarily brick your device if the process is not done
completely or incorrectly.
</p>
<div class="esp-buttons">
<button onclick={espBootloader}
><span class="icon">memory</span>ESP Bootloader</button
>
<button onclick={flashImages}
><span class="icon">developer_board</span>Flash Images</button
>
<label
><input type="checkbox" id="erase" bind:checked={eraseAll} />Erase All</label
>
<button onclick={eraseSPI}
><span class="icon">developer_board</span>Erase SPI Flash</button
>
</div>
<pre>{terminalOutput}</pre>
</section>
{/if}
</div> </div>
<style lang="scss"> <style lang="scss">
@@ -239,6 +330,10 @@
margin-block-start: 4em; margin-block-start: 4em;
} }
pre {
overflow: auto;
}
.primary { .primary {
color: var(--md-sys-color-primary); color: var(--md-sys-color-primary);
} }
@@ -249,6 +344,7 @@
.container { .container {
width: calc(min(100%, 16cm)); width: calc(min(100%, 16cm));
overflow: auto;
} }
@keyframes rotate { @keyframes rotate {
@@ -402,4 +498,8 @@
.incorrect-device { .incorrect-device {
color: var(--md-sys-color-error); color: var(--md-sys-color-error);
} }
.esp-buttons {
display: flex;
}
</style> </style>

View File

@@ -1,20 +1,50 @@
import type { PageLoad } from "./$types"; import type { PageLoad } from "./$types";
import type { FileListing, Listing } from "../../listing"; import type { FileListing, Listing } from "../../listing";
import type { VersionMeta } from "./meta";
export const load = (async ({ fetch, params }) => { export const load = (async ({ fetch, params }) => {
const result = await fetch( const result = await fetch(
`${import.meta.env.VITE_FIRMWARE_URL}/${params.device}/${params.version}/`, `${import.meta.env.VITE_FIRMWARE_URL}/${params.device}/${params.version}/`,
); );
const data: Listing[] = await result.json(); const data: Listing[] = await result.json();
const meta: VersionMeta | undefined = data.some(
(entry) => entry.type === "file" && entry.name === "meta.json",
)
? await fetch(
`${import.meta.env.VITE_FIRMWARE_URL}/${params.device}/${params.version}/meta.json`,
).then((res) => res.json())
: undefined;
return { return {
uf2: data.find( meta: {
(entry) => entry.type === "file" && entry.name === "CURRENT.UF2", version: meta?.version ?? params.version,
) as FileListing, target: meta?.target ?? params.device,
ota: data.find( path: `${import.meta.env.VITE_FIRMWARE_URL}${params.device}/${params.version}`,
(entry) => entry.type === "file" && entry.name === "firmware.bin", git_commit: meta?.git_commit ?? "",
), git_is_dirty: meta?.git_is_dirty ?? false,
version: params.version, git_date: meta?.git_date ?? data[0]?.mtime ?? "",
device: params.device, public_build: meta?.public_build ?? !params.version.startsWith("."),
development_mode: meta?.development_mode ?? 0,
update: {
uf2:
(data.find(
(entry) =>
entry.type === "file" &&
entry.name === (meta?.update?.uf2 ?? "CURRENT.UF2"),
) as FileListing) ?? undefined,
ota:
data.find(
(entry) =>
entry.type === "file" &&
entry.name === (meta?.update?.ota ?? "firmware.bin"),
) ?? undefined,
esptool: meta?.update?.esptool ?? undefined,
},
files: data.filter(
(entry) =>
entry.type === "file" && (!meta?.files || entry.name in meta.files),
) as FileListing[],
spi_flash: meta?.spi_flash ?? undefined,
},
}; };
}) satisfies PageLoad; }) satisfies PageLoad;

View File

@@ -0,0 +1,41 @@
export interface VersionMeta {
version: string;
target: string;
git_commit: string;
git_is_dirty: boolean;
git_date: string;
public_build: boolean;
development_mode: number;
update: {
ota: string | null;
uf2: string | null;
esptool: EspToolData | null;
};
files: string[];
spi_flash: SPIFlashInfo | null;
}
export interface SPIFlashInfo {
type: string;
size: string;
connection: SPIConnection;
}
export interface SPIConnection {
clk: number;
q: number;
d: number;
hd: number;
cs: number;
}
export interface EspToolData {
chip: string;
baud: string;
before: string;
after: string;
flash_mode: string;
flash_freq: string;
flash_size: string;
files: Record<string, string>;
}

View File

@@ -43,7 +43,7 @@
buildIndex($chords, $osLayout).then(searchIndex.set); buildIndex($chords, $osLayout).then(searchIndex.set);
}); });
function encodeChord(chord: ChordInfo, osLayout: Map<string, string>) { function encodeChord(chord: ChordInfo, osLayout: Map<string, string>, onlyPhrase: boolean = false) {
const plainPhrase: string[] = [""]; const plainPhrase: string[] = [""];
const extraActions: string[] = []; const extraActions: string[] = [];
const extraCodes: string[] = []; const extraCodes: string[] = [];
@@ -103,6 +103,10 @@
return result ?? `0x${it.toString(16)}`; return result ?? `0x${it.toString(16)}`;
}); });
if (onlyPhrase) {
return plainPhrase.join();
}
return [ return [
...plainPhrase, ...plainPhrase,
`+${input.join("+")}`, `+${input.join("+")}`,
@@ -182,7 +186,7 @@
function downloadVocabulary() { function downloadVocabulary() {
const vocabulary = new Set( const vocabulary = new Set(
$chords.map((it) => $chords.map((it) =>
"phrase" in it ? plainPhrase(it.phrase, $osLayout).trim() : "", "phrase" in it ? encodeChord(it, $osLayout, true).trim() : "",
), ),
); );
vocabulary.delete(""); vocabulary.delete("");