sophia_controller/serial.js

158 lines
4.3 KiB
JavaScript

// 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;
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);
}
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();
}
}