fix(homeassistant): resolve legacy device measurement keys

Resolve legacy underscore spellings for Home Assistant device measurements only when they uniquely match a registered EOS key; preserve canonical, custom, and ambiguous keys.
Correct the schema examples to show canonical hyphenated keys.
Add a synthetic regression test that exercises data acquisition without contacting Home Assistant.
This commit is contained in:
Wu Shuwen
2026-10-28 19:04:40 +01:00
committed by GitHub
parent 63e7e7e729
commit 727f20fdf0
2 changed files with 130 additions and 5 deletions
+39 -4
View File
@@ -6,7 +6,7 @@ from typing import Optional, Union
import pandas as pd
import requests
from loguru import logger
from pydantic import Field, computed_field, field_validator
from pydantic import Field, PrivateAttr, computed_field, field_validator
from akkudoktoreos.adapter.adapterabc import AdapterProvider
from akkudoktoreos.config.configabc import SettingsBaseModel
@@ -94,11 +94,15 @@ class HomeAssistantAdapterCommonSettings(SettingsBaseModel):
device_measurement_entity_ids: Optional[dict[str, str]] = Field(
default=None,
json_schema_extra={
"description": "Mapping of EOS measurement keys used by device (resource) simulations to Home Assistant entity IDs.",
"description": (
"Mapping of EOS device measurement keys to Home Assistant entity IDs. "
"Registered keys such as 'battery1-soc-factor' are accepted; legacy "
"underscore spellings are resolved when they uniquely match a registered key."
),
"examples": [
{
"ev11_soc_factor": "sensor.ev11_soc_factor",
"battery1_soc_factor": "sensor.battery1_soc_factor",
"ev11-soc-factor": "sensor.ev11_soc_factor",
"battery1-soc-factor": "sensor.battery1_soc_factor",
}
],
},
@@ -219,6 +223,8 @@ class HomeAssistantAdapterCommonSettings(SettingsBaseModel):
class HomeAssistantAdapter(AdapterProvider):
_legacy_device_measurement_key_warning_emitted: bool = PrivateAttr(default=False)
@classmethod
def provider_id(cls) -> str:
"""Return the unique identifier for the adapter provider."""
@@ -276,6 +282,22 @@ class HomeAssistantAdapter(AdapterProvider):
solution_entity_ids = []
return solution_entity_ids
@staticmethod
def _resolve_device_measurement_key(measurement_key: str, registered_keys: set[str]) -> str:
"""Resolve a legacy underscore alias to one registered device measurement key."""
if measurement_key in registered_keys:
return measurement_key
aliases = {key for key in registered_keys if key.replace("-", "_") == measurement_key}
if len(aliases) == 1:
return next(iter(aliases))
if len(aliases) > 1:
logger.warning(
"Ambiguous legacy device measurement key '{}'; leaving it unchanged.",
measurement_key,
)
return measurement_key
def _entity_id_from_resource_id(self, resource_id: str) -> str:
return HOMEASSISTANT_ENTITY_ID_PREFIX + resource_id
@@ -461,10 +483,23 @@ class HomeAssistantAdapter(AdapterProvider):
# Retrieve measurements necessary for device simulations
entity_ids = self.config.adapter.homeassistant.device_measurement_entity_ids
if entity_ids:
registered_measurement_keys = set(self.config.devices.measurement_keys)
for (
measurement_key,
entity_id,
) in entity_ids.items():
resolved_measurement_key = self._resolve_device_measurement_key(
measurement_key, registered_measurement_keys
)
if resolved_measurement_key != measurement_key:
if not self._legacy_device_measurement_key_warning_emitted:
logger.warning(
"Legacy device measurement key '{}' mapped to registered key '{}'.",
measurement_key,
resolved_measurement_key,
)
self._legacy_device_measurement_key_warning_emitted = True
measurement_key = resolved_measurement_key
if entity_id:
try:
state = self.get_entity_state(entity_id)