refactor: use standard prettier formatting

This commit is contained in:
2024-04-06 13:15:35 +02:00
parent 86ec8651b6
commit 854ab6d3be
106 changed files with 2703 additions and 2046 deletions

View File

@@ -1,171 +1,190 @@
import {LineBreakTransformer} from "$lib/serial/line-break-transformer"
import {serialLog} from "$lib/serial/connection"
import type {Chord} from "$lib/serial/chord"
import {SemVer} from "$lib/serial/sem-ver"
import {parseChordActions, parsePhrase, stringifyChordActions, stringifyPhrase} from "$lib/serial/chord"
import {browser} from "$app/environment"
import { LineBreakTransformer } from "$lib/serial/line-break-transformer";
import { serialLog } from "$lib/serial/connection";
import type { Chord } from "$lib/serial/chord";
import { SemVer } from "$lib/serial/sem-ver";
import {
parseChordActions,
parsePhrase,
stringifyChordActions,
stringifyPhrase,
} from "$lib/serial/chord";
import { browser } from "$app/environment";
const PORT_FILTERS: Map<string, SerialPortFilter> = new Map([
["ONE M0", {usbProductId: 32783, usbVendorId: 9114}],
["LITE S2", {usbProductId: 33070, usbVendorId: 12346}],
["LITE M0", {usbProductId: 32796, usbVendorId: 9114}],
["X", {usbProductId: 33163, usbVendorId: 12346}],
])
["ONE M0", { usbProductId: 32783, usbVendorId: 9114 }],
["LITE S2", { usbProductId: 33070, usbVendorId: 12346 }],
["LITE M0", { usbProductId: 32796, usbVendorId: 9114 }],
["X", { usbProductId: 33163, usbVendorId: 12346 }],
]);
const KEY_COUNTS = {
ONE: 90,
LITE: 67,
X: 256,
} as const
} as const;
if (browser && navigator.serial === undefined && import.meta.env.TAURI_FAMILY !== undefined) {
await import("./tauri-serial")
if (
browser &&
navigator.serial === undefined &&
import.meta.env.TAURI_FAMILY !== undefined
) {
await import("./tauri-serial");
}
export async function getViablePorts(): Promise<SerialPort[]> {
return navigator.serial.getPorts().then(ports =>
ports.filter(it => {
const {usbProductId, usbVendorId} = it.getInfo()
return navigator.serial.getPorts().then((ports) =>
ports.filter((it) => {
const { usbProductId, usbVendorId } = it.getInfo();
for (const filter of PORT_FILTERS.values()) {
if (filter.usbProductId === usbProductId && filter.usbVendorId === usbVendorId) {
return true
if (
filter.usbProductId === usbProductId &&
filter.usbVendorId === usbVendorId
) {
return true;
}
}
return false
return false;
}),
)
);
}
export async function canAutoConnect() {
return getViablePorts().then(it => it.length === 1)
return getViablePorts().then((it) => it.length === 1);
}
function timeout<T>(promise: Promise<T>, ms: number): Promise<T> {
let timer: number
let timer: number;
return Promise.race([
promise,
new Promise<T>((_, reject) => {
timer = setTimeout(() => reject(new Error("Timeout")), ms) as unknown as number
timer = setTimeout(
() => reject(new Error("Timeout")),
ms,
) as unknown as number;
}),
]).finally(() => clearTimeout(timer))
]).finally(() => clearTimeout(timer));
}
export class CharaDevice {
private port!: SerialPort
private reader!: ReadableStreamDefaultReader<string>
private port!: SerialPort;
private reader!: ReadableStreamDefaultReader<string>;
private readonly abortController1 = new AbortController()
private readonly abortController2 = new AbortController()
private readonly abortController1 = new AbortController();
private readonly abortController2 = new AbortController();
private streamClosed!: Promise<void>
private streamClosed!: Promise<void>;
private lock?: Promise<true>
private lock?: Promise<true>;
private readonly suspendDebounce = 100
private suspendDebounceId?: number
private readonly suspendDebounce = 100;
private suspendDebounceId?: number;
version!: SemVer
company!: "CHARACHORDER"
device!: "ONE" | "LITE" | "X"
chipset!: "M0" | "S2"
keyCount!: 90 | 67 | 256
version!: SemVer;
company!: "CHARACHORDER";
device!: "ONE" | "LITE" | "X";
chipset!: "M0" | "S2";
keyCount!: 90 | 67 | 256;
get portInfo() {
return this.port.getInfo()
return this.port.getInfo();
}
constructor(private readonly baudRate = 115200) {}
async init(manual = false) {
try {
const ports = await getViablePorts()
const ports = await getViablePorts();
this.port =
!manual && ports.length === 1
? ports[0]
: await navigator.serial.requestPort({filters: [...PORT_FILTERS.values()]})
: await navigator.serial.requestPort({
filters: [...PORT_FILTERS.values()],
});
await this.port.open({baudRate: this.baudRate})
const info = this.port.getInfo()
serialLog.update(it => {
await this.port.open({ baudRate: this.baudRate });
const info = this.port.getInfo();
serialLog.update((it) => {
it.push({
type: "system",
value: `Connected; ID: 0x${info.usbProductId?.toString(16)}; Vendor: 0x${info.usbVendorId?.toString(
value: `Connected; ID: 0x${info.usbProductId?.toString(
16,
)}`,
})
return it
})
await this.port.close()
)}; Vendor: 0x${info.usbVendorId?.toString(16)}`,
});
return it;
});
await this.port.close();
this.version = new SemVer(await this.send("VERSION").then(([version]) => version))
const [company, device, chipset] = await this.send("ID")
this.company = company as "CHARACHORDER"
this.device = device as "ONE" | "LITE" | "X"
this.chipset = chipset as "M0" | "S2"
this.keyCount = KEY_COUNTS[this.device]
this.version = new SemVer(
await this.send("VERSION").then(([version]) => version),
);
const [company, device, chipset] = await this.send("ID");
this.company = company as "CHARACHORDER";
this.device = device as "ONE" | "LITE" | "X";
this.chipset = chipset as "M0" | "S2";
this.keyCount = KEY_COUNTS[this.device];
} catch (e) {
alert(e)
console.error(e)
throw e
alert(e);
console.error(e);
throw e;
}
}
private async suspend() {
await this.reader.cancel()
await this.reader.cancel();
await this.streamClosed.catch(() => {
/** noop */
})
this.reader.releaseLock()
await this.port.close()
serialLog.update(it => {
});
this.reader.releaseLock();
await this.port.close();
serialLog.update((it) => {
it.push({
type: "system",
value: "Connection suspended",
})
return it
})
});
return it;
});
}
private async wake() {
await this.port.open({baudRate: this.baudRate})
const decoderStream = new TextDecoderStream()
await this.port.open({ baudRate: this.baudRate });
const decoderStream = new TextDecoderStream();
this.streamClosed = this.port.readable!.pipeTo(decoderStream.writable, {
signal: this.abortController1.signal,
})
});
this.reader = decoderStream
.readable!.pipeThrough(new TransformStream(new LineBreakTransformer()), {
signal: this.abortController2.signal,
})
.getReader()
serialLog.update(it => {
.getReader();
serialLog.update((it) => {
it.push({
type: "system",
value: "Connection resumed",
})
return it
})
});
return it;
});
}
private async internalRead() {
try {
const {value} = await timeout(this.reader.read(), 5000)
serialLog.update(it => {
const { value } = await timeout(this.reader.read(), 5000);
serialLog.update((it) => {
it.push({
type: "output",
value: value!,
})
return it
})
return value!
});
return it;
});
return value!;
} catch (e) {
serialLog.update(it => {
serialLog.update((it) => {
it.push({
type: "output",
value: `${e}`,
})
return it
})
});
return it;
});
}
}
@@ -173,61 +192,64 @@ export class CharaDevice {
* Send a command to the device
*/
private async internalSend(...command: string[]) {
const writer = this.port.writable!.getWriter()
const writer = this.port.writable!.getWriter();
try {
serialLog.update(it => {
serialLog.update((it) => {
it.push({
type: "input",
value: command.join(" "),
})
return it
})
await writer.write(new TextEncoder().encode(`${command.join(" ")}\r\n`))
});
return it;
});
await writer.write(new TextEncoder().encode(`${command.join(" ")}\r\n`));
} finally {
writer.releaseLock()
writer.releaseLock();
}
}
async forget() {
await this.port.forget()
await this.port.forget();
}
/**
* Read/write to serial port
*/
async runWith<T>(
callback: (send: typeof this.internalSend, read: typeof this.internalRead) => T | Promise<T>,
callback: (
send: typeof this.internalSend,
read: typeof this.internalRead,
) => T | Promise<T>,
): Promise<T> {
while (this.lock) {
await this.lock
await this.lock;
}
const send = this.internalSend.bind(this)
const read = this.internalRead.bind(this)
let resolveLock: (result: true) => void
this.lock = new Promise<true>(resolve => {
resolveLock = resolve
})
let result!: T
const send = this.internalSend.bind(this);
const read = this.internalRead.bind(this);
let resolveLock: (result: true) => void;
this.lock = new Promise<true>((resolve) => {
resolveLock = resolve;
});
let result!: T;
try {
if (this.suspendDebounceId) {
clearTimeout(this.suspendDebounceId)
clearTimeout(this.suspendDebounceId);
} else {
await this.wake()
await this.wake();
}
result = await callback(send, read)
result = await callback(send, read);
} finally {
delete this.lock
delete this.lock;
this.suspendDebounceId = setTimeout(() => {
// cannot be locked here as all the code until clearTimeout is sync
console.assert(this.lock === undefined)
console.assert(this.lock === undefined);
this.lock = this.suspend().then(() => {
delete this.lock
delete this.suspendDebounceId
return true
})
}, this.suspendDebounce) as any
resolveLock!(true)
return result
delete this.lock;
delete this.suspendDebounceId;
return true;
});
}, this.suspendDebounce) as any;
resolveLock!(true);
return result;
}
}
@@ -236,34 +258,40 @@ export class CharaDevice {
*/
async send(...command: string[]) {
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(" "))
})
await send(...command);
const commandString = command
.join(" ")
.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
return read().then((it) =>
it.replace(new RegExp(`^${commandString} `), "").split(" "),
);
});
}
async getChordCount(): Promise<number> {
const [count] = await this.send("CML C0")
return Number.parseInt(count)
const [count] = await this.send("CML C0");
return Number.parseInt(count);
}
/**
* 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(`CML C1 ${index}`);
return {
actions: parseChordActions(actions),
phrase: parsePhrase(phrase),
}
};
}
/**
* Retrieves the phrase for a set of actions
*/
async getChordPhrase(actions: number[]): Promise<number[] | undefined> {
const [phrase] = await this.send(`CML C2 ${stringifyChordActions(actions)}`)
return phrase === "2" ? undefined : parsePhrase(phrase)
const [phrase] = await this.send(
`CML C2 ${stringifyChordActions(actions)}`,
);
return phrase === "2" ? undefined : parsePhrase(phrase);
}
async setChord(chord: Chord) {
@@ -272,14 +300,17 @@ export class CharaDevice {
"C3",
stringifyChordActions(chord.actions),
stringifyPhrase(chord.phrase),
)
if (status !== "0") console.error(`Failed with status ${status}`)
);
if (status !== "0") console.error(`Failed with status ${status}`);
}
async deleteChord(chord: Pick<Chord, "actions">) {
const status = await this.send(`CML C4 ${stringifyChordActions(chord.actions)}`)
console.log(status)
if (status.at(-1) !== "2" && status.at(-1) !== "0") throw new Error(`Failed with status ${status}`)
const status = await this.send(
`CML C4 ${stringifyChordActions(chord.actions)}`,
);
console.log(status);
if (status.at(-1) !== "2" && status.at(-1) !== "0")
throw new Error(`Failed with status ${status}`);
}
/**
@@ -289,9 +320,9 @@ 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)
if (status !== "0") throw new Error(`Failed with status ${status}`)
const [status] = await this.send(`VAR B4 A${layer} ${id} ${action}`);
console.log(status);
if (status !== "0") throw new Error(`Failed with status ${status}`);
}
/**
@@ -301,9 +332,9 @@ 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}`)
if (status !== "0") throw new Error(`Failed with status ${status}`)
return Number(position)
const [position, status] = await this.send(`VAR B3 A${layer} ${id}`);
if (status !== "0") throw new Error(`Failed with status ${status}`);
return Number(position);
}
/**
@@ -314,8 +345,8 @@ export class CharaDevice {
* **This does not need to be called for chords**
*/
async commit() {
const [status] = await this.send("VAR B0")
if (status !== "0") throw new Error(`Failed with status ${status}`)
const [status] = await this.send("VAR B0");
if (status !== "0") throw new Error(`Failed with status ${status}`);
}
/**
@@ -325,39 +356,47 @@ export class CharaDevice {
* To permanently store the settings, you *must* call commit.
*/
async setSetting(id: number, value: number) {
const [status] = await this.send(`VAR B2 ${id.toString(16).toUpperCase()} ${value}`)
if (status !== "0") throw new Error(`Failed with status ${status}`)
const [status] = await this.send(
`VAR B2 ${id.toString(16).toUpperCase()} ${value}`,
);
if (status !== "0") throw new Error(`Failed with status ${status}`);
}
/**
* Retrieves a setting from the device
*/
async getSetting(id: number): Promise<number> {
const [value, status] = await this.send(`VAR B1 ${id.toString(16).toUpperCase()}`)
const [value, status] = await this.send(
`VAR B1 ${id.toString(16).toUpperCase()}`,
);
if (status !== "0")
throw new Error(`Setting "0x${id.toString(16)}" doesn't exist (Status code ${status})`)
return Number(value)
throw new Error(
`Setting "0x${id.toString(16)}" doesn't exist (Status code ${status})`,
);
return Number(value);
}
/**
* Reboots the device
*/
async reboot() {
await this.send("RST")
await this.send("RST");
}
/**
* Reboots the device to the bootloader
*/
async bootloader() {
await this.send("RST BOOTLOADER")
await this.send("RST BOOTLOADER");
}
/**
* Resets the device
*/
async reset(type: "FACTORY" | "PARAMS" | "KEYMAPS" | "STARTER" | "CLEARCML" | "FUNC") {
await this.send(`RST ${type}`)
async reset(
type: "FACTORY" | "PARAMS" | "KEYMAPS" | "STARTER" | "CLEARCML" | "FUNC",
) {
await this.send(`RST ${type}`);
}
/**
@@ -366,6 +405,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("RAM"));
}
}