"Initial_commit"
This commit is contained in:
250
services/jbdProtocol.ts
Normal file
250
services/jbdProtocol.ts
Normal file
@@ -0,0 +1,250 @@
|
||||
|
||||
import { BMSBasicInfo, BMSCellInfo, ProtectionStatus } from '../types';
|
||||
|
||||
export const START_BYTE = 0xDD;
|
||||
export const READ_CMD = 0xA5;
|
||||
export const WRITE_CMD = 0x5A;
|
||||
export const END_BYTE = 0x77;
|
||||
|
||||
export const CMD_BASIC_INFO = 0x03;
|
||||
export const CMD_CELL_INFO = 0x04;
|
||||
export const CMD_HW_VERSION = 0x05;
|
||||
export const CMD_MOSFET_CTRL = 0xE1;
|
||||
export const CMD_ENTER_FACTORY = 0x00;
|
||||
export const CMD_EXIT_FACTORY = 0x01;
|
||||
|
||||
// EEPROM Registers
|
||||
export const REG_COVP = 0x24;
|
||||
export const REG_COVP_REL = 0x25;
|
||||
export const REG_CUVP = 0x26;
|
||||
export const REG_CUVP_REL = 0x27;
|
||||
export const REG_POVP = 0x20;
|
||||
export const REG_POVP_REL = 0x21;
|
||||
export const REG_PUVP = 0x22;
|
||||
export const REG_PUVP_REL = 0x23;
|
||||
|
||||
export const REG_CHG_OT = 0x18;
|
||||
export const REG_CHG_OT_REL = 0x19;
|
||||
export const REG_CHG_UT = 0x1A;
|
||||
export const REG_CHG_UT_REL = 0x1B;
|
||||
export const REG_DSG_OT = 0x1C;
|
||||
export const REG_DSG_OT_REL = 0x1D;
|
||||
export const REG_DSG_UT = 0x1E;
|
||||
export const REG_DSG_UT_REL = 0x1F;
|
||||
|
||||
export const REG_CELL_V_DELAYS = 0x3D;
|
||||
export const REG_PACK_V_DELAYS = 0x3C;
|
||||
export const REG_CHG_T_DELAYS = 0x3A;
|
||||
export const REG_DSG_T_DELAYS = 0x3B;
|
||||
export const REG_CHG_OC_DELAYS = 0x3E;
|
||||
export const REG_DSG_OC_DELAYS = 0x3F;
|
||||
|
||||
export const REG_COVP_HIGH = 0x36;
|
||||
export const REG_CUVP_HIGH = 0x37;
|
||||
export const REG_FUNC_CONFIG = 0x2D;
|
||||
export const REG_NTC_CONFIG = 0x2E;
|
||||
export const REG_BAL_START = 0x2A;
|
||||
export const REG_BAL_WINDOW = 0x2B;
|
||||
|
||||
export const REG_DESIGN_CAP = 0x10;
|
||||
export const REG_CYCLE_CAP = 0x11;
|
||||
export const REG_DSG_RATE = 0x14;
|
||||
export const REG_CAP_100 = 0x12;
|
||||
export const REG_CAP_80 = 0x32;
|
||||
export const REG_CAP_60 = 0x33;
|
||||
export const REG_CAP_40 = 0x34;
|
||||
export const REG_CAP_20 = 0x35;
|
||||
export const REG_CAP_0 = 0x13;
|
||||
export const REG_FET_CTRL = 0x30;
|
||||
export const REG_LED_TIMER = 0x31;
|
||||
|
||||
export const REG_SHUNT_RES = 0x2C;
|
||||
export const REG_CELL_CNT = 0x2F;
|
||||
export const REG_CYCLE_CNT = 0x17;
|
||||
export const REG_SERIAL_NUM = 0x16;
|
||||
export const REG_MFG_DATE = 0x15;
|
||||
|
||||
export const REG_MFG_NAME = 0xA0;
|
||||
export const REG_DEVICE_NAME = 0xA1;
|
||||
export const REG_BARCODE = 0xA2;
|
||||
|
||||
|
||||
export class JBDProtocol {
|
||||
|
||||
public static calculateChecksum(payload: Uint8Array): number {
|
||||
let sum = 0;
|
||||
for (let i = 0; i < payload.length; i++) {
|
||||
sum += payload[i];
|
||||
}
|
||||
return (0x10000 - sum) & 0xFFFF;
|
||||
}
|
||||
|
||||
public static createPacket(command: number, data: Uint8Array = new Uint8Array(0), mode: number = READ_CMD): Uint8Array {
|
||||
const payloadLength = data.length;
|
||||
const packet = new Uint8Array(7 + payloadLength);
|
||||
packet[0] = START_BYTE;
|
||||
packet[1] = mode;
|
||||
packet[2] = command;
|
||||
packet[3] = payloadLength;
|
||||
|
||||
packet.set(data, 4);
|
||||
|
||||
const checksumPayload = packet.slice(2, 4 + payloadLength);
|
||||
const checksum = this.calculateChecksum(checksumPayload);
|
||||
|
||||
packet[4 + payloadLength] = (checksum >> 8) & 0xFF;
|
||||
packet[4 + payloadLength + 1] = checksum & 0xFF;
|
||||
packet[4 + payloadLength + 2] = END_BYTE;
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
public static parseResponse(data: Uint8Array): { payload: Uint8Array | null, error?: string } {
|
||||
if (data.length < 7) return { payload: null, error: "Too short" };
|
||||
if (data[0] !== START_BYTE) return { payload: null, error: "Invalid Start Byte" };
|
||||
if (data[data.length - 1] !== END_BYTE) return { payload: null, error: "Invalid End Byte" };
|
||||
|
||||
// Standard length check: data[3] should match internal payload length
|
||||
// But some BMS versions send 0x00 at data[3] even with data present.
|
||||
// So we rely on the ACTUAL packet size passed to us to determine payload.
|
||||
// Structure: [DD] [Mode] [Cmd] [Len] [DATA...] [ChkH] [ChkL] [77]
|
||||
// Indices: 0 1 2 3 4... N-3 N-2 N-1
|
||||
|
||||
// Checksum covers from Cmd (index 2) to end of Data (index N-3 inclusive)
|
||||
// Checksum values are at N-3 (High) and N-2 (Low)
|
||||
|
||||
const endOfDataIndex = data.length - 3;
|
||||
const checksumPayload = data.slice(2, endOfDataIndex);
|
||||
const calculatedChecksum = this.calculateChecksum(checksumPayload);
|
||||
const receivedChecksum = (data[data.length - 3] << 8) | data[data.length - 2];
|
||||
|
||||
if (calculatedChecksum !== receivedChecksum && receivedChecksum != 0) {
|
||||
// NOTE: We return the payload even on checksum error for debugging purposes if needed,
|
||||
// or strictly enforce it.
|
||||
// For now, strict enforcement but detailed error.
|
||||
// However, seeing the user log, if checksum logic on hardware is weird, we might need lax mode.
|
||||
// Based on the log provided:
|
||||
// Data: 03 00 00 0A ... A5
|
||||
// User Checksum: 00 00
|
||||
// If the hardware puts 00 00 checksum, it might be ignoring checksum.
|
||||
|
||||
if (receivedChecksum === 0 && calculatedChecksum !== 0) {
|
||||
// Some clones send 0 checksum? Let's treat it as a warning but allow it?
|
||||
// No, let's report error.
|
||||
}
|
||||
|
||||
return {
|
||||
payload: null,
|
||||
error: `CS Mismatch (Calc:${calculatedChecksum.toString(16).toUpperCase()} Recv:${receivedChecksum.toString(16).toUpperCase()})`
|
||||
};
|
||||
}
|
||||
|
||||
// Payload is from index 4 to endOfDataIndex
|
||||
return { payload: data.slice(4, endOfDataIndex) };
|
||||
}
|
||||
|
||||
public static parseString(payload: Uint8Array): string {
|
||||
if (payload.length === 0) return "";
|
||||
const len = payload[0];
|
||||
if (payload.length < 1 + len) return "";
|
||||
const strBytes = payload.slice(1, 1 + len);
|
||||
return new TextDecoder().decode(strBytes);
|
||||
}
|
||||
|
||||
public static encodeString(str: string, maxLen: number = 31): Uint8Array {
|
||||
const encoder = new TextEncoder();
|
||||
const bytes = encoder.encode(str);
|
||||
const len = Math.min(bytes.length, maxLen);
|
||||
const result = new Uint8Array(len + 1);
|
||||
result[0] = len;
|
||||
result.set(bytes.slice(0, len), 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static parseDate(val: number): string {
|
||||
const year = (val >> 9) + 2000;
|
||||
const month = (val >> 5) & 0x0F;
|
||||
const day = val & 0x1F;
|
||||
return `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
public static encodeDate(dateStr: string): number {
|
||||
const d = new Date(dateStr);
|
||||
const year = d.getFullYear();
|
||||
const month = d.getMonth() + 1;
|
||||
const day = d.getDate();
|
||||
return ((year - 2000) << 9) | ((month & 0x0F) << 5) | (day & 0x1F);
|
||||
}
|
||||
|
||||
public static parseBasicInfo(payload: Uint8Array): BMSBasicInfo {
|
||||
const view = new DataView(payload.buffer, payload.byteOffset, payload.byteLength);
|
||||
const packVoltage = view.getUint16(0, false) / 100;
|
||||
const current = view.getInt16(2, false) / 100;
|
||||
const remainingCapacity = view.getUint16(4, false) / 100;
|
||||
const fullCapacity = view.getUint16(6, false) / 100;
|
||||
const cycleCount = view.getUint16(8, false);
|
||||
const productionDateInt = view.getUint16(10, false);
|
||||
const productionDate = this.parseDate(productionDateInt);
|
||||
|
||||
const balanceStatus = view.getUint16(12, false);
|
||||
const balanceStatusHigh = view.getUint16(14, false);
|
||||
const fullBalance = balanceStatus | (balanceStatusHigh << 16);
|
||||
|
||||
const protectionStatusRaw = view.getUint16(16, false);
|
||||
const protectionStatus = this.parseProtectionStatus(protectionStatusRaw);
|
||||
|
||||
const version = view.getUint8(18);
|
||||
const rsoc = view.getUint8(19);
|
||||
|
||||
const mosfetRaw = view.getUint8(20);
|
||||
const mosfetStatus = {
|
||||
charge: (mosfetRaw & 1) === 1,
|
||||
discharge: ((mosfetRaw >> 1) & 1) === 1
|
||||
};
|
||||
|
||||
const ntcCount = view.getUint8(22);
|
||||
const ntcTemps: number[] = [];
|
||||
let offset = 23;
|
||||
for(let i=0; i<ntcCount; i++) {
|
||||
// Safety check for buffer overflow
|
||||
if (offset + 1 >= payload.byteLength) break;
|
||||
const rawTemp = view.getUint16(offset, false);
|
||||
ntcTemps.push((rawTemp - 2731) / 10);
|
||||
offset += 2;
|
||||
}
|
||||
|
||||
return {
|
||||
packVoltage, current, remainingCapacity, fullCapacity, cycleCount,
|
||||
productionDate, balanceStatus: fullBalance, protectionStatus, version,
|
||||
rsoc, mosfetStatus, ntcCount, ntcTemps
|
||||
};
|
||||
}
|
||||
|
||||
public static parseCellInfo(payload: Uint8Array): BMSCellInfo {
|
||||
const view = new DataView(payload.buffer, payload.byteOffset, payload.byteLength);
|
||||
const voltages: number[] = [];
|
||||
for (let i = 0; i < payload.length; i += 2) {
|
||||
if (i + 1 < payload.length) {
|
||||
voltages.push(view.getUint16(i, false) / 1000);
|
||||
}
|
||||
}
|
||||
return { voltages };
|
||||
}
|
||||
|
||||
private static parseProtectionStatus(raw: number): ProtectionStatus {
|
||||
return {
|
||||
covp: !!(raw & 1),
|
||||
cuvp: !!((raw >> 1) & 1),
|
||||
povp: !!((raw >> 2) & 1),
|
||||
puvp: !!((raw >> 3) & 1),
|
||||
chgot: !!((raw >> 4) & 1),
|
||||
chgut: !!((raw >> 5) & 1),
|
||||
dsgot: !!((raw >> 6) & 1),
|
||||
dsgut: !!((raw >> 7) & 1),
|
||||
chgoc: !!((raw >> 8) & 1),
|
||||
dsgoc: !!((raw >> 9) & 1),
|
||||
sc: !!((raw >> 10) & 1),
|
||||
afe: !!((raw >> 11) & 1),
|
||||
};
|
||||
}
|
||||
}
|
||||
335
services/serialService.ts
Normal file
335
services/serialService.ts
Normal file
@@ -0,0 +1,335 @@
|
||||
|
||||
import { JBDProtocol, READ_CMD, WRITE_CMD, CMD_ENTER_FACTORY, CMD_EXIT_FACTORY } from './jbdProtocol';
|
||||
|
||||
type LogType = 'tx' | 'rx' | 'info' | 'error';
|
||||
type LogCallback = (type: LogType, message: string) => void;
|
||||
|
||||
export class SerialService {
|
||||
private port: any | null = null;
|
||||
private reader: ReadableStreamDefaultReader | null = null;
|
||||
private transportLock: Promise<void> = Promise.resolve();
|
||||
private logCallback: LogCallback | null = null;
|
||||
|
||||
public setLogCallback(callback: LogCallback | null) {
|
||||
this.logCallback = callback;
|
||||
}
|
||||
|
||||
public log(type: LogType, message: string) {
|
||||
if (this.logCallback) {
|
||||
this.logCallback(type, message);
|
||||
}
|
||||
}
|
||||
|
||||
private toHex(data: Uint8Array): string {
|
||||
return Array.from(data).map(b => b.toString(16).padStart(2, '0').toUpperCase()).join(' ');
|
||||
}
|
||||
|
||||
public async connect(): Promise<void> {
|
||||
if (!('serial' in navigator)) {
|
||||
throw new Error('Web Serial API not supported');
|
||||
}
|
||||
|
||||
if (this.port && this.port.writable) {
|
||||
return;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const port = await navigator.serial.requestPort();
|
||||
|
||||
try {
|
||||
await port.open({ baudRate: 9600 });
|
||||
this.port = port;
|
||||
this.log('info', 'Serial port connected');
|
||||
} catch (e: any) {
|
||||
console.error("Serial port open failed:", e);
|
||||
this.log('error', 'Connection failed: ' + e.message);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public async disconnect(): Promise<void> {
|
||||
try {
|
||||
await this.transportLock;
|
||||
} catch (e) {
|
||||
console.warn("Transport lock error during disconnect:", e);
|
||||
}
|
||||
|
||||
if (this.reader) {
|
||||
try {
|
||||
await this.reader.cancel();
|
||||
} catch (e) {
|
||||
console.warn("Reader cancel failed:", e);
|
||||
}
|
||||
try {
|
||||
this.reader.releaseLock();
|
||||
} catch (e) {
|
||||
console.warn("Reader releaseLock failed:", e);
|
||||
}
|
||||
this.reader = null;
|
||||
}
|
||||
|
||||
if (this.port) {
|
||||
try {
|
||||
await this.port.close();
|
||||
this.log('info', 'Serial port disconnected');
|
||||
} catch (e) {
|
||||
console.warn("Port close failed:", e);
|
||||
}
|
||||
}
|
||||
this.port = null;
|
||||
}
|
||||
|
||||
public async enterFactoryModeRead(): Promise<void> {
|
||||
await this.writeRegisterRaw(0x00, new Uint8Array([0x56, 0x78]));
|
||||
}
|
||||
|
||||
public async enterFactoryModeWrite(): Promise<void> {
|
||||
await this.writeRegisterRaw(0x01, new Uint8Array([0x28, 0x28]));
|
||||
}
|
||||
|
||||
public async exitFactoryMode(): Promise<void> {
|
||||
await this.writeRegisterRaw(0x01, new Uint8Array([0x00, 0x00]));
|
||||
}
|
||||
|
||||
public async sendCommand(command: number, data: Uint8Array = new Uint8Array(0), mode: number = READ_CMD): Promise<Uint8Array> {
|
||||
const result = this.transportLock.then(() => this.executeCommandUnsafe(command, data, mode));
|
||||
this.transportLock = result.then(() => {}).catch(() => {});
|
||||
return result;
|
||||
}
|
||||
|
||||
public async sendRaw(data: Uint8Array): Promise<void> {
|
||||
const result = this.transportLock.then(async () => {
|
||||
if (!this.port || !this.port.writable) throw new Error('Port not open');
|
||||
|
||||
this.log('tx', this.toHex(data));
|
||||
|
||||
const writer = this.port.writable.getWriter();
|
||||
try {
|
||||
await writer.write(data);
|
||||
} finally {
|
||||
writer.releaseLock();
|
||||
}
|
||||
});
|
||||
this.transportLock = result.then(() => {}).catch(() => {});
|
||||
return result;
|
||||
}
|
||||
|
||||
public async readRaw(): Promise<Uint8Array> {
|
||||
return this.transportLock.then(async () => {
|
||||
if (!this.port || !this.port.readable) throw new Error('Port not readable');
|
||||
const reader = this.port.readable.getReader();
|
||||
this.reader = reader;
|
||||
try {
|
||||
const timeoutMs = 2000;
|
||||
const timerId = setTimeout(() => reader.cancel(), timeoutMs);
|
||||
|
||||
let result;
|
||||
try {
|
||||
result = await reader.read();
|
||||
} catch (e) {
|
||||
if (e instanceof Error && e.message.includes('abort')) {
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
return new Uint8Array(0);
|
||||
} finally {
|
||||
clearTimeout(timerId);
|
||||
}
|
||||
|
||||
const { value, done } = result;
|
||||
|
||||
if (done && !value) {
|
||||
return new Uint8Array(0);
|
||||
}
|
||||
|
||||
if (value) {
|
||||
this.log('rx', this.toHex(value));
|
||||
return value;
|
||||
}
|
||||
return new Uint8Array(0);
|
||||
} finally {
|
||||
this.reader = null;
|
||||
reader.releaseLock();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async readRegister(reg: number): Promise<number> {
|
||||
const data = await this.sendCommand(reg, new Uint8Array(0), READ_CMD);
|
||||
if (data.length < 2) return 0;
|
||||
return (data[0] << 8) | data[1];
|
||||
}
|
||||
|
||||
public async readRegisterBytes(reg: number): Promise<Uint8Array> {
|
||||
return await this.sendCommand(reg, new Uint8Array(0), READ_CMD);
|
||||
}
|
||||
|
||||
public async writeRegister(reg: number, value: number): Promise<void> {
|
||||
const data = new Uint8Array([(value >> 8) & 0xFF, value & 0xFF]);
|
||||
await this.sendCommand(reg, data, WRITE_CMD);
|
||||
}
|
||||
|
||||
private async writeRegisterRaw(reg: number, data: Uint8Array): Promise<void> {
|
||||
await this.sendCommand(reg, data, WRITE_CMD);
|
||||
}
|
||||
|
||||
public async writeRegisterBytes(reg: number, data: Uint8Array): Promise<void> {
|
||||
await this.sendCommand(reg, data, WRITE_CMD);
|
||||
}
|
||||
|
||||
private async executeCommandUnsafe(command: number, data: Uint8Array, mode: number): Promise<Uint8Array> {
|
||||
if (!this.port || !this.port.writable) throw new Error('Port not open');
|
||||
|
||||
const packet = JBDProtocol.createPacket(command, data, mode);
|
||||
|
||||
// LOG TX
|
||||
this.log('tx', this.toHex(packet));
|
||||
|
||||
const writer = this.port.writable.getWriter();
|
||||
|
||||
try {
|
||||
await writer.write(packet);
|
||||
} finally {
|
||||
writer.releaseLock();
|
||||
}
|
||||
|
||||
return await this.readResponse();
|
||||
}
|
||||
|
||||
private async readResponse(): Promise<Uint8Array> {
|
||||
if (!this.port || !this.port.readable) throw new Error('Port not readable');
|
||||
|
||||
const reader = this.port.readable.getReader();
|
||||
this.reader = reader;
|
||||
|
||||
try {
|
||||
let buffer: number[] = [];
|
||||
const timeoutMs = 1500;
|
||||
const startTime = Date.now();
|
||||
|
||||
while (true) {
|
||||
const elapsedTime = Date.now() - startTime;
|
||||
const remainingTime = timeoutMs - elapsedTime;
|
||||
|
||||
if (remainingTime <= 0) throw new Error('Read timeout');
|
||||
|
||||
const timerId = setTimeout(() => reader.cancel(), remainingTime);
|
||||
|
||||
let result;
|
||||
try {
|
||||
result = await reader.read();
|
||||
} catch (e) {
|
||||
if (Date.now() - startTime >= timeoutMs) {
|
||||
throw new Error('Read timeout');
|
||||
}
|
||||
throw e;
|
||||
} finally {
|
||||
clearTimeout(timerId);
|
||||
}
|
||||
|
||||
const { value, done } = result;
|
||||
|
||||
if (done) {
|
||||
if (Date.now() - startTime >= timeoutMs) {
|
||||
throw new Error('Read timeout');
|
||||
}
|
||||
throw new Error('Stream closed');
|
||||
}
|
||||
|
||||
if (value) {
|
||||
this.log('rx', this.toHex(value));
|
||||
for(let byte of value) buffer.push(byte);
|
||||
}
|
||||
|
||||
// Process Buffer
|
||||
while (true) {
|
||||
const startIndex = buffer.indexOf(0xDD);
|
||||
if (startIndex === -1) {
|
||||
// No start byte, clear useless buffer but keep potential partials if needed?
|
||||
// Actually safer to just clear if no DD found at all and buffer is huge.
|
||||
if (buffer.length > 200) buffer.length = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
// Clean up bytes before start index
|
||||
if (startIndex > 0) {
|
||||
buffer.splice(0, startIndex);
|
||||
continue; // Re-evaluate
|
||||
}
|
||||
|
||||
// We have DD at 0.
|
||||
if (buffer.length < 4) break; // Need more data for basic header
|
||||
|
||||
// Standard logic: length is at index 3
|
||||
const declaredLen = buffer[3];
|
||||
const standardPacketLen = declaredLen + 7;
|
||||
|
||||
let packetFound = false;
|
||||
let packetLen = 0;
|
||||
|
||||
// Strategy 1: Trust declared length if it makes sense and ends with 0x77
|
||||
if (declaredLen > 0 && buffer.length >= standardPacketLen) {
|
||||
if (buffer[standardPacketLen - 1] === 0x77) {
|
||||
packetLen = standardPacketLen;
|
||||
packetFound = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Strategy 2: If declared length is 0 (anomaly) or Strategy 1 failed (bad length byte),
|
||||
// scan for the next 0x77 to find the packet boundary.
|
||||
if (!packetFound) {
|
||||
// Start searching for 0x77 after the header (index 3)
|
||||
// Minimum packet size is 7 bytes (DD Cmd Status Len ChkH ChkL 77)
|
||||
for (let i = 6; i < buffer.length; i++) {
|
||||
if (buffer[i] === 0x77) {
|
||||
packetLen = i + 1;
|
||||
// Optimization: Don't just take the first 77 if it's too short to be valid?
|
||||
// But we just want to try parsing it.
|
||||
packetFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (packetFound) {
|
||||
const packet = new Uint8Array(buffer.slice(0, packetLen));
|
||||
const { payload, error } = JBDProtocol.parseResponse(packet);
|
||||
|
||||
if (payload) {
|
||||
// Success! Remove this packet from buffer and return payload
|
||||
// (NOTE: This returns the FIRST valid packet found.
|
||||
// If multiple are queued, subsequent calls will handle them?
|
||||
// No, executeCommandUnsafe expects ONE return.
|
||||
// But we might have read garbage before.)
|
||||
return payload;
|
||||
} else {
|
||||
// Checksum failed or structure invalid
|
||||
this.log('error', `Packet Err: ${error}`);
|
||||
// If scanning found a 77 but checksum failed, it might be a coincidence data byte 0x77.
|
||||
// We should probably consume the buffer up to that point?
|
||||
// Or just consume the start byte (0xDD) and retry?
|
||||
// Safer to consume just the start byte to try finding another sync.
|
||||
buffer.shift();
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
// Packet incomplete, wait for more data
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
this.reader = null;
|
||||
reader.releaseLock();
|
||||
}
|
||||
}
|
||||
|
||||
public async toggleMosfet(charge: boolean, discharge: boolean): Promise<void> {
|
||||
const cBit = charge ? 0 : 1;
|
||||
const dBit = discharge ? 0 : 1;
|
||||
const controlByte = cBit | (dBit << 1);
|
||||
await this.sendCommand(0xE1, new Uint8Array([0x00, controlByte]), WRITE_CMD);
|
||||
}
|
||||
}
|
||||
|
||||
export const serialService = new SerialService();
|
||||
Reference in New Issue
Block a user