Skip to content

Commit 568ba68

Browse files
mbuhansenclaude
andcommitted
feat(mqtt): publish BMW i3 SOC Havrla values and add mode command
- New battery sensors with HA discovery when a BMW i3 is selected: bmwi3_pack_resistance_mohm, bmwi3_soc_havrla, bmwi3_soc_havrla_mode - New command <topic>/command/SET_BMWI3_SOCHAVR, payload 0/disable, 1/auto or 2/enable; stored in NVS like the web setting - Left out on SMALL_FLASH_DEVICE together with the calculation Based on Adam Chyský's MQTT additions, adapted to the upstream MQTT layout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent f30bf55 commit 568ba68

1 file changed

Lines changed: 84 additions & 3 deletions

File tree

‎Software/src/devboard/mqtt/mqtt.cpp‎

Lines changed: 84 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
#include <freertos/FreeRTOS.h>
66
#include <src/communication/nvm/comm_nvm.h>
77
#include "../../battery/BATTERIES.h"
8+
#ifndef SMALL_FLASH_DEVICE
9+
#include "../../battery/BMW-I3-BATTERY.h"
10+
#endif // SMALL_FLASH_DEVICE
811
#include "../../communication/contactorcontrol/comm_contactorcontrol.h"
912
#include "../../datalayer/battery_aggregate.h"
1013
#include "../../datalayer/datalayer.h"
@@ -221,6 +224,24 @@ static bool supports_insulation(Battery* b) {
221224
static bool supports_leaf_metrics(Battery* b) {
222225
return b != nullptr && user_selected_battery_type == BatteryType::NissanLeaf;
223226
}
227+
#ifndef SMALL_FLASH_DEVICE
228+
static bool supports_bmwi3_soc_havrla(Battery* b) {
229+
return b != nullptr && user_selected_battery_type == BatteryType::BmwI3;
230+
}
231+
232+
static const char* bmwi3_soc_havrla_mode_text(uint8_t mode) {
233+
switch (mode) {
234+
case 0:
235+
return "Disabled";
236+
case 1:
237+
return "Auto";
238+
case 2:
239+
return "Enabled";
240+
default:
241+
return "Unknown";
242+
}
243+
}
244+
#endif // SMALL_FLASH_DEVICE
224245
// Emulator-level condition: the heap diagnostics are opt-in from the MQTT settings page.
225246
static bool heap_metrics_enabled(Battery* b) {
226247
return mqtt_publish_heap_metrics;
@@ -268,6 +289,11 @@ static const SensorConfig batterySensorConfigTemplate[] = {
268289
{"leaf_vbat", "VBAT +12 level", "V", "voltage", supports_leaf_metrics},
269290
{"leaf_capacity_ah", "Actual capacity (Ah)", "Ah", "", supports_leaf_metrics},
270291
{"leaf_capacity", "Actual capacity", "kWh", "energy_storage", supports_leaf_metrics},
292+
#ifndef SMALL_FLASH_DEVICE
293+
{"bmwi3_pack_resistance_mohm", "Pack Internal Resistance", "mΩ", "", supports_bmwi3_soc_havrla},
294+
{"bmwi3_soc_havrla", "SoC Havrla", "%", "battery", supports_bmwi3_soc_havrla},
295+
{"bmwi3_soc_havrla_mode", "SoC Havrla Mode", "", "", supports_bmwi3_soc_havrla},
296+
#endif // SMALL_FLASH_DEVICE
271297
{"charge_session", "BYD Charge: Session", "", "", supports_byd_autocal_metrics},
272298
{"charge_grant", "BYD Charge: Grant From Battery", "", "", supports_byd_autocal_metrics},
273299
{"charge_bms_mode", "BYD Charge: Battery Mode", "", "", supports_byd_autocal_metrics},
@@ -377,10 +403,17 @@ enum ButtonCommand {
377403
BTN_SET_LIMITS,
378404
BTN_ESPNOW_RUN,
379405
BTN_SET_SCALESOC,
406+
#ifndef SMALL_FLASH_DEVICE
407+
BTN_SET_BMWI3_SOCHAVR,
408+
#endif // SMALL_FLASH_DEVICE
380409
BTN_COUNT
381410
};
382-
static const char* button_commands[BTN_COUNT] = {"BMSRESET", "PAUSE", "RESUME", "RESTART",
383-
"STOP", "SET_LIMITS", "ESPNOW_RUN", "SET_SCALESOC"};
411+
static const char* button_commands[BTN_COUNT] = {
412+
"BMSRESET", "PAUSE", "RESUME", "RESTART", "STOP", "SET_LIMITS", "ESPNOW_RUN", "SET_SCALESOC",
413+
#ifndef SMALL_FLASH_DEVICE
414+
"SET_BMWI3_SOCHAVR",
415+
#endif // SMALL_FLASH_DEVICE
416+
};
384417
static String button_command_topics[BTN_COUNT];
385418

386419
static String generateCommonInfoAutoConfigTopic(const char* entity_id) {
@@ -643,6 +676,18 @@ void set_battery_attributes(JsonDocument& doc, const DATALAYER_BATTERY_TYPE& bat
643676
doc["leaf_capacity"] = (capacity_Ah * nominal_V) / 1000.0f;
644677
}
645678
}
679+
#ifndef SMALL_FLASH_DEVICE
680+
if (supports_bmwi3_soc_havrla(::battery)) {
681+
// A BMW i3 setup makes every pack a BmwI3Battery (there is no triple i3)
682+
Battery* bat = (battery_index == 3) ? ::battery3 : (battery_index == 2) ? ::battery2 : ::battery;
683+
if (bat != nullptr) {
684+
BmwI3Battery* i3 = static_cast<BmwI3Battery*>(bat);
685+
doc["bmwi3_pack_resistance_mohm"] = i3->pack_resistance_mOhm();
686+
doc["bmwi3_soc_havrla"] = ((float)i3->SOC_havrla()) / 100.0f;
687+
doc["bmwi3_soc_havrla_mode"] = bmwi3_soc_havrla_mode_text(user_selected_bmw_i3_soc_havrla);
688+
}
689+
}
690+
#endif // SMALL_FLASH_DEVICE
646691
}
647692

648693
static std::vector<EventData> order_events;
@@ -658,7 +703,7 @@ static const char* sensor_discovery_icon(const char* entity_id, const char* devi
658703
if (strcmp(entity_id, "bms_status") == 0) {
659704
return "mdi:information-box-outline";
660705
}
661-
if (strcmp(entity_id, "insulation_resistance") == 0) {
706+
if (strcmp(entity_id, "insulation_resistance") == 0 || strcmp(entity_id, "bmwi3_pack_resistance_mohm") == 0) {
662707
return "mdi:resistor";
663708
}
664709
if (strcmp(entity_id, "state_of_health") == 0 || strcmp(entity_id, "leaf_soh_raw") == 0) {
@@ -759,6 +804,11 @@ static bool publish_sensor_discovery(const SensorConfig& config, const char* id_
759804
doc["state_class"] = "measurement";
760805
doc["suggested_display_precision"] = 0;
761806
}
807+
// Same for the BMW i3 pack resistance estimate, a whole number of mOhm
808+
if (strcmp(config.entity_id, "bmwi3_pack_resistance_mohm") == 0) {
809+
doc["state_class"] = "measurement";
810+
doc["suggested_display_precision"] = 0;
811+
}
762812
// The state of health figures are percentages carried with no device_class, so they miss the
763813
// state_class assignment above too. Mark them as measurements and show two decimals, like
764814
// LeafSpy does.
@@ -1338,6 +1388,37 @@ void mqtt_message_received(char* topic_raw, int topic_len, char* data, int data_
13381388
}
13391389
}
13401390

1391+
#ifndef SMALL_FLASH_DEVICE
1392+
// BMW i3 SOC Havrla mode: "0"/"disable", "1"/"auto" or "2"/"enable". Stored like the
1393+
// setting on the web page, so it survives a reboot.
1394+
if (strcmp(topic, button_command_topics[BTN_SET_BMWI3_SOCHAVR].c_str()) == 0) {
1395+
char* data_str = strndup(data, data_len);
1396+
String value(data_str);
1397+
free(data_str);
1398+
value.trim();
1399+
value.toLowerCase();
1400+
1401+
int mode = -1;
1402+
if (value == "0" || value == "disable" || value == "disabled") {
1403+
mode = 0;
1404+
} else if (value == "1" || value == "auto") {
1405+
mode = 1;
1406+
} else if (value == "2" || value == "enable" || value == "enabled") {
1407+
mode = 2;
1408+
}
1409+
1410+
if (mode < 0) {
1411+
logging.printf("MQTT: invalid SET_BMWI3_SOCHAVR payload [%.*s], expected 0/disable, 1/auto or 2/enable\n",
1412+
data_len, data);
1413+
} else {
1414+
user_selected_bmw_i3_soc_havrla = (uint8_t)mode;
1415+
BatteryEmulatorSettingsStore settings;
1416+
settings.saveUInt("BMWI3SOCHAVR", user_selected_bmw_i3_soc_havrla);
1417+
logging.printf("MQTT: BMW i3 SOC Havrla mode set to %s\n", bmwi3_soc_havrla_mode_text(mode));
1418+
}
1419+
}
1420+
#endif // SMALL_FLASH_DEVICE
1421+
13411422
free(topic);
13421423
}
13431424

0 commit comments

Comments
 (0)