// serial.js const HEADER1 = 0xAA; const HEADER2 = 0x55; const BAUD_RATE = 1000000; const CMD_ID_REQUEST = 0x01; const CMD_FILE_LIST = 0x02; const CMD_LOAD_FILE = 0x03; const CMD_SAVE_FILE = 0x05; const CMD_DELETE_FILE = 0x04; const CMD_SET_POSITION = 0x07; const CMD_PLAY_FILE = 0x08; const CMD_SCAN_CHANNEL = 0x09; const CMD_WRITE_DATA = 0x10; const CMD_READ_DATA = 0x11; const CMD_WRITE_CONFIG_UPDATE = 0x12; const CMD_START_POSITION_STREAM = 0x14; const POSITION_STREAM = 0x15; export class SerialManager { constructor() { this.port = null; this.writer = null; this.reader = null; } async connect() { this.port = await navigator.serial.requestPort(); await this.port.open({ baudRate: BAUD_RATE }); this.writer = this.port.writable.getWriter(); this.reader = this.port.readable.getReader(); } async send(commandCode, payload = []) { const length = payload.length; const lengthHigh = (length >> 8) & 0xFF; const lengthLow = length & 0xFF; let checksum = commandCode ^ lengthHigh ^ lengthLow; for (let byte of payload) { checksum ^= byte; } const message = [HEADER1, HEADER2, commandCode, lengthHigh, lengthLow, ...payload, checksum]; //console.log(new Uint8Array(message)); await this.writer.write(new Uint8Array(message)); } async requestIDPacket() { console.log("Requesting ID packet"); await this.send(CMD_ID_REQUEST); } async requestFileList() { console.log("Requesting File List"); await this.send(CMD_FILE_LIST); } async requestFile(filename) { console.log("Requesting File: " + filename); const encoder = new TextEncoder(); const payload = Array.from(encoder.encode(filename)); await this.send(CMD_LOAD_FILE, payload); // CMD_LOAD_FILE } async requestPlayFile(payload) { console.log("Playing File: " + "filename"); await this.send(CMD_PLAY_FILE, payload); } async saveFile(payload) { console.log("Saving File: " + "filename"); await this.send(CMD_SAVE_FILE, payload); } async deleteFile(payload) { console.log("Deleting File: " + "filename"); await this.send(CMD_DELETE_FILE, payload); } async sendSetPositions(payload) { //console.log("Setting positions"); await this.send(CMD_SET_POSITION, payload); } async requestScan(channel) { console.log("Scanning Channel " + (channel + 1)); let payload = new Uint8Array([channel]); await this.send(CMD_SCAN_CHANNEL, payload); } async requestWriteData(payload) { console.log(payload); await this.send(CMD_WRITE_DATA, payload); } async sendConfigUpdate(payload) { console.log(payload); await this.send(CMD_WRITE_CONFIG_UPDATE, payload); } async requestPositionStreaming(stream) { //stream true/false await this.send(CMD_START_POSITION_STREAM, new Uint8Array([stream])); } startReading(onPacket) { const decoder = new TextDecoder(); let buffer = []; const processBuffer = () => { while (buffer.length >= 5) { if (buffer[0] !== HEADER1 || buffer[1] !== HEADER2) { buffer.shift(); // discard until headers align continue; } const command = buffer[2]; const length = (buffer[3] << 8) | buffer[4]; if (buffer.length < 5 + length) return; // wait for full payload const payload = buffer.slice(5, 5 + length); onPacket(command, payload); buffer = buffer.slice(5 + length); // remove processed packet } }; const loop = async () => { while (this.port.readable) { try { const { value, done } = await this.reader.read(); if (done) break; if (value) { buffer.push(...value); processBuffer(); } } catch (err) { console.error("Read error:", err); break; } } }; loop(); } async receive(timeoutMs = 1000) { const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout waiting for response")), timeoutMs) ); const readPromise = this.reader.read(); try { const { value } = await Promise.race([readPromise, timeoutPromise]); return value; } catch (err) { console.error("Receive error:", err.message); return null; } } disconnect() { this.reader.releaseLock(); this.writer.releaseLock(); this.port.close(); } }