Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions components/seplos_bms_v3_ble/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,14 @@
CONF_DISCHARGE_OVERCURRENT_PROTECTION = "discharge_overcurrent_protection"
CONF_CHARGE_OVERTEMPERATURE_PROTECTION = "charge_overtemperature_protection"
CONF_CHARGE_LOW_TEMPERATURE_ALARM = "charge_low_temperature_alarm"
CONF_DISCHARGE_OVERTEMPERATURE_PROTECTION = "discharge_overtemperature_protection"
CONF_ENVIRONMENT_UNDERTEMPERATURE_PROTECTION = "environment_undertemperature_protection"
CONF_MOSFET_OVERTEMPERATURE_PROTECTION = "mosfet_overtemperature_protection"
CONF_BALANCING_START_VOLTAGE = "balancing_start_voltage"
CONF_BALANCING_START_DIFFERENCE = "balancing_start_difference"
CONF_LOW_STATE_OF_CHARGE_ALARM = "low_state_of_charge_alarm"
CONF_INVERTER_CHARGE_CURRENT_LIMIT = "inverter_charge_current_limit"
CONF_INVERTER_DISCHARGE_CURRENT_LIMIT = "inverter_discharge_current_limit"

# key: sensor_schema kwargs
SENSOR_DEFS = {
Expand Down Expand Up @@ -427,6 +435,62 @@
"device_class": DEVICE_CLASS_TEMPERATURE,
"state_class": STATE_CLASS_MEASUREMENT,
},
CONF_DISCHARGE_OVERTEMPERATURE_PROTECTION: {
"unit_of_measurement": UNIT_CELSIUS,
"icon": "mdi:thermometer-plus",
"accuracy_decimals": 1,
"device_class": DEVICE_CLASS_TEMPERATURE,
"state_class": STATE_CLASS_MEASUREMENT,
},
CONF_ENVIRONMENT_UNDERTEMPERATURE_PROTECTION: {
"unit_of_measurement": UNIT_CELSIUS,
"icon": "mdi:thermometer-minus",
"accuracy_decimals": 1,
"device_class": DEVICE_CLASS_TEMPERATURE,
"state_class": STATE_CLASS_MEASUREMENT,
},
CONF_MOSFET_OVERTEMPERATURE_PROTECTION: {
"unit_of_measurement": UNIT_CELSIUS,
"icon": "mdi:thermometer-plus",
"accuracy_decimals": 1,
"device_class": DEVICE_CLASS_TEMPERATURE,
"state_class": STATE_CLASS_MEASUREMENT,
},
CONF_BALANCING_START_VOLTAGE: {
"unit_of_measurement": UNIT_VOLT,
"icon": "mdi:scale-balance",
"accuracy_decimals": 3,
"device_class": DEVICE_CLASS_VOLTAGE,
"state_class": STATE_CLASS_MEASUREMENT,
},
CONF_BALANCING_START_DIFFERENCE: {
"unit_of_measurement": UNIT_VOLT,
"icon": "mdi:scale-balance",
"accuracy_decimals": 3,
"device_class": DEVICE_CLASS_VOLTAGE,
"state_class": STATE_CLASS_MEASUREMENT,
},
CONF_LOW_STATE_OF_CHARGE_ALARM: {
"unit_of_measurement": UNIT_PERCENT,
"icon": "mdi:battery-alert",
"accuracy_decimals": 1,
"device_class": DEVICE_CLASS_EMPTY,
"state_class": STATE_CLASS_MEASUREMENT,
},
CONF_INVERTER_CHARGE_CURRENT_LIMIT: {
"unit_of_measurement": UNIT_AMPERE,
"icon": "mdi:battery-arrow-up",
"accuracy_decimals": 0,
"device_class": DEVICE_CLASS_CURRENT,
"state_class": STATE_CLASS_MEASUREMENT,
},
CONF_INVERTER_DISCHARGE_CURRENT_LIMIT: {
"unit_of_measurement": UNIT_AMPERE,
"icon": "mdi:battery-arrow-down",
"accuracy_decimals": 0,
"device_class": DEVICE_CLASS_CURRENT,
"state_class": STATE_CLASS_MEASUREMENT,
},
CONF_MIN_CELL_VOLTAGE: {
"unit_of_measurement": UNIT_VOLT,
"icon": "mdi:battery-minus-outline",
Expand Down
29 changes: 19 additions & 10 deletions components/seplos_bms_v3_ble/seplos_bms_v3_ble.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -643,18 +643,27 @@ void SeplosBmsV3Ble::decode_spa2_data_(const std::vector<uint8_t> &data) {
};
auto temperature = [&](uint16_t addr) -> float { return (reg(addr) - 2731.5f) * 0.1f; };

int16_t discharge_limit = (int16_t) reg(0x1367);

ESP_LOGD(TAG, "Discharge Overtemperature Protection: %.1f °C", temperature(0x133A));
ESP_LOGD(TAG, "Under Environment Temperature Protection: %.1f °C", temperature(0x1346));
ESP_LOGD(TAG, "Over Power Temperature Protection: %.1f °C", temperature(0x134A));
ESP_LOGD(TAG, "Balancing Open Voltage: %u mV", reg(0x1350));
ESP_LOGD(TAG, "Balancing Open Difference: %u mV", reg(0x1351));
ESP_LOGD(TAG, "SOC Low Alarm: %.1f %%", reg(0x1355) * 0.1f);
// Reg 4922: discharge overtemperature protection
this->publish_state_(this->discharge_overtemperature_protection_sensor_, temperature(0x133A));
// Reg 4934: environment undertemperature protection
this->publish_state_(this->environment_undertemperature_protection_sensor_, temperature(0x1346));
// Reg 4938: mosfet overtemperature protection
this->publish_state_(this->mosfet_overtemperature_protection_sensor_, temperature(0x134A));
// Reg 4944: balancing start voltage
this->publish_state_(this->balancing_start_voltage_sensor_, reg(0x1350) * 0.001f);
// Reg 4945: balancing start difference
this->publish_state_(this->balancing_start_difference_sensor_, reg(0x1351) * 0.001f);
// Reg 4949: low state of charge alarm
this->publish_state_(this->low_state_of_charge_alarm_sensor_, reg(0x1355) * 0.1f);
// Reg 4952/4953: rated/total capacity already published via EIA, log only
ESP_LOGD(TAG, "Rated Capacity: %.2f Ah", reg(0x1358) * 0.01f);
ESP_LOGD(TAG, "Total Capacity: %.2f Ah", reg(0x1359) * 0.01f);
ESP_LOGD(TAG, "PCS Charge Current Limit: %u A", reg(0x1366));
ESP_LOGD(TAG, "PCS Discharge Current Limit: %d A", discharge_limit < 0 ? -discharge_limit : discharge_limit);
// Reg 4966: inverter charge current limit
this->publish_state_(this->inverter_charge_current_limit_sensor_, (float) reg(0x1366));
// Reg 4967: inverter discharge current limit (signed, magnitude)
int16_t discharge_limit = (int16_t) reg(0x1367);
this->publish_state_(this->inverter_discharge_current_limit_sensor_,
(float) (discharge_limit < 0 ? -discharge_limit : discharge_limit));
}

