thermostat

This commit is contained in:
2025-11-05 18:26:36 +01:00
parent 478450794f
commit cb555a1f67
10 changed files with 1293 additions and 11 deletions

View File

@@ -4,12 +4,16 @@ import asyncio
import json
import logging
import os
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
import redis.asyncio as aioredis
import yaml
from aiomqtt import Client
from pydantic import ValidationError
from packages.home_capabilities import LightState, ThermostatState
# Configure logging
logging.basicConfig(
@@ -129,12 +133,35 @@ async def handle_abstract_set(
Args:
mqtt_client: MQTT client instance
device_id: Device identifier
device_type: Device type (e.g., 'light')
device_type: Device type (e.g., 'light', 'thermostat')
vendor_topic: Vendor-specific SET topic
payload: Message payload
"""
# Extract actual payload (remove type wrapper if present)
vendor_payload = payload.get("payload", payload)
# Validate payload based on device type
try:
if device_type == "light":
# Validate light SET payload (power and/or brightness)
LightState.model_validate(vendor_payload)
elif device_type == "thermostat":
# For thermostat SET: only allow mode and target fields
allowed_set_fields = {"mode", "target"}
invalid_fields = set(vendor_payload.keys()) - allowed_set_fields
if invalid_fields:
logger.warning(
f"Thermostat SET {device_id} contains invalid fields {invalid_fields}, "
f"only {allowed_set_fields} allowed"
)
return
# Validate against ThermostatState (current/battery/window_open are optional)
ThermostatState.model_validate(vendor_payload)
except ValidationError as e:
logger.error(f"Validation failed for {device_type} SET {device_id}: {e}")
return
vendor_message = json.dumps(vendor_payload)
logger.info(f"→ vendor SET {device_id}: {vendor_topic}{vendor_message}")
@@ -155,10 +182,21 @@ async def handle_vendor_state(
mqtt_client: MQTT client instance
redis_client: Redis client instance
device_id: Device identifier
device_type: Device type (e.g., 'light')
device_type: Device type (e.g., 'light', 'thermostat')
payload: State payload
redis_channel: Redis channel for UI updates
"""
# Validate state payload based on device type
try:
if device_type == "light":
LightState.model_validate(payload)
elif device_type == "thermostat":
# Validate thermostat state: mode, target, current (required), battery, window_open
ThermostatState.model_validate(payload)
except ValidationError as e:
logger.error(f"Validation failed for {device_type} STATE {device_id}: {e}")
return
# Publish to abstract state topic (retained)
abstract_topic = f"home/{device_type}/{device_id}/state"
abstract_message = json.dumps(payload)
@@ -166,11 +204,12 @@ async def handle_vendor_state(
logger.info(f"← abstract STATE {device_id}: {abstract_topic}{abstract_message}")
await mqtt_client.publish(abstract_topic, abstract_message, qos=1, retain=True)
# Publish to Redis for UI updates
# Publish to Redis for UI updates with timestamp
ui_update = {
"type": "state",
"device_id": device_id,
"payload": payload
"payload": payload,
"ts": datetime.now(timezone.utc).isoformat()
}
redis_message = json.dumps(ui_update)