Rename bms_temperature_celcius to bms_temperature_celsius
Correct the spelling in the JBD and EcoFlow snapshots, the diagnostic tool, and the tests. UPS.from_dict maps the old name to the new name, so a client still reads data from an older server during an upgrade. InfluxDB and Prometheus now receive this measurement under the new name.
This commit is contained in:
+7
-1
@@ -44,6 +44,10 @@ class BMSInfoField:
|
||||
|
||||
type BMSField = BMSScalarField | BMSMultiField | BMSInfoField
|
||||
|
||||
# Corrected field spellings: old name to current name. An older server can
|
||||
# still send the old name during an upgrade.
|
||||
RENAMED_FIELDS = {"bms_temperature_celcius": "bms_temperature_celsius"}
|
||||
|
||||
|
||||
def _field_from_dict(d: dict[str, Any]) -> BMSField:
|
||||
"""Reconstruct a BMSField from its JSON-serialized dict."""
|
||||
@@ -69,7 +73,9 @@ class UPS(ABC):
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict[str, Any]) -> "UPS":
|
||||
"""Reconstruct a UPS snapshot from a JSON-decoded field dict."""
|
||||
return _UPSSnapshot({name: _field_from_dict(v) for name, v in data.items()})
|
||||
return _UPSSnapshot({
|
||||
RENAMED_FIELDS.get(name, name): _field_from_dict(v) for name, v in data.items()
|
||||
})
|
||||
|
||||
|
||||
class _UPSSnapshot(UPS):
|
||||
|
||||
@@ -353,7 +353,7 @@ class River2Decoder:
|
||||
else:
|
||||
charge_ratio(values["soc"], priority=1)
|
||||
temp = values["temp"]
|
||||
snapshot.bms_temperature_celcius = BMSMultiField(
|
||||
snapshot.bms_temperature_celsius = BMSMultiField(
|
||||
help="Battery Temperature", label="sensor",
|
||||
raw_values={1: temp}, values={1: str(temp)}, units="°C",
|
||||
)
|
||||
|
||||
@@ -305,7 +305,7 @@ class River3Decoder:
|
||||
for source, priority in (("cms_batt_temp", 1), ("bms_max_cell_temp", 2)):
|
||||
temp = values.get(source)
|
||||
if temp is not None and priority >= self._river3_temp_priority:
|
||||
snapshot.bms_temperature_celcius = BMSMultiField(
|
||||
snapshot.bms_temperature_celsius = BMSMultiField(
|
||||
help="Battery Temperature", label="sensor",
|
||||
raw_values={1: temp}, values={1: str(temp)}, units="°C",
|
||||
)
|
||||
|
||||
@@ -23,7 +23,7 @@ class EcoFlowSnapshot(UPS):
|
||||
ef_packets_received: BMSScalarField | None = None
|
||||
|
||||
bms_capacity_charge_ratio: BMSScalarField | None = None
|
||||
bms_temperature_celcius: BMSMultiField | None = None
|
||||
bms_temperature_celsius: BMSMultiField | None = None
|
||||
bms_charge_is_charging: BMSScalarField | None = None
|
||||
bms_charge_is_discharging: BMSScalarField | None = None
|
||||
ef_input_power_watts: BMSScalarField | None = None
|
||||
|
||||
+1
-1
@@ -295,7 +295,7 @@ def parse_03_response(response: bytearray, debug: int = 0) -> JBDBMS | bool:
|
||||
result.bms_temperature_sensor_num = BMSScalarField(
|
||||
help="Temperature Sensors", raw_value=ntc_num, value="{0}".format(ntc_num)
|
||||
)
|
||||
result.bms_temperature_celcius = BMSMultiField(
|
||||
result.bms_temperature_celsius = BMSMultiField(
|
||||
help="Temperature",
|
||||
label="sensor",
|
||||
raw_values={i + 1: temp for i, temp in enumerate(temperatures)},
|
||||
|
||||
@@ -33,7 +33,7 @@ class JBDBMS(UPS):
|
||||
bms_cell_number: BMSScalarField | None = None
|
||||
bms_cells_balancing: BMSMultiField | None = None
|
||||
bms_temperature_sensor_num: BMSScalarField | None = None
|
||||
bms_temperature_celcius: BMSMultiField | None = None
|
||||
bms_temperature_celsius: BMSMultiField | None = None
|
||||
bms_voltage_cells_volts: BMSMultiField | None = None
|
||||
|
||||
def items(self) -> Iterator[tuple[str, BMSField]]:
|
||||
|
||||
@@ -19,7 +19,7 @@ rules for interpreting the device reports.
|
||||
|---|---|
|
||||
| `ef_input_power_watts` | Total device input power, W. |
|
||||
| `ef_output_power_watts` | Total device output power, W. |
|
||||
| `bms_temperature_celcius` | Battery temperature at sensor 1, °C. The spelling preserves the existing field name. |
|
||||
| `bms_temperature_celsius` | Battery temperature at sensor 1, °C. Versions before this rename wrote `bms_temperature_celcius`. |
|
||||
| `bms_capacity_charge_ratio` | Battery charge as a ratio from 0 to 1. |
|
||||
| `ef_remaining_time_minutes` | General remaining-time estimate, minutes. |
|
||||
| `ef_charge_remaining_time_minutes` | Charging-time estimate from the energy management module, minutes. |
|
||||
|
||||
+1
-1
@@ -68,7 +68,7 @@ def populated_jbdbms() -> JBDBMS:
|
||||
bms.bms_manufacture_date = BMSInfoField(
|
||||
help="Date of Manufacture", info="2023-01-15"
|
||||
)
|
||||
bms.bms_temperature_celcius = BMSMultiField(
|
||||
bms.bms_temperature_celsius = BMSMultiField(
|
||||
help="Temperature",
|
||||
label="sensor",
|
||||
raw_values={1: 25.0},
|
||||
|
||||
@@ -103,7 +103,7 @@ def test_legacy_numeric_decoder_entry_points(model, packets):
|
||||
|
||||
@pytest.mark.parametrize("decoder_type,high_packet,low_packet,field,high,low", [
|
||||
(ecoflow_river2.River2Decoder, EMS_PACKET, PD_PACKET, "bms_capacity_charge_ratio", 0.875, 0.47),
|
||||
(ecoflow_river3.River3Decoder, CELL_TEMP_PACKET, COMPOSITE_TEMP_PACKET, "bms_temperature_celcius", 29, 20),
|
||||
(ecoflow_river3.River3Decoder, CELL_TEMP_PACKET, COMPOSITE_TEMP_PACKET, "bms_temperature_celsius", 29, 20),
|
||||
], ids=["river2_soc", "river3_temperature"])
|
||||
def test_measurement_priorities_are_per_instance_and_reset_per_connection(
|
||||
decoder_type, high_packet, low_packet, field, high, low,
|
||||
|
||||
@@ -145,6 +145,11 @@ class TestUPS:
|
||||
assert isinstance(items["bms_temps"], BMSMultiField)
|
||||
assert items["bms_temps"].raw_values == {1: 25.0}
|
||||
|
||||
def test_from_dict_corrects_renamed_field(self):
|
||||
field = {"help": "Temperature", "label": "sensor", "raw_values": {1: 25.0}, "values": {1: "25.00"}}
|
||||
ups = UPS.from_dict({"bms_temperature_celcius": field})
|
||||
assert [name for name, _ in ups.items()] == ["bms_temperature_celsius"]
|
||||
|
||||
def test_bool_true_when_populated(self):
|
||||
assert bool(self._snapshot()) is True
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ MOCK_RESPONSE = {
|
||||
"help": "Date of Manufacture",
|
||||
"info": "2023-01-15",
|
||||
},
|
||||
"bms_temperature_celcius": {
|
||||
"bms_temperature_celsius": {
|
||||
"help": "Temperature",
|
||||
"label": "sensor",
|
||||
"raw_values": {1: 25.0},
|
||||
@@ -70,7 +70,7 @@ class TestReadData:
|
||||
def test_multi_field_deserialized(self):
|
||||
result = self._call()
|
||||
items = dict(result["myups"].items())
|
||||
f = items["bms_temperature_celcius"]
|
||||
f = items["bms_temperature_celsius"]
|
||||
assert isinstance(f, BMSMultiField)
|
||||
assert f.raw_values == {1: 25.0}
|
||||
assert f.label == "sensor"
|
||||
|
||||
@@ -217,8 +217,8 @@ def test_snapshot_contains_power_temperature_and_times():
|
||||
assert snap.ef_input_power_watts.units == "W"
|
||||
assert snap.ef_output_power_watts.raw_value == 42
|
||||
assert snap.bms_capacity_charge_ratio.raw_value == 0.7325
|
||||
assert snap.bms_temperature_celcius.raw_values == {1: 29}
|
||||
assert snap.bms_temperature_celcius.units == "°C"
|
||||
assert snap.bms_temperature_celsius.raw_values == {1: 29}
|
||||
assert snap.bms_temperature_celsius.units == "°C"
|
||||
assert snap.ef_remaining_time_minutes.raw_value == 87
|
||||
assert snap.ef_charge_remaining_time_minutes.raw_value == 87
|
||||
assert snap.ef_discharge_remaining_time_minutes.raw_value == 240
|
||||
@@ -261,7 +261,7 @@ def test_unavailable_times_clear_previous_values_and_zero_is_valid():
|
||||
def test_short_bms_has_temperature_without_inventing_charge_status():
|
||||
dev = device()
|
||||
dev._record(heartbeat(3, bms_payload()[:21], 0x32))
|
||||
assert dev.snapshot.bms_temperature_celcius.raw_values == {1: 29}
|
||||
assert dev.snapshot.bms_temperature_celsius.raw_values == {1: 29}
|
||||
assert dev.snapshot.bms_capacity_charge_ratio.raw_value == 0.73
|
||||
assert dev.snapshot.bms_charge_is_charging is None
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ def test_river2_bypass_burst_without_inverter_finishes_immediately(script, monke
|
||||
snap = dev.snapshot
|
||||
assert snap.ef_packets_received.raw_value == 5
|
||||
assert snap.bms_capacity_charge_ratio.raw_value == pytest.approx(0.8775)
|
||||
assert snap.bms_temperature_celcius.raw_values == {1: 28}
|
||||
assert snap.bms_temperature_celsius.raw_values == {1: 28}
|
||||
assert snap.bms_charge_is_charging.raw_value is False
|
||||
assert snap.bms_charge_is_discharging.raw_value is False
|
||||
assert snap.ef_input_power_watts.raw_value == snap.ef_output_power_watts.raw_value == 35
|
||||
|
||||
@@ -111,7 +111,7 @@ def test_river3_snapshot_and_serialization(prefix):
|
||||
snap = dev.snapshot
|
||||
assert dev._pkt_version == 3
|
||||
assert snap.bms_capacity_charge_ratio.raw_value == pytest.approx(0.88918)
|
||||
assert snap.bms_temperature_celcius.raw_values == {1: 29}
|
||||
assert snap.bms_temperature_celsius.raw_values == {1: 29}
|
||||
assert snap.ef_input_power_watts.raw_value == 244
|
||||
assert snap.ef_output_power_watts.raw_value == 39
|
||||
assert snap.bms_charge_is_charging.raw_value is True
|
||||
@@ -142,7 +142,7 @@ def test_partial_updates_keep_missing_values_and_preserve_explicit_zero():
|
||||
assert dev.snapshot.bms_charge_is_charging.raw_value is False
|
||||
assert dev.snapshot.bms_charge_is_discharging.raw_value is False
|
||||
assert dev.snapshot.ef_remaining_time_minutes is None
|
||||
assert dev.snapshot.bms_temperature_celcius.raw_values == {1: -5}
|
||||
assert dev.snapshot.bms_temperature_celsius.raw_values == {1: -5}
|
||||
assert dev.snapshot.ef_output_power_watts.raw_value == 44
|
||||
|
||||
|
||||
@@ -150,9 +150,9 @@ def test_cell_temperature_remains_preferred_across_partial_updates():
|
||||
dev = device()
|
||||
dev._record(report(integer(259, 29)))
|
||||
dev._record(report(integer(102, 26)))
|
||||
assert dev.snapshot.bms_temperature_celcius.raw_values == {1: 29}
|
||||
assert dev.snapshot.bms_temperature_celsius.raw_values == {1: 29}
|
||||
dev._record(report(integer(259, 30)))
|
||||
assert dev.snapshot.bms_temperature_celcius.raw_values == {1: 30}
|
||||
assert dev.snapshot.bms_temperature_celsius.raw_values == {1: 30}
|
||||
|
||||
|
||||
def test_remaining_time_tracks_battery_direction_and_missing_estimates():
|
||||
@@ -184,7 +184,7 @@ def test_no_load_sample_matches_display_hours_without_changing_minute_units():
|
||||
dev._record(report(integer(363, 1)))
|
||||
snap = dev.snapshot
|
||||
assert snap.bms_capacity_charge_ratio.raw_value == pytest.approx(0.89)
|
||||
assert snap.bms_temperature_celcius.raw_values == {1: 29}
|
||||
assert snap.bms_temperature_celsius.raw_values == {1: 29}
|
||||
assert snap.ef_input_power_watts.raw_value == 0
|
||||
assert snap.ef_output_power_watts.raw_value == 0
|
||||
assert snap.bms_charge_is_charging.raw_value is False
|
||||
|
||||
@@ -153,13 +153,13 @@ class TestJBDBMS:
|
||||
assert "bms_voltage_total_volts" in keys
|
||||
assert "bms_current_amps" in keys
|
||||
assert "bms_manufacture_date" in keys
|
||||
assert "bms_temperature_celcius" in keys
|
||||
assert "bms_temperature_celsius" in keys
|
||||
|
||||
def test_items_yields_correct_types(self, populated_jbdbms):
|
||||
d = dict(populated_jbdbms.items())
|
||||
assert isinstance(d["bms_voltage_total_volts"], BMSScalarField)
|
||||
assert isinstance(d["bms_manufacture_date"], BMSInfoField)
|
||||
assert isinstance(d["bms_temperature_celcius"], BMSMultiField)
|
||||
assert isinstance(d["bms_temperature_celsius"], BMSMultiField)
|
||||
|
||||
def test_is_ups_subclass(self):
|
||||
assert isinstance(JBDBMS(), UPS)
|
||||
@@ -209,8 +209,8 @@ class TestParse03Response:
|
||||
|
||||
def test_temperature(self, valid_03_response):
|
||||
result = parse_03_response(valid_03_response)
|
||||
assert result.bms_temperature_celcius.raw_values[1] == pytest.approx(25.0)
|
||||
assert result.bms_temperature_celcius.units == "°C"
|
||||
assert result.bms_temperature_celsius.raw_values[1] == pytest.approx(25.0)
|
||||
assert result.bms_temperature_celsius.units == "°C"
|
||||
|
||||
def test_mosfet_charging(self, valid_03_response):
|
||||
result = parse_03_response(valid_03_response)
|
||||
@@ -379,9 +379,9 @@ class TestParse03ProtectionBits:
|
||||
_recompute_checksum(response)
|
||||
result = parse_03_response(response)
|
||||
assert isinstance(result, JBDBMS)
|
||||
assert len(result.bms_temperature_celcius.raw_values) == 2
|
||||
assert result.bms_temperature_celcius.raw_values[1] == pytest.approx(25.0)
|
||||
assert result.bms_temperature_celcius.raw_values[2] == pytest.approx(19.2)
|
||||
assert len(result.bms_temperature_celsius.raw_values) == 2
|
||||
assert result.bms_temperature_celsius.raw_values[1] == pytest.approx(25.0)
|
||||
assert result.bms_temperature_celsius.raw_values[2] == pytest.approx(19.2)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
+1
-1
@@ -72,7 +72,7 @@ MODEL_PROFILES = {
|
||||
# RuntimePropertyUpload (0x16) is not confirmed for River 3.
|
||||
"expected_fields": {
|
||||
"ef_pkt_02_fe_15", "bms_capacity_charge_ratio",
|
||||
"bms_temperature_celcius", "ef_input_power_watts",
|
||||
"bms_temperature_celsius", "ef_input_power_watts",
|
||||
"ef_output_power_watts", "bms_charge_is_charging",
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user