180 lines
4.3 KiB
TypeScript
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();
|
|
};
|
|
});
|
|
}
|