#ifdef USE_ESP32
Expand Down
33 changes: 33 additions & 0 deletions components/seplos_bms_v3_ble/seplos_bms_v3_ble.h
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,31 @@ class SeplosBmsV3Ble :
void set_charge_low_temperature_alarm_sensor(sensor::Sensor *charge_low_temperature_alarm_sensor) {
charge_low_temperature_alarm_sensor_ = charge_low_temperature_alarm_sensor;
}
void set_discharge_overtemperature_protection_sensor(sensor::Sensor *discharge_overtemperature_protection_sensor) {
discharge_overtemperature_protection_sensor_ = discharge_overtemperature_protection_sensor;
}
void set_environment_undertemperature_protection_sensor(
sensor::Sensor *environment_undertemperature_protection_sensor) {
environment_undertemperature_protection_sensor_ = environment_undertemperature_protection_sensor;
}
void set_mosfet_overtemperature_protection_sensor(sensor::Sensor *mosfet_overtemperature_protection_sensor) {
mosfet_overtemperature_protection_sensor_ = mosfet_overtemperature_protection_sensor;
}
void set_balancing_start_voltage_sensor(sensor::Sensor *balancing_start_voltage_sensor) {
balancing_start_voltage_sensor_ = balancing_start_voltage_sensor;
}
void set_balancing_start_difference_sensor(sensor::Sensor *balancing_start_difference_sensor) {
balancing_start_difference_sensor_ = balancing_start_difference_sensor;
}
void set_low_state_of_charge_alarm_sensor(sensor::Sensor *low_state_of_charge_alarm_sensor) {
low_state_of_charge_alarm_sensor_ = low_state_of_charge_alarm_sensor;
}
void set_inverter_charge_current_limit_sensor(sensor::Sensor *inverter_charge_current_limit_sensor) {
inverter_charge_current_limit_sensor_ = inverter_charge_current_limit_sensor;
}
void set_inverter_discharge_current_limit_sensor(sensor::Sensor *inverter_discharge_current_limit_sensor) {
inverter_discharge_current_limit_sensor_ = inverter_discharge_current_limit_sensor;
}

