Skip to content

Commit e91db69

Browse files
Leitetclaude
andcommitted
patch: Update Ambibox MQTT to hide car-related topics when EV unplugged
SessionState is only advertised when EV is connected (PAUSED, STOPPED, ERROR, CHARGE_LOOP). Car-related MQTT topics (soc, power, current, sessionState, chargeProtocol) are cleared with empty payloads on unplug and excluded from periodic publishing. Added error toggle for simulating charger error state. UI controls are grayed out when unplugged and state cards hide car-specific info. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
1 parent d7fff06 commit e91db69

8 files changed

Lines changed: 180 additions & 71 deletions

File tree

cmd/simulator/static/js/app.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,7 @@ document.addEventListener('alpine:init', () => {
449449

450450
async toggleWakeUp() {
451451
const state = this.data?.state || {};
452-
const isAwake = state.sessionState === 'CHARGE_LOOP' || state.sessionState === 'PAUSED';
452+
const isAwake = state.sessionState === 'CHARGE_LOOP' || state.sessionState === 'PAUSED' || state.sessionState === 'STOPPED' || state.sessionState === 'ERROR';
453453
try {
454454
await api.wakeUp(this.id, !isAwake);
455455
await this.loadAllData();

cmd/simulator/static/js/components.js

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,11 @@ document.addEventListener('alpine:init', () => {
427427

428428
get isAwake() {
429429
const sessionState = this.state.sessionState;
430-
return sessionState === 'CHARGE_LOOP' || sessionState === 'PAUSED';
430+
return sessionState === 'CHARGE_LOOP' || sessionState === 'PAUSED' || sessionState === 'STOPPED' || sessionState === 'ERROR';
431+
},
432+
433+
get hasError() {
434+
return this.state.errorState || false;
431435
},
432436

433437
get maxCharge() {
@@ -519,6 +523,10 @@ document.addEventListener('alpine:init', () => {
519523
await Alpine.store('simulator').toggleWakeUp();
520524
},
521525

526+
async toggleError() {
527+
await Alpine.store('simulator').setState('errorState', !this.hasError);
528+
},
529+
522530
async setControlMode() {
523531
this.changingControlMode = true;
524532
try {
@@ -537,22 +545,31 @@ document.addEventListener('alpine:init', () => {
537545

538546
get stateCards() {
539547
const s = this.state;
540-
return [
541-
{ label: 'Session State', value: s.sessionState || 'IDLE', highlight: true },
548+
const cards = [
542549
{ label: 'Control Mode', value: s.controlMode || 'CONTROLLABLE', highlight: true },
543550
{ label: 'EV Connected', value: s.evConnected ? 'Yes' : 'No' },
544-
{ label: 'Charge Protocol', value: s.chargeProtocol || 'CCS' },
545-
{ label: 'Power AC', value: `${(s.powerAc || 0).toFixed(0)} W`, highlight: true, colorClass: this.getPowerColorClass(s.powerAc) },
546-
{ label: 'Power DC', value: `${(s.powerDc || 0).toFixed(0)} W` },
547-
{ label: 'State of Charge', value: `${(s.soc || 0).toFixed(1)} %`, highlight: true },
551+
];
552+
// Car-related cards only when plugged in
553+
if (s.evConnected) {
554+
cards.unshift({ label: 'Session State', value: s.sessionState || 'PAUSED', highlight: true });
555+
cards.push(
556+
{ label: 'Charge Protocol', value: s.chargeProtocol || 'CCS' },
557+
{ label: 'Power AC', value: `${(s.powerAc || 0).toFixed(0)} W`, highlight: true, colorClass: this.getPowerColorClass(s.powerAc) },
558+
{ label: 'Power DC', value: `${(s.powerDc || 0).toFixed(0)} W` },
559+
{ label: 'State of Charge', value: `${(s.soc || 0).toFixed(1)} %`, highlight: true },
560+
{ label: 'Current AC (L1)', value: `${(s.currentAc1 || 0).toFixed(1)} A` },
561+
);
562+
}
563+
// Charger-level cards always shown
564+
cards.push(
548565
{ label: 'Voltage AC (L1/L2/L3)', value: `${(s.voltageAc1 || 0).toFixed(0)} V` },
549-
{ label: 'Current AC (L1)', value: `${(s.currentAc1 || 0).toFixed(1)} A` },
550566
{ label: 'Frequency', value: `${(s.frequency || 50).toFixed(1)} Hz` },
551567
{ label: 'Energy Import', value: `${(s.energyAcImport || 0).toFixed(0)} Wh` },
552568
{ label: 'Energy Export', value: `${(s.energyAcExport || 0).toFixed(0)} Wh` },
553569
{ label: 'Max Charge Power', value: `${(s.chargePowerMax || 0).toFixed(0)} W` },
554-
{ label: 'Max Discharge Power', value: `${(s.dischargePowerMax || 0).toFixed(0)} W` }
555-
];
570+
{ label: 'Max Discharge Power', value: `${(s.dischargePowerMax || 0).toFixed(0)} W` },
571+
);
572+
return cards;
556573
},
557574

558575
getPowerColorClass(power) {

cmd/simulator/static/js/protocols/mqtt.js

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,11 @@ export function mqttControls() {
3333

3434
get isAwake() {
3535
const sessionState = this.state.sessionState;
36-
return sessionState === 'CHARGE_LOOP' || sessionState === 'PAUSED';
36+
return sessionState === 'CHARGE_LOOP' || sessionState === 'PAUSED' || sessionState === 'STOPPED' || sessionState === 'ERROR';
37+
},
38+
39+
get hasError() {
40+
return this.state.errorState || false;
3741
},
3842

3943
get maxCharge() {
@@ -110,6 +114,10 @@ export function mqttControls() {
110114
await Alpine.store('simulator').toggleWakeUp();
111115
},
112116

117+
async toggleError() {
118+
await Alpine.store('simulator').setState('errorState', !this.hasError);
119+
},
120+
113121
async setControlMode() {
114122
await Alpine.store('simulator').setState('controlMode', this.controlMode);
115123
}
@@ -124,22 +132,29 @@ export function mqttState() {
124132

125133
get stateCards() {
126134
const s = this.state;
127-
return [
128-
{ label: 'Session State', value: s.sessionState || 'IDLE', highlight: true },
135+
const cards = [
129136
{ label: 'Control Mode', value: s.controlMode || 'CONTROLLABLE', highlight: true },
130137
{ label: 'EV Connected', value: s.evConnected ? 'Yes' : 'No' },
131-
{ label: 'Charge Protocol', value: s.chargeProtocol || 'CCS' },
132-
{ label: 'Power AC', value: `${(s.powerAc || 0).toFixed(0)} W`, highlight: true, colorClass: this.getPowerColorClass(s.powerAc) },
133-
{ label: 'Power DC', value: `${(s.powerDc || 0).toFixed(0)} W` },
134-
{ label: 'State of Charge', value: `${(s.soc || 0).toFixed(1)} %`, highlight: true },
138+
];
139+
if (s.evConnected) {
140+
cards.unshift({ label: 'Session State', value: s.sessionState || 'PAUSED', highlight: true });
141+
cards.push(
142+
{ label: 'Charge Protocol', value: s.chargeProtocol || 'CCS' },
143+
{ label: 'Power AC', value: `${(s.powerAc || 0).toFixed(0)} W`, highlight: true, colorClass: this.getPowerColorClass(s.powerAc) },
144+
{ label: 'Power DC', value: `${(s.powerDc || 0).toFixed(0)} W` },
145+
{ label: 'State of Charge', value: `${(s.soc || 0).toFixed(1)} %`, highlight: true },
146+
{ label: 'Current AC (L1)', value: `${(s.currentAc1 || 0).toFixed(1)} A` },
147+
);
148+
}
149+
cards.push(
135150
{ label: 'Voltage AC (L1/L2/L3)', value: `${(s.voltageAc1 || 0).toFixed(0)} V` },
136-
{ label: 'Current AC (L1)', value: `${(s.currentAc1 || 0).toFixed(1)} A` },
137151
{ label: 'Frequency', value: `${(s.frequency || 50).toFixed(1)} Hz` },
138152
{ label: 'Energy Import', value: `${(s.energyAcImport || 0).toFixed(0)} Wh` },
139153
{ label: 'Energy Export', value: `${(s.energyAcExport || 0).toFixed(0)} Wh` },
140154
{ label: 'Max Charge Power', value: `${(s.chargePowerMax || 0).toFixed(0)} W` },
141-
{ label: 'Max Discharge Power', value: `${(s.dischargePowerMax || 0).toFixed(0)} W` }
142-
];
155+
{ label: 'Max Discharge Power', value: `${(s.dischargePowerMax || 0).toFixed(0)} W` },
156+
);
157+
return cards;
143158
},
144159

145160
getPowerColorClass(power) {

cmd/simulator/static/js/stores.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1660,7 +1660,7 @@ document.addEventListener('alpine:init', () => {
16601660

16611661
async toggleWakeUp() {
16621662
const state = this.data?.state || {};
1663-
const isAwake = state.sessionState === 'CHARGE_LOOP' || state.sessionState === 'PAUSED';
1663+
const isAwake = state.sessionState === 'CHARGE_LOOP' || state.sessionState === 'PAUSED' || state.sessionState === 'STOPPED' || state.sessionState === 'ERROR';
16641664
try {
16651665
await api.wakeUp(this.id, !isAwake);
16661666
await this.loadAllData();

cmd/simulator/templates/index.html

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1502,6 +1502,21 @@ <h2 class="dashboard-title">Site Load</h2>
15021502
x-text="isAwake ? 'Yes' : 'No'"></span>
15031503
</div>
15041504
</div>
1505+
<template x-if="evConnected">
1506+
<div class="control-group">
1507+
<span class="control-group-label">Error</span>
1508+
<div class="toggle-switch">
1509+
<div class="toggle-switch-track"
1510+
:class="{ 'on': hasError }"
1511+
@click="toggleError()">
1512+
<div class="toggle-switch-thumb"></div>
1513+
</div>
1514+
<span class="toggle-switch-status"
1515+
:class="hasError ? 'stopped' : 'running'"
1516+
x-text="hasError ? 'Yes' : 'No'"></span>
1517+
</div>
1518+
</div>
1519+
</template>
15051520
<div class="control-group">
15061521
<span class="control-group-label">Mode</span>
15071522
<select x-model="controlMode" @change="setControlMode()">
@@ -1510,7 +1525,7 @@ <h2 class="dashboard-title">Site Load</h2>
15101525
</select>
15111526
</div>
15121527
</div>
1513-
<div class="controls-column-right">
1528+
<div class="controls-column-right" :style="!evConnected && 'opacity: 0.4; pointer-events: none'">
15141529
<div class="control-group vertical">
15151530
<span class="control-group-label">Power Control</span>
15161531
<div class="power-slider-container">
@@ -1524,6 +1539,7 @@ <h2 class="dashboard-title">Site Load</h2>
15241539
<input type="range" class="power-slider-input"
15251540
:min="-maxDischarge" :max="maxCharge"
15261541
:value="powerSliderValue" step="100"
1542+
:disabled="!evConnected"
15271543
@input="onPowerInput($event)"
15281544
@change="onPowerChange($event)">
15291545
</div>
@@ -1539,6 +1555,7 @@ <h2 class="dashboard-title">Site Load</h2>
15391555
<div class="soc-slider-track">
15401556
<input type="range" class="soc-slider-input"
15411557
min="0" max="100" :value="socSliderValue" step="1"
1558+
:disabled="!evConnected"
15421559
@input="onSocInput($event)"
15431560
@change="onSocChange($event)">
15441561
</div>

0 commit comments

Comments
 (0)