Unmarshal messages client side

This commit is contained in:
Jasper Blanckenburg 2024-09-05 22:24:01 +02:00
parent 39fec8bdfa
commit 1f0c9779f6
6 changed files with 1552 additions and 192 deletions

1306
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -33,7 +33,8 @@
"tailwindcss": "^3.4.3",
"tslib": "^2.4.1",
"typescript": "^5.0.0",
"vite": "^5.0.3"
"vite": "^5.0.3",
"vite-plugin-node-polyfills": "^0.22.0"
},
"type": "module",
"dependencies": {

View File

@ -1,5 +1,44 @@
export interface MarshalledMessage {
id: number;
ext: boolean;
canfd: boolean;
data: number[];
}
export type RawMessage = {
ext?: boolean;
canfd?: boolean;
id: number;
data: Buffer;
};
export function marshalMessage(msg: RawMessage): MarshalledMessage {
return {
id: msg.id,
ext: msg.ext ?? false,
canfd: msg.canfd ?? false,
data: Array.from(msg.data)
};
}
export function unmarshalMessage(msg: MarshalledMessage): RawMessage {
return {
id: msg.id,
ext: msg.ext,
canfd: msg.canfd,
data: Buffer.from(msg.data)
};
}
export interface AMSMessage {
type: 'error' | 'status' | 'slaveStatus' | 'slaveLog' | 'shuntCurrentLog' | 'shuntVoltage1Log' | 'shuntVoltage2Log';
type:
| 'error'
| 'status'
| 'slaveStatus'
| 'slaveLog'
| 'shuntCurrentLog'
| 'shuntVoltage1Log'
| 'shuntVoltage2Log';
}
export interface AMSError extends AMSMessage {
@ -73,4 +112,171 @@ export interface ShuntLog extends AMSMessage {
type: 'shuntCurrentLog' | 'shuntVoltage1Log' | 'shuntVoltage2Log';
value: number;
}
}
import { N_CELLS_PER_SLAVE, N_TEMP_SENSORS_PER_SLAVE } from '$lib/defs';
export const CAN_ID_AMS_ERROR = 0x00c;
export const CAN_ID_AMS_STATUS = 0x00a;
export const CAN_ID_AMS_SLAVE_STATUS_BASE = 0x080;
export const CAN_ID_AMS_SLAVE_STATUS_MASK = 0xff0;
export const CAN_ID_AMS_SLAVE_LOG_BASE = 0x600;
export const CAN_ID_AMS_SLAVE_LOG_MASK = 0xf00;
export const CAN_ID_SHUNT_CURRENT = 0x521;
export const CAN_ID_SHUNT_VOLTAGE_1 = 0x522;
export const CAN_ID_SHUNT_VOLTAGE_2 = 0x523;
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;
}
}
function decodeShunt(msg: RawMessage, type: ShuntLog['type']): ShuntLog | null {
if (msg.data.length != 6) {
console.warn('invalid shunt log frame', msg);
return null;
}
return {
type: type,
value: msg.data.readInt32BE(2) * 1e-3
};
}
export function decodeMessage(msg: RawMessage): AMSMessage | null {
if (msg.ext || msg.canfd) {
console.warn('invalid message', msg);
return null;
}
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;
case CAN_ID_SHUNT_CURRENT:
message = decodeShunt(msg, 'shuntCurrentLog');
break;
case CAN_ID_SHUNT_VOLTAGE_1:
message = decodeShunt(msg, 'shuntVoltage1Log');
break;
case CAN_ID_SHUNT_VOLTAGE_2:
message = decodeShunt(msg, 'shuntVoltage2Log');
break;
default:
console.warn(`unknown message id ${msg.id}`, msg);
}
}
return message;
}

View File

