Files
matrix64/home_assistant.py

172 lines
6.1 KiB
Python

"""
Home Assistant API client for Matrix64 LED display.
"""
import requests
import datetime
from config import (
HA_TOKEN, HASS_URL,
BRIGHTNESS_ENTITY_ID, WEATHER_ENTITY_ID,
INTERIOR_TEMP_ENTITY_ID, INTERIOR_HUMIDITY_ENTITY_ID,
TESLA_BATTERY_ENTITY, TESLA_RANGE_ENTITY,
TESLA_CHARGING_ENTITY, TESLA_PLUGGED_ENTITY,
SOLAR_PRODUCTION_ENTITY, SOLAR_BATTERY_ENTITY,
SOLAR_BATTERY_POWER_ENTITY, SOLAR_GRID_POWER_ENTITY,
SOLAR_LOAD_POWER_ENTITY, SOLAR_TODAY_ENERGY_ENTITY,
TESLA_CHARGER_POWER_ENTITY
)
def get_entity_value(entity_id):
"""Fetch entity state from Home Assistant."""
headers = {
"Authorization": f"Bearer {HA_TOKEN}",
"Content-Type": "application/json",
}
url = f"{HASS_URL}/api/states/{entity_id}"
try:
response = requests.get(url, headers=headers, timeout=10)
if response.status_code == 200:
return response.json()
else:
print(f"Failed to retrieve entity {entity_id}: {response.text}")
return None
except Exception as e:
print(f"Error fetching {entity_id}: {e}")
return None
def get_weather():
"""Get weather attributes from Home Assistant."""
weather = get_entity_value(WEATHER_ENTITY_ID)
if weather:
return weather.get("attributes", {})
return {}
def get_weather_description():
"""Get current weather state/condition."""
weather = get_entity_value(WEATHER_ENTITY_ID)
if weather:
return weather.get("state", "unknown")
return "unknown"
def get_interior_weather():
"""Get interior temperature and humidity from BTH01-3132 sensor."""
temp_data = get_entity_value(INTERIOR_TEMP_ENTITY_ID)
humidity_data = get_entity_value(INTERIOR_HUMIDITY_ENTITY_ID)
temperature = temp_data.get("state") if temp_data else None
humidity = humidity_data.get("state") if humidity_data else None
return {"humidity": humidity, "temperature": temperature}
def get_brightness():
"""Get screen brightness value."""
brightness = get_entity_value(BRIGHTNESS_ENTITY_ID)
if brightness:
return brightness.get("state")
return None
def get_tesla_status():
"""Get Tesla Model 3 status from Home Assistant."""
battery = get_entity_value(TESLA_BATTERY_ENTITY)
range_km = get_entity_value(TESLA_RANGE_ENTITY)
charging = get_entity_value(TESLA_CHARGING_ENTITY)
plugged = get_entity_value(TESLA_PLUGGED_ENTITY)
return {
"battery": int(float(battery.get("state", 0))) if battery else None,
"range": int(float(range_km.get("state", 0))) if range_km else None,
"charging": charging.get("state") == "on" if charging else False,
"plugged": plugged.get("state") == "on" if plugged else False,
}
def get_solar_status():
"""Get solar/inverter status from Home Assistant."""
production = get_entity_value(SOLAR_PRODUCTION_ENTITY)
battery = get_entity_value(SOLAR_BATTERY_ENTITY)
battery_power = get_entity_value(SOLAR_BATTERY_POWER_ENTITY)
grid_power = get_entity_value(SOLAR_GRID_POWER_ENTITY)
load_power = get_entity_value(SOLAR_LOAD_POWER_ENTITY)
today_energy = get_entity_value(SOLAR_TODAY_ENERGY_ENTITY)
tesla_charger = get_entity_value(TESLA_CHARGER_POWER_ENTITY)
def safe_float(entity, default=None):
if entity:
try:
return float(entity.get("state", 0))
except (ValueError, TypeError):
pass
return default
# Tesla charger power (already in W)
tesla_power_w = safe_float(tesla_charger, 0)
return {
"production": safe_float(production),
"battery_pct": safe_float(battery),
"battery_power": safe_float(battery_power),
"grid_power": safe_float(grid_power),
"load_power": safe_float(load_power),
"today_energy": safe_float(today_energy),
"tesla_power": tesla_power_w,
}
def get_solar_history():
"""Get today's solar production and consumption history from HA History API.
Returns dict with 'production' and 'consumption' lists of (hour_float, watts)."""
headers = {
"Authorization": f"Bearer {HA_TOKEN}",
"Content-Type": "application/json",
}
now = datetime.datetime.now()
start = now.replace(hour=0, minute=0, second=0, microsecond=0)
start_str = start.strftime("%Y-%m-%dT%H:%M:%S")
entities = f"{SOLAR_PRODUCTION_ENTITY},{SOLAR_LOAD_POWER_ENTITY}"
url = (f"{HASS_URL}/api/history/period/{start_str}"
f"?filter_entity_id={entities}"
f"&minimal_response&no_attributes")
try:
response = requests.get(url, headers=headers, timeout=15)
if response.status_code != 200:
return {"production": [], "consumption": []}
data = response.json()
utc_offset = now - datetime.datetime.utcnow()
def parse_entries(entries):
points = []
for entry in entries:
try:
state = float(entry.get("state", 0))
ts = entry.get("last_changed", "")
if "+" in ts:
ts = ts.split("+")[0]
elif ts.endswith("Z"):
ts = ts[:-1]
dt = datetime.datetime.fromisoformat(ts)
dt_local = dt + utc_offset
hour_f = dt_local.hour + dt_local.minute / 60.0
points.append((hour_f, state))
except (ValueError, TypeError):
continue
return points
result = {"production": [], "consumption": []}
for entity_data in data:
if not entity_data:
continue
entity_id = entity_data[0].get("entity_id", "")
if entity_id == SOLAR_PRODUCTION_ENTITY:
result["production"] = parse_entries(entity_data)
elif entity_id == SOLAR_LOAD_POWER_ENTITY:
result["consumption"] = parse_entries(entity_data)
return result
except Exception as e:
print(f"Error fetching solar history: {e}")
return {"production": [], "consumption": []}