void set_min_cell_voltage_sensor(sensor::Sensor *min_cell_voltage_sensor) {
min_cell_voltage_sensor_ = min_cell_voltage_sensor;
Expand Down Expand Up @@ -324,6 +349,14 @@ class SeplosBmsV3Ble :
sensor::Sensor *discharge_overcurrent_protection_sensor_{nullptr};
sensor::Sensor *charge_overtemperature_protection_sensor_{nullptr};
sensor::Sensor *charge_low_temperature_alarm_sensor_{nullptr};
sensor::Sensor *discharge_overtemperature_protection_sensor_{nullptr};
sensor::Sensor *environment_undertemperature_protection_sensor_{nullptr};
sensor::Sensor *mosfet_overtemperature_protection_sensor_{nullptr};
sensor::Sensor *balancing_start_voltage_sensor_{nullptr};
sensor::Sensor *balancing_start_difference_sensor_{nullptr};
sensor::Sensor *low_state_of_charge_alarm_sensor_{nullptr};
sensor::Sensor *inverter_charge_current_limit_sensor_{nullptr};
sensor::Sensor *inverter_discharge_current_limit_sensor_{nullptr};

sensor::Sensor *min_cell_voltage_sensor_{nullptr};
sensor::Sensor *max_cell_voltage_sensor_{nullptr};
Expand Down
16 changes: 16 additions & 0 deletions esp32-seplos-v3-ble-example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,22 @@ sensor:
name: "charge overtemperature protection"
charge_low_temperature_alarm:
name: "charge low temperature alarm"
discharge_overtemperature_protection:
name: "discharge overtemperature protection"
environment_undertemperature_protection:
name: "environment undertemperature protection"
mosfet_overtemperature_protection:
name: "mosfet overtemperature protection"
balancing_start_voltage:
name: "balancing start voltage"
balancing_start_difference:
name: "balancing start difference"
low_state_of_charge_alarm:
name: "low state of charge alarm"
inverter_charge_current_limit:
name: "inverter charge current limit"
inverter_discharge_current_limit:
name: "inverter discharge current limit"

# Pack-specific sensors
- platform: seplos_bms_v3_ble_pack
Expand Down
1 change: 1 addition & 0 deletions tests/components/seplos_bms_v3_ble/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class TestableSeplosBmsV3Ble : public SeplosBmsV3Ble {
void decode_eic(const std::vector<uint8_t> &data) { decode_eic_data_(data); }
void decode_pct(const std::vector<uint8_t> &data) { decode_pct_data_(data); }
void decode_spa1(const std::vector<uint8_t> &data) { decode_spa1_data_(data); }
void decode_spa2(const std::vector<uint8_t> &data) { decode_spa2_data_(data); }
};

} // namespace esphome::seplos_bms_v3_ble::testing
6 changes: 3 additions & 3 deletions tests/components/seplos_bms_v3_ble/frames.h
Original file line number Diff line number Diff line change
Expand Up @@ -187,9 +187,9 @@ static const std::vector<uint8_t> SPA_DATA_1 = {
// SPA payload, second request (106 bytes) – System Parameters, registers 0x1335–0x1369
//
// Reg Field Raw Decoded
// 0x1350 Balancing Open Voltage 0x0D48 3400 mV
// 0x1351 Balancing Open Difference 0x0032 50 mV
// 0x1355 SOC Low Alarm 0x0032 5.0 %
// 0x1350 Balancing Start Voltage 0x0D48 3400 mV
// 0x1351 Balancing Start Difference 0x0032 50 mV
// 0x1355 Low State Of Charge Alarm 0x0032 5.0 %
// 0x1358 Rated Capacity 0x80E8 330.00 Ah
// 0x1359 Total Capacity 0x80E8 330.00 Ah
// 0x1366 PCS Charge Current Limit 0x00B4 180 A
Expand Down
48 changes: 48 additions & 0 deletions tests/components/seplos_bms_v3_ble/seplos_bms_v3_ble_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,54 @@ TEST(SeplosBmsV3BleSpa1Test, NullSensorsDoNotCrash) {
EXPECT_NO_FATAL_FAILURE(bms.decode_spa1(SPA_DATA_1));
}

// ── SPA2: configuration sensors (registers 0x1335–0x1369) ──────────────────────

TEST(SeplosBmsV3BleSpa2Test, TemperatureThresholds) {
TestableSeplosBmsV3Ble bms;
sensor::Sensor discharge_otp, environment_utp, mosfet_otp;
bms.set_discharge_overtemperature_protection_sensor(&discharge_otp);
bms.set_environment_undertemperature_protection_sensor(&environment_utp);
bms.set_mosfet_overtemperature_protection_sensor(&mosfet_otp);

bms.decode_spa2(SPA_DATA_2);

EXPECT_NEAR(discharge_otp.state, 60.0f, 0.1f);
EXPECT_NEAR(environment_utp.state, -10.1f, 0.1f);
EXPECT_NEAR(mosfet_otp.state, 110.0f, 0.1f);
}

TEST(SeplosBmsV3BleSpa2Test, BalancingParameters) {
TestableSeplosBmsV3Ble bms;
sensor::Sensor start_voltage, start_difference;
bms.set_balancing_start_voltage_sensor(&start_voltage);
bms.set_balancing_start_difference_sensor(&start_difference);

bms.decode_spa2(SPA_DATA_2);

EXPECT_NEAR(start_voltage.state, 3.400f, 0.001f);
EXPECT_NEAR(start_difference.state, 0.050f, 0.001f);
}

TEST(SeplosBmsV3BleSpa2Test, CapacityAndCurrentConfig) {
TestableSeplosBmsV3Ble bms;
sensor::Sensor low_state_of_charge_alarm, charge_limit, discharge_limit;
bms.set_low_state_of_charge_alarm_sensor(&low_state_of_charge_alarm);
bms.set_inverter_charge_current_limit_sensor(&charge_limit);
bms.set_inverter_discharge_current_limit_sensor(&discharge_limit);

bms.decode_spa2(SPA_DATA_2);

EXPECT_NEAR(low_state_of_charge_alarm.state, 5.0f, 0.1f);
EXPECT_FLOAT_EQ(charge_limit.state, 180.0f);
EXPECT_FLOAT_EQ(discharge_limit.state, 180.0f);
}

TEST(SeplosBmsV3BleSpa2Test, NullSensorsDoNotCrash) {
TestableSeplosBmsV3Ble bms;

EXPECT_NO_FATAL_FAILURE(bms.decode_spa2(SPA_DATA_2));
}

// ── Null sensors do not crash ─────────────────────────────────────────────────

TEST(SeplosBmsV3BleSafetyTest, NullSensorsDoNotCrash) {
Expand Down
2 changes: 1 addition & 1 deletion tests/test_component_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ def test_switches_dict(self):
class TestSeplosBmsV3BleSensorLists:
def test_sensor_defs_completeness(self):
assert "total_voltage" in v3_sensor.SENSOR_DEFS
assert len(v3_sensor.SENSOR_DEFS) == 54
assert len(v3_sensor.SENSOR_DEFS) == 62

def test_binary_sensor_defs_dict(self):
assert len(v3_binary_sensor.BINARY_SENSOR_DEFS) == 7
Expand Down