@ -1,5 +1,15 @@
<script lang="ts">
import type { AMSError, AMSMessage, AMSStatus, SlaveLog, SlaveStatus, ShuntLog } from '$lib/messages';
import {
type AMSError,
type AMSMessage,
type AMSStatus,
type SlaveLog,
type SlaveStatus,
type ShuntLog,
type MarshalledMessage,
unmarshalMessage,
decodeMessage
} from '$lib/messages';
import { source } from 'sveltekit-sse';
import MasterStatusDisplay from './master-status-display.svelte';
import SlaveStatusDisplay from './slave-status-display.svelte';
@ -7,7 +17,6 @@
import MasterErrorDisplay from './master-error-display.svelte';
import { SlaveLogData } from '$lib/slave-log';
import ShuntStatusDisplay from './shunt-status-display.svelte';
// const value = source('/data').select('message');
let error: AMSError | undefined;
let amsStatus: AMSStatus | undefined;
@ -31,7 +40,11 @@
if (!value) {
return;
}
const msg = JSON.parse(value) as AMSMessage;
const msg = decodeMessage(unmarshalMessage(JSON.parse(value) as MarshalledMessage));
if (!msg) {
return;
}
switch (msg.type) {
case 'error':
error = msg as AMSError;
@ -64,7 +77,11 @@
<div class="vertical-wrapper">
<MasterErrorDisplay {error} />
<MasterStatusDisplay status={amsStatus} />
<ShuntStatusDisplay currentLogData={shuntCurrentLog} voltage1LogData={shuntVoltage1Log} voltage2LogData={shuntVoltage2Log} />
<ShuntStatusDisplay
currentLogData={shuntCurrentLog}
voltage1LogData={shuntVoltage1Log}
voltage2LogData={shuntVoltage2Log}
/>
</div>
{#each Object.entries(slaveStatus) as [id, status]}
<SlaveStatusDisplay {id} {status} logData={slaveLog[id]} />
@ -87,4 +104,4 @@
margin: 0;
padding: 0;
}
</style>
</style>

View File

@ -1,155 +1,18 @@
import { produce } from 'sveltekit-sse';
import * as can from 'socketcan';
import type {
AMSError,
AMSMessage,
AMSStatus,
SlaveLog,
SlaveLogLastCell,
SlaveLogTemperature,
SlaveLogVoltage,
SlaveStatus,
ShuntLog
import {
type RawMessage,
marshalMessage,
CAN_ID_AMS_ERROR,
CAN_ID_AMS_SLAVE_STATUS_MASK,
CAN_ID_AMS_SLAVE_LOG_MASK,
CAN_ID_AMS_SLAVE_LOG_BASE,
CAN_ID_AMS_SLAVE_STATUS_BASE,
CAN_ID_AMS_STATUS,
CAN_ID_SHUNT_CURRENT,
CAN_ID_SHUNT_VOLTAGE_1,
CAN_ID_SHUNT_VOLTAGE_2
} 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;
const CAN_ID_SHUNT_CURRENT = 0x521;
const CAN_ID_SHUNT_VOLTAGE_1 = 0x522;
const CAN_ID_SHUNT_VOLTAGE_2 = 0x523;
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;
}
}
function decodeShunt(msg: RawMessage, type: String): ShuntLog | null {
if (msg.data.length != 6) {
console.warn('invalid shunt log frame', msg);
return null;
}
const data = msg.data;
return {
type: type,
value: data.readInt32BE(2) * 1e-3,
};
}
export function POST() {
return produce(async function start({ emit }) {
@ -165,39 +28,7 @@ export function POST() {
]);
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;
case CAN_ID_SHUNT_CURRENT:
message = decodeShunt(msg, 'shuntCurrentLog');
break;
case CAN_ID_SHUNT_VOLTAGE_1:
message = decodeShunt(msg, 'shuntVoltage1Log');
break;
case CAN_ID_SHUNT_VOLTAGE_2:
message = decodeShunt(msg, 'shuntVoltage2Log');
break;
}
}
if (message) {
emit('message', JSON.stringify(message));
}
emit('message', JSON.stringify(marshalMessage(msg)));
});
network.start();

View File

@ -1,6 +1,7 @@
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
import { nodePolyfills } from 'vite-plugin-node-polyfills';
export default defineConfig({
plugins: [sveltekit()]
plugins: [sveltekit(), nodePolyfills()]
});