add type relay
This commit is contained in:
@@ -15,7 +15,7 @@ import uuid
|
||||
from aiomqtt import Client
|
||||
from pydantic import ValidationError
|
||||
|
||||
from packages.home_capabilities import LightState, ThermostatState, ContactState, TempHumidityState
|
||||
from packages.home_capabilities import LightState, ThermostatState, ContactState, TempHumidityState, RelayState
|
||||
from apps.abstraction.transformation import (
|
||||
transform_abstract_to_vendor,
|
||||
transform_vendor_to_abstract
|
||||
@@ -154,6 +154,9 @@ async def handle_abstract_set(
|
||||
if device_type == "light":
|
||||
# Validate light SET payload (power and/or brightness)
|
||||
LightState.model_validate(abstract_payload)
|
||||
elif device_type == "relay":
|
||||
# Validate relay SET payload (power only)
|
||||
RelayState.model_validate(abstract_payload)
|
||||
elif device_type == "thermostat":
|
||||
# For thermostat SET: only allow mode and target fields
|
||||
allowed_set_fields = {"mode", "target"}
|
||||
@@ -216,6 +219,8 @@ async def handle_vendor_state(
|
||||
try:
|
||||
if device_type == "light":
|
||||
LightState.model_validate(abstract_payload)
|
||||
elif device_type == "relay":
|
||||
RelayState.model_validate(abstract_payload)
|
||||
elif device_type == "thermostat":
|
||||
# Validate thermostat state: mode, target, current (required), battery, window_open
|
||||
ThermostatState.model_validate(abstract_payload)
|
||||
|
||||
@@ -307,6 +307,40 @@ def _transform_temp_humidity_sensor_max_to_abstract(payload: dict[str, Any]) ->
|
||||
return payload
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# HANDLER FUNCTIONS: relay - zigbee2mqtt technology
|
||||
# ============================================================================
|
||||
|
||||
def _transform_relay_zigbee2mqtt_to_vendor(payload: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Transform abstract relay payload to zigbee2mqtt format.
|
||||
|
||||
Relay only has power on/off, same transformation as light.
|
||||
- power: 'on'/'off' -> state: 'ON'/'OFF'
|
||||
"""
|
||||
vendor_payload = payload.copy()
|
||||
|
||||
if "power" in vendor_payload:
|
||||
power_value = vendor_payload.pop("power")
|
||||
vendor_payload["state"] = power_value.upper() if isinstance(power_value, str) else power_value
|
||||
|
||||
return vendor_payload
|
||||
|
||||
|
||||
def _transform_relay_zigbee2mqtt_to_abstract(payload: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Transform zigbee2mqtt relay payload to abstract format.
|
||||
|
||||
Relay only has power on/off, same transformation as light.
|
||||
- state: 'ON'/'OFF' -> power: 'on'/'off'
|
||||
"""
|
||||
abstract_payload = payload.copy()
|
||||
|
||||
if "state" in abstract_payload:
|
||||
state_value = abstract_payload.pop("state")
|
||||
abstract_payload["power"] = state_value.lower() if isinstance(state_value, str) else state_value
|
||||
|
||||
return abstract_payload
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# HANDLER FUNCTIONS: max technology (Homegear MAX!)
|
||||
# ============================================================================
|
||||
@@ -420,6 +454,10 @@ TRANSFORM_HANDLERS: dict[tuple[str, str, str], TransformHandler] = {
|
||||
("temp_humidity", "zigbee2mqtt", "to_abstract"): _transform_temp_humidity_sensor_zigbee2mqtt_to_abstract,
|
||||
("temp_humidity", "max", "to_vendor"): _transform_temp_humidity_sensor_max_to_vendor,
|
||||
("temp_humidity", "max", "to_abstract"): _transform_temp_humidity_sensor_max_to_abstract,
|
||||
|
||||
# Relay transformations
|
||||
("relay", "zigbee2mqtt", "to_vendor"): _transform_relay_zigbee2mqtt_to_vendor,
|
||||
("relay", "zigbee2mqtt", "to_abstract"): _transform_relay_zigbee2mqtt_to_abstract,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -20,10 +20,12 @@ from packages.home_capabilities import (
|
||||
THERMOSTAT_VERSION,
|
||||
CONTACT_SENSOR_VERSION,
|
||||
TEMP_HUMIDITY_SENSOR_VERSION,
|
||||
RELAY_VERSION,
|
||||
LightState,
|
||||
ThermostatState,
|
||||
ContactState,
|
||||
TempHumidityState
|
||||
TempHumidityState,
|
||||
RelayState
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -148,7 +150,8 @@ async def spec() -> dict[str, dict[str, str]]:
|
||||
"light": LIGHT_VERSION,
|
||||
"thermostat": THERMOSTAT_VERSION,
|
||||
"contact": CONTACT_SENSOR_VERSION,
|
||||
"temp_humidity": TEMP_HUMIDITY_SENSOR_VERSION
|
||||
"temp_humidity": TEMP_HUMIDITY_SENSOR_VERSION,
|
||||
"relay": RELAY_VERSION
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,6 +361,14 @@ async def set_device(device_id: str, request: SetDeviceRequest) -> dict[str, str
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
detail=f"Invalid payload for light: {e}"
|
||||
)
|
||||
elif request.type == "relay":
|
||||
try:
|
||||
RelayState(**request.payload)
|
||||
except ValidationError as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
detail=f"Invalid payload for relay: {e}"
|
||||
)
|
||||
elif request.type == "thermostat":
|
||||
try:
|
||||
# For thermostat SET: only allow mode and target
|
||||
|
||||
@@ -571,6 +571,8 @@
|
||||
{% if device.type == "light" %}
|
||||
Light
|
||||
{% if device.features.brightness %}• Dimmbar{% endif %}
|
||||
{% elif device.type == "relay" %}
|
||||
Relay
|
||||
{% elif device.type == "thermostat" %}
|
||||
Thermostat
|
||||
{% elif device.type == "contact" or device.type == "contact_sensor" %}
|
||||
@@ -621,6 +623,21 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% elif device.type == "relay" %}
|
||||
<div class="device-state">
|
||||
<span class="state-label">Status:</span>
|
||||
<span class="state-value off" id="state-{{ device.device_id }}">off</span>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<button
|
||||
class="toggle-button off"
|
||||
id="toggle-{{ device.device_id }}"
|
||||
onclick="toggleDevice('{{ device.device_id }}')">
|
||||
Einschalten
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{% elif device.type == "thermostat" %}
|
||||
<div class="thermostat-display">
|
||||
<div class="temp-reading">
|
||||
@@ -807,11 +824,13 @@
|
||||
let eventSource = null;
|
||||
let currentState = {};
|
||||
let thermostatTargets = {};
|
||||
let deviceTypes = {};
|
||||
|
||||
// Initialize device states
|
||||
{% for room in rooms %}
|
||||
{% for device in room.devices %}
|
||||
{% if device.type == "light" %}
|
||||
deviceTypes['{{ device.device_id }}'] = '{{ device.type }}';
|
||||
{% if device.type == "light" or device.type == "relay" %}
|
||||
currentState['{{ device.device_id }}'] = 'off';
|
||||
{% elif device.type == "thermostat" %}
|
||||
thermostatTargets['{{ device.device_id }}'] = 21.0;
|
||||
@@ -822,6 +841,7 @@
|
||||
// Toggle device state
|
||||
async function toggleDevice(deviceId) {
|
||||
const newState = currentState[deviceId] === 'on' ? 'off' : 'on';
|
||||
const deviceType = deviceTypes[deviceId] || 'light';
|
||||
|
||||
try {
|
||||
const response = await fetch(api(`/devices/${deviceId}/set`), {
|
||||
@@ -830,7 +850,7 @@
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
type: 'light',
|
||||
type: deviceType,
|
||||
payload: {
|
||||
power: newState
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user