133 lines
4.3 KiB
JavaScript
133 lines
4.3 KiB
JavaScript
class SlcanCodec {
|
|
constructor() {
|
|
this.buffer = '';
|
|
this.decoder = new TextDecoder('ascii');
|
|
}
|
|
|
|
feed(chunk, onFrame, onError) {
|
|
const text = this.decoder.decode(chunk, { stream: true });
|
|
if (text.includes('\x07')) {
|
|
console.warn('SLCAN adapter returned NACK');
|
|
}
|
|
this.buffer += text.replace(/\x07/g, '');
|
|
this.parseBuffer(onFrame);
|
|
}
|
|
|
|
parseBuffer(onFrame) {
|
|
while (this.buffer.length > 0) {
|
|
this.buffer = this.buffer.replace(/^[\r\n]+/, '');
|
|
if (!this.buffer) return;
|
|
|
|
const frameStart = this.buffer.search(/[TtRr]/);
|
|
if (frameStart === -1) {
|
|
this.buffer = '';
|
|
return;
|
|
}
|
|
if (frameStart > 0) {
|
|
this.buffer = this.buffer.slice(frameStart);
|
|
}
|
|
|
|
const type = this.buffer[0];
|
|
if (type === 'T') {
|
|
if (this.buffer.length < 10) return;
|
|
const dlcText = this.buffer[9];
|
|
if (!/^[0-8]$/.test(dlcText)) {
|
|
console.warn(`Malformed SLCAN frame header: ${this.buffer.slice(0, 10)}`);
|
|
this.buffer = this.buffer.slice(1);
|
|
continue;
|
|
}
|
|
|
|
const dlc = parseInt(dlcText, 16);
|
|
const frameLength = 10 + dlc * 2;
|
|
if (this.buffer.length < frameLength) return;
|
|
|
|
const line = this.buffer.slice(0, frameLength);
|
|
this.buffer = this.buffer.slice(frameLength).replace(/^[\r\n]+/, '');
|
|
this.parseLine(line, onFrame);
|
|
continue;
|
|
}
|
|
|
|
if (type === 't') {
|
|
if (this.buffer.length < 5) return;
|
|
const dlcText = this.buffer[4];
|
|
if (!/^[0-8]$/.test(dlcText)) {
|
|
this.buffer = this.buffer.slice(1);
|
|
continue;
|
|
}
|
|
const frameLength = 5 + parseInt(dlcText, 16) * 2;
|
|
if (this.buffer.length < frameLength) return;
|
|
this.buffer = this.buffer.slice(frameLength).replace(/^[\r\n]+/, '');
|
|
continue;
|
|
}
|
|
|
|
const lineEndIndex = this.findLineEnd();
|
|
if (lineEndIndex === -1) return;
|
|
this.buffer = this.buffer.slice(lineEndIndex + 1);
|
|
}
|
|
}
|
|
|
|
findLineEnd() {
|
|
const crIndex = this.buffer.indexOf('\r');
|
|
const lfIndex = this.buffer.indexOf('\n');
|
|
|
|
if (crIndex === -1) return lfIndex;
|
|
if (lfIndex === -1) return crIndex;
|
|
return Math.min(crIndex, lfIndex);
|
|
}
|
|
|
|
parseLine(line, onFrame) {
|
|
if (!line) return;
|
|
|
|
const type = line[0];
|
|
if (type === 't' || type === 'r' || type === 'R') return;
|
|
if (type !== 'T') return;
|
|
|
|
if (line.length < 10) {
|
|
console.warn(`Malformed SLCAN frame: ${line}`);
|
|
return;
|
|
}
|
|
|
|
const idText = line.slice(1, 9);
|
|
const dlcText = line[9];
|
|
if (!/^[0-9a-fA-F]{8}$/.test(idText) || !/^[0-8]$/.test(dlcText)) {
|
|
console.warn(`Malformed SLCAN frame: ${line}`);
|
|
return;
|
|
}
|
|
|
|
const dlc = parseInt(dlcText, 16);
|
|
const dataText = line.slice(10, 10 + dlc * 2);
|
|
if (dataText.length !== dlc * 2 || !/^[0-9a-fA-F]*$/.test(dataText)) {
|
|
console.warn(`Malformed SLCAN payload: ${line}`);
|
|
return;
|
|
}
|
|
|
|
const data = new Uint8Array(dlc);
|
|
for (let i = 0; i < dlc; i++) {
|
|
data[i] = parseInt(dataText.slice(i * 2, i * 2 + 2), 16);
|
|
}
|
|
|
|
onFrame?.({
|
|
id: parseInt(idText, 16) & 0x1FFFFFFF,
|
|
data,
|
|
len: dlc,
|
|
});
|
|
}
|
|
|
|
encodeExtendedFrame(messageId, data, len) {
|
|
const canId = messageId & 0x1FFFFFFF;
|
|
const frameLength = Math.min(len, data.length ?? len);
|
|
if (frameLength > 8) {
|
|
throw new Error('SLCAN classic CAN frames support a maximum DLC of 8');
|
|
}
|
|
|
|
let dataHex = '';
|
|
for (let i = 0; i < frameLength; i++) {
|
|
dataHex += data[i].toString(16).toUpperCase().padStart(2, '0');
|
|
}
|
|
|
|
return `T${canId.toString(16).toUpperCase().padStart(8, '0')}${frameLength.toString(16).toUpperCase()}${dataHex}\r`;
|
|
}
|
|
}
|
|
|
|
export default SlcanCodec;
|