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
|
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:
|
def _field_from_dict(d: dict[str, Any]) -> BMSField:
|
||||||
"""Reconstruct a BMSField from its JSON-serialized dict."""
|
"""Reconstruct a BMSField from its JSON-serialized dict."""
|
||||||
@@ -69,7 +73,9 @@ class UPS(ABC):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, data: dict[str, Any]) -> "UPS":
|
def from_dict(cls, data: dict[str, Any]) -> "UPS":
|
||||||
"""Reconstruct a UPS snapshot from a JSON-decoded field dict."""
|
"""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):
|
class _UPSSnapshot(UPS):
|
||||||
|
|||||||
@@ -353,7 +353,7 @@ class River2Decoder:
|
|||||||
else:
|
else:
|
||||||
charge_ratio(values["soc"], priority=1)
|
charge_ratio(values["soc"], priority=1)
|
||||||
temp = values["temp"]
|
temp = values["temp"]
|
||||||
snapshot.bms_temperature_celcius = BMSMultiField(
|
snapshot.bms_temperature_celsius = BMSMultiField(
|
||||||
help="Battery Temperature", label="sensor",
|
help="Battery Temperature", label="sensor",
|
||||||
raw_values={1: temp}, values={1: str(temp)}, units="°C",
|
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)):
|
for source, priority in (("cms_batt_temp", 1), ("bms_max_cell_temp", 2)):
|
||||||
temp = values.get(source)
|
temp = values.get(source)
|
||||||
if temp is not None and priority >= self._river3_temp_priority:
|
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",
|
help="Battery Temperature", label="sensor",
|
||||||
raw_values={1: temp}, values={1: str(temp)}, units="°C",
|
raw_values={1: temp}, values={1: str(temp)}, units="°C",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class EcoFlowSnapshot(UPS):
|
|||||||
ef_packets_received: BMSScalarField | None = None
|
ef_packets_received: BMSScalarField | None = None
|
||||||
|
|
||||||
bms_capacity_charge_ratio: 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_charging: BMSScalarField | None = None
|
||||||
bms_charge_is_discharging: BMSScalarField | None = None
|
bms_charge_is_discharging: BMSScalarField | None = None
|
||||||
ef_input_power_watts: 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(
|
result.bms_temperature_sensor_num = BMSScalarField(
|
||||||
help="Temperature Sensors", raw_value=ntc_num, value="{0}".format(ntc_num)
|
help="Temperature Sensors", raw_value=ntc_num, value="{0}".format(ntc_num)
|
||||||
)
|
)
|
||||||
result.bms_temperature_celcius = BMSMultiField(
|
result.bms_temperature_celsius = BMSMultiField(
|
||||||
help="Temperature",
|
help="Temperature",
|
||||||
label="sensor",
|
label="sensor",
|
||||||
raw_values={i + 1: temp for i, temp in enumerate(temperatures)},
|
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_cell_number: BMSScalarField | None = None
|
||||||
bms_cells_balancing: BMSMultiField | None = None
|
bms_cells_balancing: BMSMultiField | None = None
|
||||||
bms_temperature_sensor_num: BMSScalarField | 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
|
bms_voltage_cells_volts: BMSMultiField | None = None
|
||||||
|
|
||||||
def items(self) -> Iterator[tuple[str, BMSField]]:
|
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_input_power_watts` | Total device input power, W. |
|
||||||
| `ef_output_power_watts` | Total device output 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. |
|
| `bms_capacity_charge_ratio` | Battery charge as a ratio from 0 to 1. |
|
||||||
| `ef_remaining_time_minutes` | General remaining-time estimate, minutes. |
|
| `ef_remaining_time_minutes` | General remaining-time estimate, minutes. |
|
||||||
| `ef_charge_remaining_time_minutes` | Charging-time estimate from the energy management module, 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(
|
bms.bms_manufacture_date = BMSInfoField(
|
||||||
help="Date of Manufacture", info="2023-01-15"
|
help="Date of Manufacture", info="2023-01-15"
|
||||||
)
|
)
|
||||||
bms.bms_temperature_celcius = BMSMultiField(
|
bms.bms_temperature_celsius = BMSMultiField(
|
||||||
help="Temperature",
|
help="Temperature",
|
||||||
label="sensor",
|
label="sensor",
|
||||||
raw_values={1: 25.0},
|
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", [
|
@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_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"])
|
], ids=["river2_soc", "river3_temperature"])
|
||||||
def test_measurement_priorities_are_per_instance_and_reset_per_connection(
|
def test_measurement_priorities_are_per_instance_and_reset_per_connection(
|
||||||
decoder_type, high_packet, low_packet, field, high, low,
|
decoder_type, high_packet, low_packet, field, high, low,
|
||||||
|
|||||||
@@ -145,6 +145,11 @@ class TestUPS:
|
|||||||
assert isinstance(items["bms_temps"], BMSMultiField)
|
assert isinstance(items["bms_temps"], BMSMultiField)
|
||||||
assert items["bms_temps"].raw_values == {1: 25.0}
|
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):
|
def test_bool_true_when_populated(self):
|
||||||
assert bool(self._snapshot()) is True
|
assert bool(self._snapshot()) is True
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ MOCK_RESPONSE = {
|
|||||||
"help": "Date of Manufacture",
|
"help": "Date of Manufacture",
|
||||||
"info": "2023-01-15",
|
"info": "2023-01-15",
|
||||||
},
|
},
|
||||||
"bms_temperature_celcius": {
|
"bms_temperature_celsius": {
|
||||||
"help": "Temperature",
|
"help": "Temperature",
|
||||||
"label": "sensor",
|
"label": "sensor",
|
||||||
"raw_values": {1: 25.0},
|
"raw_values": {1: 25.0},
|
||||||
@@ -70,7 +70,7 @@ class TestReadData:
|
|||||||
def test_multi_field_deserialized(self):
|
def test_multi_field_deserialized(self):
|
||||||
result = self._call()
|
result = self._call()
|
||||||
items = dict(result["myups"].items())
|
items = dict(result["myups"].items())
|
||||||
f = items["bms_temperature_celcius"]
|
f = items["bms_temperature_celsius"]
|
||||||
assert isinstance(f, BMSMultiField)
|
assert isinstance(f, BMSMultiField)
|
||||||
assert f.raw_values == {1: 25.0}
|
assert f.raw_values == {1: 25.0}
|
||||||
assert f.label == "sensor"
|
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_input_power_watts.units == "W"
|
||||||
assert snap.ef_output_power_watts.raw_value == 42
|
assert snap.ef_output_power_watts.raw_value == 42
|
||||||
assert snap.bms_capacity_charge_ratio.raw_value == 0.7325
|
assert snap.bms_capacity_charge_ratio.raw_value == 0.7325
|
||||||
assert snap.bms_temperature_celcius.raw_values == {1: 29}
|
assert snap.bms_temperature_celsius.raw_values == {1: 29}
|
||||||
assert snap.bms_temperature_celcius.units == "°C"
|
assert snap.bms_temperature_celsius.units == "°C"
|
||||||
assert snap.ef_remaining_time_minutes.raw_value == 87
|
assert snap.ef_remaining_time_minutes.raw_value == 87
|
||||||
assert snap.ef_charge_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
|
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():
|
def test_short_bms_has_temperature_without_inventing_charge_status():
|
||||||
dev = device()
|
dev = device()
|
||||||
dev._record(heartbeat(3, bms_payload()[:21], 0x32))
|
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_capacity_charge_ratio.raw_value == 0.73
|
||||||
assert dev.snapshot.bms_charge_is_charging is None
|
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
|
snap = dev.snapshot
|
||||||
assert snap.ef_packets_received.raw_value == 5
|
assert snap.ef_packets_received.raw_value == 5
|
||||||
assert snap.bms_capacity_charge_ratio.raw_value == pytest.approx(0.8775)
|
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_charging.raw_value is False
|
||||||
assert snap.bms_charge_is_discharging.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
|
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
|
snap = dev.snapshot
|
||||||
assert dev._pkt_version == 3
|
assert dev._pkt_version == 3
|
||||||
assert snap.bms_capacity_charge_ratio.raw_value == pytest.approx(0.88918)
|
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_input_power_watts.raw_value == 244
|
||||||
assert snap.ef_output_power_watts.raw_value == 39
|
assert snap.ef_output_power_watts.raw_value == 39
|
||||||
assert snap.bms_charge_is_charging.raw_value is True
|
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_charging.raw_value is False
|
||||||
assert dev.snapshot.bms_charge_is_discharging.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.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
|
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 = device()
|
||||||
dev._record(report(integer(259, 29)))
|
dev._record(report(integer(259, 29)))
|
||||||
dev._record(report(integer(102, 26)))
|
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)))
|
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():
|
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)))
|
dev._record(report(integer(363, 1)))
|
||||||
snap = dev.snapshot
|
snap = dev.snapshot
|
||||||
assert snap.bms_capacity_charge_ratio.raw_value == pytest.approx(0.89)
|
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_input_power_watts.raw_value == 0
|
||||||
assert snap.ef_output_power_watts.raw_value == 0
|
assert snap.ef_output_power_watts.raw_value == 0
|
||||||
assert snap.bms_charge_is_charging.raw_value is False
|
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_voltage_total_volts" in keys
|
||||||
assert "bms_current_amps" in keys
|
assert "bms_current_amps" in keys
|
||||||
assert "bms_manufacture_date" 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):
|
def test_items_yields_correct_types(self, populated_jbdbms):
|
||||||
d = dict(populated_jbdbms.items())
|
d = dict(populated_jbdbms.items())
|
||||||
assert isinstance(d["bms_voltage_total_volts"], BMSScalarField)
|
assert isinstance(d["bms_voltage_total_volts"], BMSScalarField)
|
||||||
assert isinstance(d["bms_manufacture_date"], BMSInfoField)
|
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):
|
def test_is_ups_subclass(self):
|
||||||
assert isinstance(JBDBMS(), UPS)
|
assert isinstance(JBDBMS(), UPS)
|
||||||
@@ -209,8 +209,8 @@ class TestParse03Response:
|
|||||||
|
|
||||||
def test_temperature(self, valid_03_response):
|
def test_temperature(self, valid_03_response):
|
||||||
result = parse_03_response(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_celsius.raw_values[1] == pytest.approx(25.0)
|
||||||
assert result.bms_temperature_celcius.units == "°C"
|
assert result.bms_temperature_celsius.units == "°C"
|
||||||
|
|
||||||
def test_mosfet_charging(self, valid_03_response):
|
def test_mosfet_charging(self, valid_03_response):
|
||||||
result = parse_03_response(valid_03_response)
|
result = parse_03_response(valid_03_response)
|
||||||
@@ -379,9 +379,9 @@ class TestParse03ProtectionBits:
|
|||||||
_recompute_checksum(response)
|
_recompute_checksum(response)
|
||||||
result = parse_03_response(response)
|
result = parse_03_response(response)
|
||||||
assert isinstance(result, JBDBMS)
|
assert isinstance(result, JBDBMS)
|
||||||
assert len(result.bms_temperature_celcius.raw_values) == 2
|
assert len(result.bms_temperature_celsius.raw_values) == 2
|
||||||
assert result.bms_temperature_celcius.raw_values[1] == pytest.approx(25.0)
|
assert result.bms_temperature_celsius.raw_values[1] == pytest.approx(25.0)
|
||||||
assert result.bms_temperature_celcius.raw_values[2] == pytest.approx(19.2)
|
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.
|
# RuntimePropertyUpload (0x16) is not confirmed for River 3.
|
||||||
"expected_fields": {
|
"expected_fields": {
|
||||||
"ef_pkt_02_fe_15", "bms_capacity_charge_ratio",
|
"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",
|
"ef_output_power_watts", "bms_charge_is_charging",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user