|
|
@@ -110,34 +110,56 @@ class HomeAssistantService:
|
|
|
return False
|
|
|
|
|
|
async def get_energy(self, plug: "SmartPlug") -> dict | None:
|
|
|
- """Get energy data from HA entity attributes.
|
|
|
+ """Get energy data from HA sensor entities or switch attributes.
|
|
|
|
|
|
- HA entities may have power attributes - check common patterns.
|
|
|
+ First tries dedicated sensor entities if configured, then falls back
|
|
|
+ to checking the switch entity's attributes.
|
|
|
Returns dict with energy data or None if not available.
|
|
|
"""
|
|
|
if not self.base_url or not self.token:
|
|
|
return None
|
|
|
|
|
|
+ power = None
|
|
|
+ today = None
|
|
|
+ total = None
|
|
|
+
|
|
|
try:
|
|
|
async with httpx.AsyncClient(timeout=self.timeout) as client:
|
|
|
- response = await client.get(
|
|
|
- f"{self.base_url}/api/states/{plug.ha_entity_id}",
|
|
|
- headers=self._headers(),
|
|
|
- )
|
|
|
- response.raise_for_status()
|
|
|
- attrs = response.json().get("attributes", {})
|
|
|
+ # Fetch power from dedicated sensor entity if configured
|
|
|
+ if plug.ha_power_entity:
|
|
|
+ power = await self._get_sensor_value(client, plug.ha_power_entity)
|
|
|
+
|
|
|
+ # Fetch today's energy from dedicated sensor entity if configured
|
|
|
+ if plug.ha_energy_today_entity:
|
|
|
+ today = await self._get_sensor_value(client, plug.ha_energy_today_entity)
|
|
|
+
|
|
|
+ # Fetch total energy from dedicated sensor entity if configured
|
|
|
+ if plug.ha_energy_total_entity:
|
|
|
+ total = await self._get_sensor_value(client, plug.ha_energy_total_entity)
|
|
|
+
|
|
|
+ # Fallback: try switch entity attributes (original behavior)
|
|
|
+ if power is None:
|
|
|
+ response = await client.get(
|
|
|
+ f"{self.base_url}/api/states/{plug.ha_entity_id}",
|
|
|
+ headers=self._headers(),
|
|
|
+ )
|
|
|
+ response.raise_for_status()
|
|
|
+ attrs = response.json().get("attributes", {})
|
|
|
+ power = attrs.get("current_power_w") or attrs.get("power")
|
|
|
+ if today is None:
|
|
|
+ today = attrs.get("today_energy_kwh")
|
|
|
+ if total is None:
|
|
|
+ total = attrs.get("total_energy_kwh")
|
|
|
|
|
|
- # Common HA power monitoring attributes
|
|
|
- power = attrs.get("current_power_w") or attrs.get("power")
|
|
|
if power is None:
|
|
|
return None
|
|
|
|
|
|
return {
|
|
|
"power": power,
|
|
|
- "voltage": attrs.get("voltage"),
|
|
|
- "current": attrs.get("current"),
|
|
|
- "today": attrs.get("today_energy_kwh"),
|
|
|
- "total": attrs.get("total_energy_kwh"),
|
|
|
+ "voltage": None,
|
|
|
+ "current": None,
|
|
|
+ "today": today,
|
|
|
+ "total": total,
|
|
|
"yesterday": None,
|
|
|
"factor": None,
|
|
|
"apparent_power": None,
|
|
|
@@ -146,6 +168,21 @@ class HomeAssistantService:
|
|
|
except Exception:
|
|
|
return None
|
|
|
|
|
|
+ async def _get_sensor_value(self, client: httpx.AsyncClient, entity_id: str) -> float | None:
|
|
|
+ """Fetch numeric value from a HA sensor entity."""
|
|
|
+ try:
|
|
|
+ response = await client.get(
|
|
|
+ f"{self.base_url}/api/states/{entity_id}",
|
|
|
+ headers=self._headers(),
|
|
|
+ )
|
|
|
+ response.raise_for_status()
|
|
|
+ state = response.json().get("state")
|
|
|
+ if state and state not in ("unknown", "unavailable"):
|
|
|
+ return float(state)
|
|
|
+ except Exception:
|
|
|
+ pass
|
|
|
+ return None
|
|
|
+
|
|
|
async def test_connection(self, url: str, token: str) -> dict:
|
|
|
"""Test connection to Home Assistant.
|
|
|
|
|
|
@@ -216,6 +253,52 @@ class HomeAssistantService:
|
|
|
logger.warning(f"Failed to list HA entities: {e}")
|
|
|
return []
|
|
|
|
|
|
+ async def list_sensor_entities(self, url: str, token: str) -> list[dict]:
|
|
|
+ """List available sensor entities for energy monitoring.
|
|
|
+
|
|
|
+ Returns list of sensor entities with power/energy units.
|
|
|
+ """
|
|
|
+ try:
|
|
|
+ async with httpx.AsyncClient(timeout=self.timeout) as client:
|
|
|
+ response = await client.get(
|
|
|
+ f"{url.rstrip('/')}/api/states",
|
|
|
+ headers={"Authorization": f"Bearer {token}"},
|
|
|
+ )
|
|
|
+ response.raise_for_status()
|
|
|
+
|
|
|
+ # Valid units for energy monitoring sensors
|
|
|
+ power_units = {"W", "kW", "mW"}
|
|
|
+ energy_units = {"kWh", "Wh", "MWh"}
|
|
|
+ valid_units = power_units | energy_units
|
|
|
+
|
|
|
+ entities = []
|
|
|
+ for entity in response.json():
|
|
|
+ entity_id = entity.get("entity_id", "")
|
|
|
+ domain = entity_id.split(".")[0] if "." in entity_id else ""
|
|
|
+
|
|
|
+ # Filter to sensor domain only
|
|
|
+ if domain != "sensor":
|
|
|
+ continue
|
|
|
+
|
|
|
+ attrs = entity.get("attributes", {})
|
|
|
+ unit = attrs.get("unit_of_measurement", "")
|
|
|
+
|
|
|
+ # Only include sensors with power/energy units
|
|
|
+ if unit in valid_units:
|
|
|
+ entities.append(
|
|
|
+ {
|
|
|
+ "entity_id": entity_id,
|
|
|
+ "friendly_name": attrs.get("friendly_name", entity_id),
|
|
|
+ "state": entity.get("state"),
|
|
|
+ "unit_of_measurement": unit,
|
|
|
+ }
|
|
|
+ )
|
|
|
+
|
|
|
+ return sorted(entities, key=lambda x: x["friendly_name"].lower())
|
|
|
+ except Exception as e:
|
|
|
+ logger.warning(f"Failed to list HA sensor entities: {e}")
|
|
|
+ return []
|
|
|
+
|
|
|
|
|
|
# Singleton instance
|
|
|
homeassistant_service = HomeAssistantService()
|