mirror of
https://github.com/CharaChorder/DeviceManager.git
synced 2026-01-21 01:12:59 +00:00
feat: enable stricter type checking options
feat: make the app more fault tolerant
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
|
||||
export let ports: SerialPort[];
|
||||
const dispatch = createEventDispatcher<{ confirm: SerialPort | undefined }>();
|
||||
let selected = ports[0].getInfo().name;
|
||||
let selected = ports[0]?.getInfo().name;
|
||||
</script>
|
||||
|
||||
<dialog>
|
||||
|
||||
@@ -37,7 +37,7 @@ export function serializeActions(actions: number[]): bigint {
|
||||
let native = 0n;
|
||||
for (let i = 1; i <= actions.length; i++) {
|
||||
native |=
|
||||
BigInt(actions[actions.length - i] & 0x3ff) << BigInt((12 - i) * 10);
|
||||
BigInt(actions[actions.length - i]! & 0x3ff) << BigInt((12 - i) * 10);
|
||||
}
|
||||
return native;
|
||||
}
|
||||
|
||||
@@ -67,7 +67,9 @@ export async function sync() {
|
||||
syncStatus.set("downloading");
|
||||
|
||||
const max =
|
||||
Object.keys(settingInfo.settings).length + device.keyCount * 3 + chordCount;
|
||||
Object.keys(settingInfo["settings"]).length +
|
||||
device.keyCount * 3 +
|
||||
chordCount;
|
||||
let current = 0;
|
||||
syncProgress.set({ max, current });
|
||||
function progressTick() {
|
||||
@@ -76,7 +78,7 @@ export async function sync() {
|
||||
}
|
||||
|
||||
const parsedSettings: number[] = [];
|
||||
for (const key in settingInfo.settings) {
|
||||
for (const key in settingInfo["settings"]) {
|
||||
try {
|
||||
parsedSettings[Number.parseInt(key)] = await device.getSetting(
|
||||
Number.parseInt(key),
|
||||
@@ -89,7 +91,7 @@ export async function sync() {
|
||||
const parsedLayout: CharaLayout = [[], [], []];
|
||||
for (let layer = 1; layer <= 3; layer++) {
|
||||
for (let i = 0; i < device.keyCount; i++) {
|
||||
parsedLayout[layer - 1][i] = await device.getLayoutKey(layer, i);
|
||||
parsedLayout[layer - 1]![i] = await device.getLayoutKey(layer, i);
|
||||
progressTick();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,11 +48,17 @@ export async function getViablePorts(): Promise<SerialPort[]> {
|
||||
);
|
||||
}
|
||||
|
||||
type LengthArray<T, N extends number, R extends T[] = []> = number extends N
|
||||
? T[]
|
||||
: R["length"] extends N
|
||||
? R
|
||||
: LengthArray<T, N, [T, ...R]>;
|
||||
|
||||
export async function canAutoConnect() {
|
||||
return getViablePorts().then((it) => it.length === 1);
|
||||
}
|
||||
|
||||
function timeout<T>(promise: Promise<T>, ms: number): Promise<T> {
|
||||
async function timeout<T>(promise: Promise<T>, ms: number): Promise<T> {
|
||||
let timer: number;
|
||||
return Promise.race([
|
||||
promise,
|
||||
@@ -96,7 +102,7 @@ export class CharaDevice {
|
||||
const ports = await getViablePorts();
|
||||
this.port =
|
||||
!manual && ports.length === 1
|
||||
? ports[0]
|
||||
? ports[0]!
|
||||
: await navigator.serial.requestPort({
|
||||
filters: [...PORT_FILTERS.values()],
|
||||
});
|
||||
@@ -115,9 +121,9 @@ export class CharaDevice {
|
||||
await this.port.close();
|
||||
|
||||
this.version = new SemVer(
|
||||
await this.send("VERSION").then(([version]) => version),
|
||||
await this.send(1, "VERSION").then(([version]) => version),
|
||||
);
|
||||
const [company, device, chipset] = await this.send("ID");
|
||||
const [company, device, chipset] = await this.send(3, "ID");
|
||||
this.company = company as "CHARACHORDER";
|
||||
this.device = device as "ONE" | "LITE" | "X";
|
||||
this.chipset = chipset as "M0" | "S2";
|
||||
@@ -186,6 +192,7 @@ export class CharaDevice {
|
||||
return it;
|
||||
});
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -256,20 +263,38 @@ export class CharaDevice {
|
||||
/**
|
||||
* Send to serial port
|
||||
*/
|
||||
async send(...command: string[]) {
|
||||
async send<T extends number>(
|
||||
expectedLength: T,
|
||||
...command: string[]
|
||||
): Promise<LengthArray<string, T>> {
|
||||
return this.runWith(async (send, read) => {
|
||||
await send(...command);
|
||||
const commandString = command
|
||||
.join(" ")
|
||||
.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
||||
return read().then((it) =>
|
||||
it.replace(new RegExp(`^${commandString} `), "").split(" "),
|
||||
);
|
||||
const readResult = await read();
|
||||
if (readResult === undefined) {
|
||||
console.error("No response");
|
||||
return Array(expectedLength).fill("NO_RESPONSE") as LengthArray<
|
||||
string,
|
||||
T
|
||||
>;
|
||||
}
|
||||
const array = readResult
|
||||
.replace(new RegExp(`^${commandString} `), "")
|
||||
.split(" ");
|
||||
if (array.length < expectedLength) {
|
||||
console.error("Response too short");
|
||||
return array.concat(
|
||||
Array(expectedLength - array.length).fill("TOO_SHORT"),
|
||||
) as LengthArray<string, T>;
|
||||
}
|
||||
return array as LengthArray<string, T>;
|
||||
});
|
||||
}
|
||||
|
||||
async getChordCount(): Promise<number> {
|
||||
const [count] = await this.send("CML C0");
|
||||
const [count] = await this.send(1, "CML C0");
|
||||
return Number.parseInt(count);
|
||||
}
|
||||
|
||||
@@ -277,7 +302,7 @@ export class CharaDevice {
|
||||
* Retrieves a chord by index
|
||||
*/
|
||||
async getChord(index: number | number[]): Promise<Chord> {
|
||||
const [actions, phrase] = await this.send(`CML C1 ${index}`);
|
||||
const [actions, phrase] = await this.send(2, `CML C1 ${index}`);
|
||||
return {
|
||||
actions: parseChordActions(actions),
|
||||
phrase: parsePhrase(phrase),
|
||||
@@ -289,6 +314,7 @@ export class CharaDevice {
|
||||
*/
|
||||
async getChordPhrase(actions: number[]): Promise<number[] | undefined> {
|
||||
const [phrase] = await this.send(
|
||||
1,
|
||||
`CML C2 ${stringifyChordActions(actions)}`,
|
||||
);
|
||||
return phrase === "2" ? undefined : parsePhrase(phrase);
|
||||
@@ -296,6 +322,7 @@ export class CharaDevice {
|
||||
|
||||
async setChord(chord: Chord) {
|
||||
const [status] = await this.send(
|
||||
1,
|
||||
"CML",
|
||||
"C3",
|
||||
stringifyChordActions(chord.actions),
|
||||
@@ -306,10 +333,10 @@ export class CharaDevice {
|
||||
|
||||
async deleteChord(chord: Pick<Chord, "actions">) {
|
||||
const status = await this.send(
|
||||
1,
|
||||
`CML C4 ${stringifyChordActions(chord.actions)}`,
|
||||
);
|
||||
console.log(status);
|
||||
if (status.at(-1) !== "2" && status.at(-1) !== "0")
|
||||
if (status?.at(-1) !== "2" && status?.at(-1) !== "0")
|
||||
throw new Error(`Failed with status ${status}`);
|
||||
}
|
||||
|
||||
@@ -320,8 +347,7 @@ export class CharaDevice {
|
||||
* @param action the assigned action id
|
||||
*/
|
||||
async setLayoutKey(layer: number, id: number, action: number) {
|
||||
const [status] = await this.send(`VAR B4 A${layer} ${id} ${action}`);
|
||||
console.log(status);
|
||||
const [status] = await this.send(1, `VAR B4 A${layer} ${id} ${action}`);
|
||||
if (status !== "0") throw new Error(`Failed with status ${status}`);
|
||||
}
|
||||
|
||||
@@ -332,7 +358,7 @@ export class CharaDevice {
|
||||
* @returns the assigned action id
|
||||
*/
|
||||
async getLayoutKey(layer: number, id: number) {
|
||||
const [position, status] = await this.send(`VAR B3 A${layer} ${id}`);
|
||||
const [position, status] = await this.send(2, `VAR B3 A${layer} ${id}`);
|
||||
if (status !== "0") throw new Error(`Failed with status ${status}`);
|
||||
return Number(position);
|
||||
}
|
||||
@@ -345,7 +371,7 @@ export class CharaDevice {
|
||||
* **This does not need to be called for chords**
|
||||
*/
|
||||
async commit() {
|
||||
const [status] = await this.send("VAR B0");
|
||||
const [status] = await this.send(1, "VAR B0");
|
||||
if (status !== "0") throw new Error(`Failed with status ${status}`);
|
||||
}
|
||||
|
||||
@@ -357,6 +383,7 @@ export class CharaDevice {
|
||||
*/
|
||||
async setSetting(id: number, value: number) {
|
||||
const [status] = await this.send(
|
||||
1,
|
||||
`VAR B2 ${id.toString(16).toUpperCase()} ${value}`,
|
||||
);
|
||||
if (status !== "0") throw new Error(`Failed with status ${status}`);
|
||||
@@ -367,6 +394,7 @@ export class CharaDevice {
|
||||
*/
|
||||
async getSetting(id: number): Promise<number> {
|
||||
const [value, status] = await this.send(
|
||||
2,
|
||||
`VAR B1 ${id.toString(16).toUpperCase()}`,
|
||||
);
|
||||
if (status !== "0")
|
||||
@@ -380,14 +408,14 @@ export class CharaDevice {
|
||||
* Reboots the device
|
||||
*/
|
||||
async reboot() {
|
||||
await this.send("RST");
|
||||
await this.send(0, "RST");
|
||||
}
|
||||
|
||||
/**
|
||||
* Reboots the device to the bootloader
|
||||
*/
|
||||
async bootloader() {
|
||||
await this.send("RST BOOTLOADER");
|
||||
await this.send(0, "RST BOOTLOADER");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -396,7 +424,7 @@ export class CharaDevice {
|
||||
async reset(
|
||||
type: "FACTORY" | "PARAMS" | "KEYMAPS" | "STARTER" | "CLEARCML" | "FUNC",
|
||||
) {
|
||||
await this.send(`RST ${type}`);
|
||||
await this.send(0, `RST ${type}`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -405,6 +433,6 @@ export class CharaDevice {
|
||||
* This is useful for debugging when there is a suspected heap or stack issue.
|
||||
*/
|
||||
async getRamBytesAvailable(): Promise<number> {
|
||||
return Number(await this.send("RAM"));
|
||||
return Number(await this.send(1, "RAM").then(([bytes]) => bytes));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { ActionInfo, KeymapCategory } from "$lib/assets/keymaps/keymap";
|
||||
|
||||
export interface KeyInfo extends Partial<ActionInfo> {
|
||||
code: number;
|
||||
category: KeymapCategory;
|
||||
category?: KeymapCategory;
|
||||
}
|
||||
|
||||
export const KEYMAP_CATEGORIES = (await Promise.all(
|
||||
@@ -11,7 +11,7 @@ export const KEYMAP_CATEGORIES = (await Promise.all(
|
||||
),
|
||||
)) as KeymapCategory[];
|
||||
|
||||
export const KEYMAP_CODES: Record<number, KeyInfo> = Object.fromEntries(
|
||||
export const KEYMAP_CODES = new Map<number, KeyInfo>(
|
||||
KEYMAP_CATEGORIES.flatMap((category) =>
|
||||
Object.entries(category.actions).map(([code, action]) => [
|
||||
Number(code),
|
||||
@@ -20,7 +20,7 @@ export const KEYMAP_CODES: Record<number, KeyInfo> = Object.fromEntries(
|
||||
),
|
||||
);
|
||||
|
||||
export const KEYMAP_KEYCODES: Map<string, number> = new Map(
|
||||
export const KEYMAP_KEYCODES = new Map<string, number>(
|
||||
KEYMAP_CATEGORIES.flatMap((category) =>
|
||||
Object.entries(category.actions).map(
|
||||
([code, action]) => [action.keyCode!, Number(code)] as const,
|
||||
@@ -28,7 +28,7 @@ export const KEYMAP_KEYCODES: Map<string, number> = new Map(
|
||||
).filter(([keyCode]) => keyCode !== undefined),
|
||||
);
|
||||
|
||||
export const KEYMAP_IDS: Map<string, KeyInfo> = new Map(
|
||||
export const KEYMAP_IDS = new Map<string, KeyInfo>(
|
||||
KEYMAP_CATEGORIES.flatMap((category) =>
|
||||
Object.entries(category.actions).map(
|
||||
([code, action]) =>
|
||||
|
||||
@@ -14,9 +14,9 @@ export class SemVer {
|
||||
console.error("Invalid version string:", versionString);
|
||||
} else {
|
||||
const [, major, minor, patch, preRelease, meta] = result;
|
||||
this.major = Number.parseInt(major);
|
||||
this.minor = Number.parseInt(minor);
|
||||
this.patch = Number.parseInt(patch);
|
||||
this.major = Number.parseInt(major ?? "NaN");
|
||||
this.minor = Number.parseInt(minor ?? "NaN");
|
||||
this.patch = Number.parseInt(patch ?? "NaN");
|
||||
if (preRelease) this.preRelease = preRelease;
|
||||
if (meta) this.meta = meta;
|
||||
}
|
||||
|
||||
@@ -40,7 +40,6 @@ function NativeSerialPort(info: SerialPortInfo): TauriSerialPort {
|
||||
}
|
||||
|
||||
// @ts-expect-error polyfill
|
||||
// noinspection JSConstantReassignment
|
||||
navigator.serial = {
|
||||
async getPorts(): Promise<SerialPort[]> {
|
||||
return invoke<any[]>("plugin:serial|get_serial_ports").then((ports) =>
|
||||
@@ -69,6 +68,7 @@ navigator.serial = {
|
||||
props: { ports },
|
||||
});
|
||||
const port = await new Promise<SerialPort>((resolve) =>
|
||||
// @ts-expect-error polyfill
|
||||
dialog.$on("confirm", resolve),
|
||||
);
|
||||
dialog.$destroy();
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
export async function updateDevice(port: SerialPort) {
|
||||
await port.open({
|
||||
baudRate: 115200,
|
||||
dataBits: 8,
|
||||
stopBits: 1,
|
||||
parity: "none",
|
||||
bufferSize: 255,;
|
||||
})
|
||||
|
||||
const writer = port.writable!.getWriter()
|
||||
const reader = port.readable!.getReader()
|
||||
}
|
||||
Reference in New Issue
Block a user