M8-R16: expose hot water to HA Energy
This commit is contained in:
@@ -1028,7 +1028,7 @@ def _m8_energy_provider(session: Session) -> list[ExposableEntity]:
|
|||||||
elif meter.commodity == "heating":
|
elif meter.commodity == "heating":
|
||||||
unit, device_class = "GJ", "energy"
|
unit, device_class = "GJ", "energy"
|
||||||
else:
|
else:
|
||||||
unit, device_class = "m³", "volume"
|
unit, device_class = "m³", "water"
|
||||||
for suffix, getter in (
|
for suffix, getter in (
|
||||||
("total", _meter_total_getter(binding.id, source.id, channel.id)),
|
("total", _meter_total_getter(binding.id, source.id, channel.id)),
|
||||||
("today", _meter_today_getter(binding.id, source.id, channel.id)),
|
("today", _meter_today_getter(binding.id, source.id, channel.id)),
|
||||||
|
|||||||
@@ -1214,6 +1214,12 @@ M8 收尾的前置条件。agent 不得执行、记录为已执行,或以 mock
|
|||||||
|
|
||||||
## 14. Post-M8 lifecycle repair(M8-R08~R10)
|
## 14. Post-M8 lifecycle repair(M8-R08~R10)
|
||||||
|
|
||||||
|
### M8-R16 — HA Water Energy Discovery metadata
|
||||||
|
|
||||||
|
active `hot_water` Meter 的 total/today MQTT Discovery metadata 从通用 `volume` 修正为 Home
|
||||||
|
Assistant Energy Water 所要求的 `water`;保留 `m³` 与 `total_increasing`。区域供暖继续使用合法的
|
||||||
|
`energy + GJ` 组合,thermal EUR 成本实体以及既有 key、identity、unique_id 和 topic 均不变。
|
||||||
|
|
||||||
### M8-R15 — HA Discovery identity/topic repair
|
### M8-R15 — HA Discovery identity/topic repair
|
||||||
|
|
||||||
修复 HA 将多值 `device.identifiers` 任一匹配合并的风险:Source、Meter、Modbus 与成本 epoch 均使用单一完整 namespaced identifier,内部 MQTT identity 独立保存。thermal 的既有点号 identity/key 保持兼容,node/object topic segment 单独规范化;仅对 v1.6.1 实际发布过的 dot topic 做可重试、成功后抑制的精确 retained cleanup。新增默认关闭的 `Thermal Hot Water Total/Today`,金额为 `hot_water_heating + hot_water`,不含 `hot_water_tax`。
|
修复 HA 将多值 `device.identifiers` 任一匹配合并的风险:Source、Meter、Modbus 与成本 epoch 均使用单一完整 namespaced identifier,内部 MQTT identity 独立保存。thermal 的既有点号 identity/key 保持兼容,node/object topic segment 单独规范化;仅对 v1.6.1 实际发布过的 dot topic 做可重试、成功后抑制的精确 retained cleanup。新增默认关闭的 `Thermal Hot Water Total/Today`,金额为 `hot_water_heating + hot_water`,不含 `hot_water_tax`。
|
||||||
|
|||||||
@@ -2788,7 +2788,11 @@ def test_m8_catalog_has_source_meter_and_thermal_entities_disabled(energy_db) ->
|
|||||||
assert entries[f"meter.{heating.uuid}.total"].entity.unit == "GJ"
|
assert entries[f"meter.{heating.uuid}.total"].entity.unit == "GJ"
|
||||||
assert entries[f"meter.{heating.uuid}.total"].entity.device_class == "energy"
|
assert entries[f"meter.{heating.uuid}.total"].entity.device_class == "energy"
|
||||||
assert entries[f"meter.{water.uuid}.total"].entity.unit == "m³"
|
assert entries[f"meter.{water.uuid}.total"].entity.unit == "m³"
|
||||||
assert entries[f"meter.{water.uuid}.total"].entity.device_class == "volume"
|
for suffix in ("total", "today"):
|
||||||
|
water_entity = entries[f"meter.{water.uuid}.{suffix}"].entity
|
||||||
|
assert water_entity.device_class == "water"
|
||||||
|
assert water_entity.unit == "m³"
|
||||||
|
assert water_entity.state_class == "total_increasing"
|
||||||
assert entries[f"meter.{electricity.uuid}.total"].entity.unit == "kWh"
|
assert entries[f"meter.{electricity.uuid}.total"].entity.unit == "kWh"
|
||||||
assert entries[f"meter.{electricity.uuid}.total"].entity.device_class == "energy"
|
assert entries[f"meter.{electricity.uuid}.total"].entity.device_class == "energy"
|
||||||
assert entries[f"meter.{electricity.uuid}.today"].entity.state_class == "total_increasing"
|
assert entries[f"meter.{electricity.uuid}.today"].entity.state_class == "total_increasing"
|
||||||
@@ -2812,6 +2816,31 @@ def test_m8_catalog_has_source_meter_and_thermal_entities_disabled(energy_db) ->
|
|||||||
assert all(entry.entity.unit == "EUR" and entry.entity.device_class == "monetary" for entry in thermal)
|
assert all(entry.entity.unit == "EUR" and entry.entity.device_class == "monetary" for entry in thermal)
|
||||||
|
|
||||||
|
|
||||||
|
def test_m8_hot_water_discovery_payload_is_ha_energy_compatible(energy_db) -> None:
|
||||||
|
"""Hot-water Meter discovery exposes the exact HA Energy Water metadata."""
|
||||||
|
from app.integrations.expose import build_catalog
|
||||||
|
from app.services.ha_discovery import build_discovery_payload
|
||||||
|
|
||||||
|
now = datetime.now(timezone.utc)
|
||||||
|
with Session(energy_db) as session:
|
||||||
|
_source, _channel, meter = _make_thermal_source_and_meter(session, "hot_water", now)
|
||||||
|
session.commit()
|
||||||
|
entries = {item.entity.key: item.entity for item in build_catalog(session)}
|
||||||
|
|
||||||
|
for suffix in ("total", "today"):
|
||||||
|
entity = entries[f"meter.{meter.uuid}.{suffix}"]
|
||||||
|
topic, payload = build_discovery_payload(entity, "homeassistant")
|
||||||
|
assert payload["device_class"] == "water"
|
||||||
|
assert payload["unit_of_measurement"] == "m³"
|
||||||
|
assert payload["state_class"] == "total_increasing"
|
||||||
|
assert payload["unique_id"] == f"{meter.uuid}_meter_{meter.uuid}_{suffix}"
|
||||||
|
assert payload["device"]["identifiers"] == [f"home-automation:meter:{meter.uuid}"]
|
||||||
|
assert topic == (
|
||||||
|
f"homeassistant/sensor/{meter.uuid.replace('-', '_')}/"
|
||||||
|
f"meter_{meter.uuid.replace('-', '_')}_{suffix}/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_m8_meter_getter_hides_stale_or_offline_source(energy_db) -> None:
|
def test_m8_meter_getter_hides_stale_or_offline_source(energy_db) -> None:
|
||||||
from app.integrations.expose import build_catalog
|
from app.integrations.expose import build_catalog
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user