Compare commits

...

4 Commits

8 changed files with 1450 additions and 1567 deletions

View File

@ -1,2 +1,2 @@
export const N_CELLS_PER_SLAVE = 15; export const N_CELLS_PER_SLAVE = 15;
export const N_TEMP_SENSORS_PER_SLAVE = 32; export const N_TEMP_SENSORS_PER_SLAVE = 44;

View File

@ -1,5 +1,5 @@
export interface AMSMessage { export interface AMSMessage {
type: 'error' | 'status' | 'slaveStatus' | 'slaveLog'; type: 'error' | 'status' | 'shunt' | 'slaveStatus' | 'slaveLog';
} }
export interface AMSError extends AMSMessage { export interface AMSError extends AMSMessage {
@ -29,6 +29,30 @@ export interface AMSStatus extends AMSMessage {
// TODO: IMD state & R_iso // TODO: IMD state & R_iso
} }
export interface Shunt extends AMSMessage {
type: 'shunt';
logType: 'current' | 'voltage1' | 'voltage2';
}
export interface ShuntCurrent extends Shunt {
logType: 'current';
current: number;
}
export interface ShuntVoltage1 extends Shunt {
logType: 'voltage1';
voltage: number;
}
export interface ShuntVoltage2 extends Shunt {
logType: 'voltage2';
voltage: number;
}
export interface SlaveStatus extends AMSMessage { export interface SlaveStatus extends AMSMessage {
type: 'slaveStatus'; type: 'slaveStatus';

View File

@ -1,8 +1,9 @@
<script lang="ts"> <script lang="ts">
import type { AMSError, AMSMessage, AMSStatus, SlaveLog, SlaveStatus } from '$lib/messages'; import type { AMSError, AMSMessage, AMSStatus, Shunt, ShuntCurrent, ShuntVoltage1, ShuntVoltage2, SlaveLog, SlaveStatus } from '$lib/messages';
import { source } from 'sveltekit-sse'; import { source } from 'sveltekit-sse';
import MasterStatusDisplay from './master-status-display.svelte'; import MasterStatusDisplay from './master-status-display.svelte';
import SlaveStatusDisplay from './slave-status-display.svelte'; import SlaveStatusDisplay from './slave-status-display.svelte';
import ShuntStatusDisplay from './shunt-status-display.svelte';
import '../app.css'; import '../app.css';
import MasterErrorDisplay from './master-error-display.svelte'; import MasterErrorDisplay from './master-error-display.svelte';
import { SlaveLogData } from '$lib/slave-log'; import { SlaveLogData } from '$lib/slave-log';
@ -10,6 +11,10 @@
let error: AMSError | undefined; let error: AMSError | undefined;
let amsStatus: AMSStatus | undefined; let amsStatus: AMSStatus | undefined;
let shunt: Shunt | undefined;
let shuntA: ShuntCurrent | undefined
let shuntV1: ShuntVoltage1 | undefined;
let shuntV2: ShuntVoltage2 | undefined;
let slaveStatus: Record<number, SlaveStatus> = {}; let slaveStatus: Record<number, SlaveStatus> = {};
let slaveLog: Record<number, SlaveLogData> = {}; let slaveLog: Record<number, SlaveLogData> = {};
@ -35,6 +40,9 @@
case 'status': case 'status':
amsStatus = msg as AMSStatus; amsStatus = msg as AMSStatus;
break; break;
case 'shunt':
shunt = msg as Shunt;
break;
case 'slaveStatus': { case 'slaveStatus': {
const status = msg as SlaveStatus; const status = msg as SlaveStatus;
slaveStatus[status.slaveId] = status; slaveStatus[status.slaveId] = status;
@ -47,13 +55,20 @@
}); });
</script> </script>
<h1>FaSTTUBe Charger</h1> <div class="wrapper">
<MasterErrorDisplay {error} />
<MasterStatusDisplay status={amsStatus} />
<MasterErrorDisplay {error} />
<MasterStatusDisplay status={amsStatus} />
<div class="slaves">
{#each Object.entries(slaveStatus) as [id, status]} {#each Object.entries(slaveStatus) as [id, status]}
<SlaveStatusDisplay {id} {status} logData={slaveLog[id]} /> <SlaveStatusDisplay {id} {status} logData={slaveLog[id]} />
{/each} {/each}
</div> </div>
<style>
.wrapper {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
</style>

View File

@ -4,6 +4,10 @@ import type {
AMSError, AMSError,
AMSMessage, AMSMessage,
AMSStatus, AMSStatus,
Shunt,
ShuntCurrent,
ShuntVoltage1,
ShuntVoltage2,
SlaveLog, SlaveLog,
SlaveLogLastCell, SlaveLogLastCell,
SlaveLogTemperature, SlaveLogTemperature,
@ -18,6 +22,8 @@ const CAN_ID_AMS_SLAVE_STATUS_BASE = 0x080;
const CAN_ID_AMS_SLAVE_STATUS_MASK = 0xff0; const CAN_ID_AMS_SLAVE_STATUS_MASK = 0xff0;
const CAN_ID_AMS_SLAVE_LOG_BASE = 0x600; const CAN_ID_AMS_SLAVE_LOG_BASE = 0x600;
const CAN_ID_AMS_SLAVE_LOG_MASK = 0xf00; const CAN_ID_AMS_SLAVE_LOG_MASK = 0xf00;
const CAN_ID_AMS_SHUNT_BASE = 0x520;
const CAN_ID_AMS_SHUNT_MASK = 0xff0;
type RawMessage = { type RawMessage = {
ext?: boolean; ext?: boolean;
@ -59,6 +65,45 @@ function decodeStatus(msg: RawMessage): AMSStatus | null {
}; };
} }
function decodeShunt(msg: RawMessage): Shunt | null {
if (msg.data.length != 6) {
console.warn( 'invalid shunt frame', msg);
return null;
}
const index = msg.id & 0x00f;
const data = msg.data;
if (index == 1) {
const msg: ShuntCurrent = {
type: 'shunt',
logType: 'current',
current: data.readInt32BE(2) * 1e-3,
};
return msg;
} else if (index == 2) {
const msg: ShuntVoltage1 = {
type: 'shunt',
logType: 'voltage1',
voltage: data.readInt32BE(2) * 1e-3,
};
return msg;
} else if (index == 3) {
const msg: ShuntVoltage2 = {
type: 'shunt',
logType: 'voltage2',
voltage: data.readInt32BE(2) * 1e-3,
};
return msg;
} else {
console.warn('Unknown shunt index', msg.id);
return null;
}
}
function decodeSlaveStatus(msg: RawMessage): SlaveStatus | null { function decodeSlaveStatus(msg: RawMessage): SlaveStatus | null {
if (msg.data.length != 8) { if (msg.data.length != 8) {
console.warn('invalid slave status frame', msg); console.warn('invalid slave status frame', msg);
@ -139,7 +184,9 @@ export function POST() {
{ id: CAN_ID_AMS_ERROR, mask: 0xfff }, { id: CAN_ID_AMS_ERROR, mask: 0xfff },
{ id: CAN_ID_AMS_STATUS, 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_STATUS_BASE, mask: CAN_ID_AMS_SLAVE_STATUS_MASK },
{ id: CAN_ID_AMS_SLAVE_LOG_BASE, mask: CAN_ID_AMS_SLAVE_LOG_MASK } { id: CAN_ID_AMS_SLAVE_LOG_BASE, mask: CAN_ID_AMS_SLAVE_LOG_MASK },
{ id: CAN_ID_AMS_SHUNT_BASE, mask:
CAN_ID_AMS_SHUNT_MASK }
]); ]);
network.addListener('onMessage', (msg: RawMessage) => { network.addListener('onMessage', (msg: RawMessage) => {
@ -153,6 +200,8 @@ export function POST() {
message = decodeSlaveStatus(msg); message = decodeSlaveStatus(msg);
} else if ((msg.id & CAN_ID_AMS_SLAVE_LOG_MASK) == CAN_ID_AMS_SLAVE_LOG_BASE) { } else if ((msg.id & CAN_ID_AMS_SLAVE_LOG_MASK) == CAN_ID_AMS_SLAVE_LOG_BASE) {
message = decodeSlaveLog(msg); message = decodeSlaveLog(msg);
} else if ((msg.id & CAN_ID_AMS_SHUNT_MASK) == CAN_ID_AMS_SHUNT_BASE) {
message = decodeShunt(msg);
} else { } else {
switch (msg.id) { switch (msg.id) {
case CAN_ID_AMS_ERROR: case CAN_ID_AMS_ERROR:

File diff suppressed because it is too large Load Diff

View File

@ -4,18 +4,35 @@
export let status: AMSStatus | undefined; export let status: AMSStatus | undefined;
</script> </script>
{#if status} <div class="master-content">
<h2>Master Status</h2> {#if status}
<div class="status-table"> <h2><b>Master Status</b></h2>
<div>State:</div> <div class="log-collumn">
<div>{status.state}</div> <div>State: {status.state}</div>
<div>SDC Closed:</div> <div>SDC Closed: {status.sdcClosed}</div>
<div>{status.sdcClosed}</div> <div>SoC: {status.soc}</div>
<div>SoC:</div> <div>Min. cell voltage: {Math.round(status.minCellVolt * 100) / 100}</div>
<div>{status.soc}</div> <div>Max. cell temperature: {Math.round(status.maxCellTemp * 100) / 100}</div>
<div>Min. cell voltage:</div> </div>
<div>{status.minCellVolt}</div> {/if}
<div>Max. cell temperature:</div> </div>
<div>{status.maxCellTemp}</div>
</div> <style>
{/if} .master-content {
display: flex;
flex-direction: column;
width: 12vw;
border: 2px solid black;
padding: 2px;
margin: 2px;
}
.log-collumn {
display: flex;
flex-direction: column;
}
h2 {
text-align: center;
}
</style>

View File

@ -0,0 +1,38 @@
<script lang="ts">
import type { Shunt, ShuntCurrent, ShuntVoltage1, ShuntVoltage2 } from "$lib/messages";
export let shuntData: Shunt | undefined;
export let shuntA: ShuntCurrent | undefined;
export let shuntV1: ShuntVoltage1 | undefined;
export let shuntV2: ShuntVoltage2 | undefined;
</script>
<div class="shunt-content">
{#if shuntData}
<h2><b>Shunt Data</b></h2>
<div class="log-collumn">
<div>Current: {Math.round(shuntA.current * 100) / 100} </div>
<div>Battery side voltage: {Math.round(shuntV1.voltage * 100) / 100}</div>
<div>Vehicle side voltage: {Math.round(shuntV2.voltage * 100) / 100}</div>
</div>
{/if}
</div>
<style>
.shunt-content {
display: flex;
flex-direction: column;
width: 12vw;
border: 2px solid black;
padding: 2px;
margin: 2px;
}
.log-collumn {
display: flex;
flex-direction: column;
}
h2 {
text-align: center;
}
</style>

View File

@ -7,34 +7,67 @@
export let logData: SlaveLogData | undefined; export let logData: SlaveLogData | undefined;
</script> </script>
<h2>Slave #{id}</h2> <div class="slave-content" style:--lightness={status.error ? "60%" : "100%"}>
<h2><b>Slave #{id}</b></h2>
<div class="status-table"> <div class="log-collumn">
<div>Error</div> <div class="log-entry"><div>Error:</div><div><b>{status.error}</b></div></div>
<div>{status.error}</div> <div class="log-entry"><div>Min. cell voltage:</div><div><b>{Math.round(status.minCellVolt*100)/100}V</b></div></div>
<div>Min. cell voltage</div> <div class="log-entry"><div>Max. cell voltage:</div><div><b>{Math.round(status.maxCellVolt*100)/100}V</b></div></div>
<div>{status.minCellVolt}</div> <div class="log-entry"><div>Max. temperature:</div><div><b>{Math.round(status.maxTemp*100)/100}°</b></div></div>
<div>Max. cell voltage</div> <!-- <div>SoC</div>
<div>{status.maxCellVolt}</div> <div>{status.soc}</div> -->
<div>Max. temperature</div> <!-- <div>Failed temperature sensors</div>
<div>{status.maxTemp}</div> <div>{logData?.failedTempSensors ?? 0}</div> -->
<div>SoC</div> </div>
<div>{status.soc}</div>
<div>Failed temperature sensors</div>
<div>{logData?.failedTempSensors ?? 0}</div>
</div>
{#if logData} {#if logData}
<details> <div class="log-collumn">
<div class="status-table">
{#each logData.voltages as volt, i} {#each logData.voltages as volt, i}
<div>V_{i}</div> {#if i < 15}
<div>{volt}</div> <div class="log-entry" style:--hue={Math.max(((volt-2.5)*70.5), 0)}>
<div>V{i}:</div><div><b>{Math.round(volt*100)/100}V</b></div>
</div>
{/if}
{/each} {/each}
{#each logData.temperatures as temp, i} {#each logData.temperatures as temp, i}
<div>T_{i}</div> {#if i > 33 && i < 44}
<div>{temp}</div> <div class="log-entry" style:--hue={210-(temp*3.5)}>
<div>T{i}:</div><div><b>{temp}°</b></div>
</div>
{/if}
{/each} {/each}
</div> </div>
</details> {/if}
{/if} </div>
<style>
.slave-content {
display: flex;
flex-direction: column;
width: 12vw;
padding: 2px;
margin: 2px;
border: 2px solid black;
font-size: 14px;
margin-top: 10px;
background-color: hsl(0, 60%, --lightness);
}
.log-entry {
display: grid;
grid-template-columns: 5fr 3fr;
margin: 2px;
padding: 2px;
background-color: hsl(var(--hue), 80%, 80%);
}
.log-collumn {
display: flex;
flex-direction: column;
}
h2 {
text-align: center;
}
</style>