thermostat
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user