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) {
221224static 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.
225246static 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+ };
384417static String button_command_topics[BTN_COUNT ];
385418
386419static 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
648693static 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