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