180 lines
4.3 KiB
TypeScript

import { produce } from 'sveltekit-sse';
import * as can from 'socketcan';
import type {
AMSError,
AMSMessage,
AMSStatus,
SlaveLog,
SlaveLogLastCell,
SlaveLogTemperature,
SlaveLogVoltage,
SlaveStatus
} from '$lib/messages';
import { N_CELLS_PER_SLAVE, N_TEMP_SENSORS_PER_SLAVE } from '$lib/defs';
const CAN_ID_AMS_ERROR = 0x00c;
const CAN_ID_AMS_STATUS = 0x00a;
const CAN_ID_AMS_SLAVE_STATUS_BASE = 0x080;
const CAN_ID_AMS_SLAVE_STATUS_MASK = 0xff0;
const CAN_ID_AMS_SLAVE_LOG_BASE = 0x600;
const CAN_ID_AMS_SLAVE_LOG_MASK = 0xf00;
type RawMessage = {
ext?: boolean;
canfd?: boolean;
id: number;
data: Buffer;
};
function decodeError(msg: RawMessage): AMSError | null {
if (msg.data.length != 2) {
console.warn('invalid error frame', msg);
return null;
}
console.log('error', msg);
const data = msg.data;
return {
type: 'error',
kind: data[0],
arg: data[1]
};
}
function decodeStatus(msg: RawMessage): AMSStatus | null {
if (msg.data.length != 8) {
console.warn('invalid status frame', msg);
return null;
}
const data = msg.data;
return {
type: 'status',
state: data[0] & 0x7f,
sdcClosed: !!(data[0] & 0x80),
soc: data[1],
minCellVolt: data.readUInt16BE(2) * 1e-3,
maxCellTemp: data.readUInt16BE(4) * 0.0625,
imdOK: !!(data[6] & 0x80)
};
}
function decodeSlaveStatus(msg: RawMessage): SlaveStatus | null {
if (msg.data.length != 8) {
console.warn('invalid slave status frame', msg);
return null;
}
const data = msg.data;
return {
type: 'slaveStatus',
slaveId: data[0] & 0x7f,
error: !!(data[0] & 0x80),
soc: data[1],
minCellVolt: data.readUInt16BE(2) * 1e-3,
maxCellVolt: data.readUInt16BE(4) * 1e-3,
maxTemp: data.readUInt16BE(6) * 0.0625
};
}
function decodeSlaveLog(msg: RawMessage): SlaveLog | null {
if (msg.data.length != 8) {
console.warn('invalid slave log frame', msg);
return null;
}
const slaveId = (msg.id & 0xf0) >> 4;
const logIndex = msg.id & 0x00f;
const data = msg.data;
if (logIndex < N_CELLS_PER_SLAVE / 4) {
const startIndex = logIndex * 4;
const voltages = [];
for (let i = 0; i < 4; i++) {
voltages.push(data.readUInt16BE(2 * i) * 1e-3);
}
const msg: SlaveLogVoltage = {
type: 'slaveLog',
slaveId,
logType: 'voltage',
startIndex,
voltages: voltages as [number, number, number, number]
};
return msg;
} else if (logIndex == N_CELLS_PER_SLAVE / 4) {
const msg: SlaveLogLastCell = {
type: 'slaveLog',
slaveId,
logType: 'lastCell',
voltage: data.readUInt16BE(0) * 1e-3,
failed_temp_sensors: data.readInt32BE(2)
};
return msg;
} else if (logIndex < N_CELLS_PER_SLAVE / 4 + 1 + N_TEMP_SENSORS_PER_SLAVE / 8) {
const temperatures = [];
for (let i = 0; i < 8; i++) {
temperatures.push(data.readInt8(i) * 1);
}
const msg: SlaveLogTemperature = {
type: 'slaveLog',
slaveId,
logType: 'temperature',
startIndex: (logIndex - N_CELLS_PER_SLAVE / 4 - 1) * 8,
temperatures: temperatures as [number, number, number, number, number, number, number, number]
};
return msg;
} else {
console.warn('Unknown slave log index', msg.id);
return null;
}
}
export function POST() {
return produce(async function start({ emit }) {
const network = can.createRawChannel('can0');
network.setRxFilters([
{ id: CAN_ID_AMS_ERROR, mask: 0xfff },
{ id: CAN_ID_AMS_STATUS, mask: 0xfff },
{ id: CAN_ID_AMS_SLAVE_STATUS_BASE, mask: CAN_ID_AMS_SLAVE_STATUS_MASK },
{ id: CAN_ID_AMS_SLAVE_LOG_BASE, mask: CAN_ID_AMS_SLAVE_LOG_MASK }
]);
network.addListener('onMessage', (msg: RawMessage) => {
if (msg.ext || msg.canfd) {
console.warn('invalid frame', msg);
return;
}
let message: AMSMessage | null = null;
if ((msg.id & CAN_ID_AMS_SLAVE_STATUS_MASK) == CAN_ID_AMS_SLAVE_STATUS_BASE) {
message = decodeSlaveStatus(msg);
} else if ((msg.id & CAN_ID_AMS_SLAVE_LOG_MASK) == CAN_ID_AMS_SLAVE_LOG_BASE) {
message = decodeSlaveLog(msg);
} else {
switch (msg.id) {
case CAN_ID_AMS_ERROR:
message = decodeError(msg);
break;
case CAN_ID_AMS_STATUS:
message = decodeStatus(msg);
break;
default:
console.warn('unknown frame', msg);
}
}
if (message) {
emit('message', JSON.stringify(message));
}
});
network.start();
return function stop() {
network.stop();
};
});
}