Compare commits
9 Commits
temperatur
...
shellies_r
| Author | SHA1 | Date | |
|---|---|---|---|
|
0f43f37823
|
|||
|
93e70da97d
|
|||
|
62d302bf41
|
|||
|
3d6130f2c2
|
|||
|
2a8d569bb5
|
|||
|
6a5f814cb4
|
|||
|
cc3c15078c
|
|||
|
7772dac000
|
|||
|
97ea853483
|
@@ -15,7 +15,7 @@ import uuid
|
|||||||
from aiomqtt import Client
|
from aiomqtt import Client
|
||||||
from pydantic import ValidationError
|
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 (
|
from apps.abstraction.transformation import (
|
||||||
transform_abstract_to_vendor,
|
transform_abstract_to_vendor,
|
||||||
transform_vendor_to_abstract
|
transform_vendor_to_abstract
|
||||||
@@ -154,6 +154,9 @@ async def handle_abstract_set(
|
|||||||
if device_type == "light":
|
if device_type == "light":
|
||||||
# Validate light SET payload (power and/or brightness)
|
# Validate light SET payload (power and/or brightness)
|
||||||
LightState.model_validate(abstract_payload)
|
LightState.model_validate(abstract_payload)
|
||||||
|
elif device_type == "relay":
|
||||||
|
# Validate relay SET payload (power only)
|
||||||
|
RelayState.model_validate(abstract_payload)
|
||||||
elif device_type == "thermostat":
|
elif device_type == "thermostat":
|
||||||
# For thermostat SET: only allow mode and target fields
|
# For thermostat SET: only allow mode and target fields
|
||||||
allowed_set_fields = {"mode", "target"}
|
allowed_set_fields = {"mode", "target"}
|
||||||
@@ -178,9 +181,10 @@ async def handle_abstract_set(
|
|||||||
# Transform abstract payload to vendor-specific format
|
# Transform abstract payload to vendor-specific format
|
||||||
vendor_payload = transform_abstract_to_vendor(device_type, device_technology, abstract_payload)
|
vendor_payload = transform_abstract_to_vendor(device_type, device_technology, abstract_payload)
|
||||||
|
|
||||||
# For MAX! thermostats, vendor_payload is a plain string (integer temperature)
|
# For MAX! thermostats and Shelly relays, vendor_payload is a plain string
|
||||||
# For other devices, it's a dict that needs JSON encoding
|
# For other devices, it's a dict that needs JSON encoding
|
||||||
if device_technology == "max" and device_type == "thermostat":
|
if (device_technology == "max" and device_type == "thermostat") or \
|
||||||
|
(device_technology == "shelly" and device_type == "relay"):
|
||||||
vendor_message = vendor_payload # Already a string
|
vendor_message = vendor_payload # Already a string
|
||||||
else:
|
else:
|
||||||
vendor_message = json.dumps(vendor_payload)
|
vendor_message = json.dumps(vendor_payload)
|
||||||
@@ -216,6 +220,8 @@ async def handle_vendor_state(
|
|||||||
try:
|
try:
|
||||||
if device_type == "light":
|
if device_type == "light":
|
||||||
LightState.model_validate(abstract_payload)
|
LightState.model_validate(abstract_payload)
|
||||||
|
elif device_type == "relay":
|
||||||
|
RelayState.model_validate(abstract_payload)
|
||||||
elif device_type == "thermostat":
|
elif device_type == "thermostat":
|
||||||
# Validate thermostat state: mode, target, current (required), battery, window_open
|
# Validate thermostat state: mode, target, current (required), battery, window_open
|
||||||
ThermostatState.model_validate(abstract_payload)
|
ThermostatState.model_validate(abstract_payload)
|
||||||
@@ -334,9 +340,21 @@ async def mqtt_worker(config: dict[str, Any], redis_client: aioredis.Redis) -> N
|
|||||||
max_device_type = device["type"]
|
max_device_type = device["type"]
|
||||||
break
|
break
|
||||||
|
|
||||||
|
# Check for Shelly relay (also sends plain text)
|
||||||
|
is_shelly_relay = False
|
||||||
|
shelly_device_id = None
|
||||||
|
shelly_device_type = None
|
||||||
|
for device_id, device in devices.items():
|
||||||
|
if device.get("technology") == "shelly" and device.get("type") == "relay":
|
||||||
|
if topic == device["topics"]["state"]:
|
||||||
|
is_shelly_relay = True
|
||||||
|
shelly_device_id = device_id
|
||||||
|
shelly_device_type = device["type"]
|
||||||
|
break
|
||||||
|
|
||||||
# Parse payload based on device technology
|
# Parse payload based on device technology
|
||||||
if is_max_device:
|
if is_max_device or is_shelly_relay:
|
||||||
# MAX! sends plain integer/string, not JSON
|
# MAX! and Shelly send plain text, not JSON
|
||||||
payload = payload_str.strip()
|
payload = payload_str.strip()
|
||||||
else:
|
else:
|
||||||
# All other technologies use JSON
|
# All other technologies use JSON
|
||||||
@@ -372,6 +390,14 @@ async def mqtt_worker(config: dict[str, Any], redis_client: aioredis.Redis) -> N
|
|||||||
client, redis_client, max_device_id, max_device_type,
|
client, redis_client, max_device_id, max_device_type,
|
||||||
device_technology, payload, redis_channel
|
device_technology, payload, redis_channel
|
||||||
)
|
)
|
||||||
|
# For Shelly relay devices, we already identified them above
|
||||||
|
elif is_shelly_relay:
|
||||||
|
device = devices[shelly_device_id]
|
||||||
|
device_technology = device.get("technology", "unknown")
|
||||||
|
await handle_vendor_state(
|
||||||
|
client, redis_client, shelly_device_id, shelly_device_type,
|
||||||
|
device_technology, payload, redis_channel
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
# Find device by vendor state topic for other technologies
|
# Find device by vendor state topic for other technologies
|
||||||
for device_id, device in devices.items():
|
for device_id, device in devices.items():
|
||||||
|
|||||||
@@ -307,6 +307,76 @@ def _transform_temp_humidity_sensor_max_to_abstract(payload: dict[str, Any]) ->
|
|||||||
return payload
|
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: relay - shelly technology
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
def _transform_relay_shelly_to_vendor(payload: dict[str, Any]) -> str:
|
||||||
|
"""Transform abstract relay payload to Shelly format.
|
||||||
|
|
||||||
|
Shelly expects plain text 'on' or 'off' (not JSON).
|
||||||
|
- power: 'on'/'off' -> 'on'/'off' (plain string)
|
||||||
|
|
||||||
|
Example:
|
||||||
|
- Abstract: {'power': 'on'}
|
||||||
|
- Shelly: 'on'
|
||||||
|
"""
|
||||||
|
power = payload.get("power", "off")
|
||||||
|
return power
|
||||||
|
|
||||||
|
|
||||||
|
def _transform_relay_shelly_to_abstract(payload: str) -> dict[str, Any]:
|
||||||
|
"""Transform Shelly relay payload to abstract format.
|
||||||
|
|
||||||
|
Shelly sends plain text 'on' or 'off' (not JSON).
|
||||||
|
- 'on'/'off' -> power: 'on'/'off'
|
||||||
|
|
||||||
|
Example:
|
||||||
|
- Shelly: 'on'
|
||||||
|
- Abstract: {'power': 'on'}
|
||||||
|
"""
|
||||||
|
# Shelly payload is a plain string, not a dict
|
||||||
|
if isinstance(payload, str):
|
||||||
|
return {"power": payload.strip()}
|
||||||
|
|
||||||
|
# Fallback if it's already a dict (shouldn't happen)
|
||||||
|
return payload
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# HANDLER FUNCTIONS: max technology (Homegear MAX!)
|
# HANDLER FUNCTIONS: max technology (Homegear MAX!)
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
@@ -420,6 +490,12 @@ TRANSFORM_HANDLERS: dict[tuple[str, str, str], TransformHandler] = {
|
|||||||
("temp_humidity", "zigbee2mqtt", "to_abstract"): _transform_temp_humidity_sensor_zigbee2mqtt_to_abstract,
|
("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_vendor"): _transform_temp_humidity_sensor_max_to_vendor,
|
||||||
("temp_humidity", "max", "to_abstract"): _transform_temp_humidity_sensor_max_to_abstract,
|
("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,
|
||||||
|
("relay", "shelly", "to_vendor"): _transform_relay_shelly_to_vendor,
|
||||||
|
("relay", "shelly", "to_abstract"): _transform_relay_shelly_to_abstract,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -20,10 +20,12 @@ from packages.home_capabilities import (
|
|||||||
THERMOSTAT_VERSION,
|
THERMOSTAT_VERSION,
|
||||||
CONTACT_SENSOR_VERSION,
|
CONTACT_SENSOR_VERSION,
|
||||||
TEMP_HUMIDITY_SENSOR_VERSION,
|
TEMP_HUMIDITY_SENSOR_VERSION,
|
||||||
|
RELAY_VERSION,
|
||||||
LightState,
|
LightState,
|
||||||
ThermostatState,
|
ThermostatState,
|
||||||
ContactState,
|
ContactState,
|
||||||
TempHumidityState
|
TempHumidityState,
|
||||||
|
RelayState
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -148,7 +150,8 @@ async def spec() -> dict[str, dict[str, str]]:
|
|||||||
"light": LIGHT_VERSION,
|
"light": LIGHT_VERSION,
|
||||||
"thermostat": THERMOSTAT_VERSION,
|
"thermostat": THERMOSTAT_VERSION,
|
||||||
"contact": CONTACT_SENSOR_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,
|
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||||
detail=f"Invalid payload for light: {e}"
|
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":
|
elif request.type == "thermostat":
|
||||||
try:
|
try:
|
||||||
# For thermostat SET: only allow mode and target
|
# For thermostat SET: only allow mode and target
|
||||||
|
|||||||
@@ -571,6 +571,8 @@
|
|||||||
{% if device.type == "light" %}
|
{% if device.type == "light" %}
|
||||||
Light
|
Light
|
||||||
{% if device.features.brightness %}• Dimmbar{% endif %}
|
{% if device.features.brightness %}• Dimmbar{% endif %}
|
||||||
|
{% elif device.type == "relay" %}
|
||||||
|
Relay
|
||||||
{% elif device.type == "thermostat" %}
|
{% elif device.type == "thermostat" %}
|
||||||
Thermostat
|
Thermostat
|
||||||
{% elif device.type == "contact" or device.type == "contact_sensor" %}
|
{% elif device.type == "contact" or device.type == "contact_sensor" %}
|
||||||
@@ -621,6 +623,21 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% 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" %}
|
{% elif device.type == "thermostat" %}
|
||||||
<div class="thermostat-display">
|
<div class="thermostat-display">
|
||||||
<div class="temp-reading">
|
<div class="temp-reading">
|
||||||
@@ -807,11 +824,13 @@
|
|||||||
let eventSource = null;
|
let eventSource = null;
|
||||||
let currentState = {};
|
let currentState = {};
|
||||||
let thermostatTargets = {};
|
let thermostatTargets = {};
|
||||||
|
let deviceTypes = {};
|
||||||
|
|
||||||
// Initialize device states
|
// Initialize device states
|
||||||
{% for room in rooms %}
|
{% for room in rooms %}
|
||||||
{% for device in room.devices %}
|
{% 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';
|
currentState['{{ device.device_id }}'] = 'off';
|
||||||
{% elif device.type == "thermostat" %}
|
{% elif device.type == "thermostat" %}
|
||||||
thermostatTargets['{{ device.device_id }}'] = 21.0;
|
thermostatTargets['{{ device.device_id }}'] = 21.0;
|
||||||
@@ -822,6 +841,7 @@
|
|||||||
// Toggle device state
|
// Toggle device state
|
||||||
async function toggleDevice(deviceId) {
|
async function toggleDevice(deviceId) {
|
||||||
const newState = currentState[deviceId] === 'on' ? 'off' : 'on';
|
const newState = currentState[deviceId] === 'on' ? 'off' : 'on';
|
||||||
|
const deviceType = deviceTypes[deviceId] || 'light';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(api(`/devices/${deviceId}/set`), {
|
const response = await fetch(api(`/devices/${deviceId}/set`), {
|
||||||
@@ -830,7 +850,7 @@
|
|||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
type: 'light',
|
type: deviceType,
|
||||||
payload: {
|
payload: {
|
||||||
power: newState
|
power: newState
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,12 +11,11 @@ redis:
|
|||||||
channel: "ui:updates"
|
channel: "ui:updates"
|
||||||
devices:
|
devices:
|
||||||
- device_id: lampe_semeniere_wohnzimmer
|
- device_id: lampe_semeniere_wohnzimmer
|
||||||
type: light
|
type: relay
|
||||||
cap_version: "light@1.2.0"
|
cap_version: "relay@1.0.0"
|
||||||
technology: zigbee2mqtt
|
technology: zigbee2mqtt
|
||||||
features:
|
features:
|
||||||
power: true
|
power: true
|
||||||
brightness: false
|
|
||||||
topics:
|
topics:
|
||||||
state: "zigbee2mqtt/0xf0d1b8000015480b"
|
state: "zigbee2mqtt/0xf0d1b8000015480b"
|
||||||
set: "zigbee2mqtt/0xf0d1b8000015480b/set"
|
set: "zigbee2mqtt/0xf0d1b8000015480b/set"
|
||||||
@@ -26,12 +25,11 @@ devices:
|
|||||||
model: "AC10691"
|
model: "AC10691"
|
||||||
vendor: "OSRAM"
|
vendor: "OSRAM"
|
||||||
- device_id: grosse_lampe_wohnzimmer
|
- device_id: grosse_lampe_wohnzimmer
|
||||||
type: light
|
type: relay
|
||||||
cap_version: "light@1.2.0"
|
cap_version: "relay@1.0.0"
|
||||||
technology: zigbee2mqtt
|
technology: zigbee2mqtt
|
||||||
features:
|
features:
|
||||||
power: true
|
power: true
|
||||||
brightness: false
|
|
||||||
topics:
|
topics:
|
||||||
state: "zigbee2mqtt/0xf0d1b80000151aca"
|
state: "zigbee2mqtt/0xf0d1b80000151aca"
|
||||||
set: "zigbee2mqtt/0xf0d1b80000151aca/set"
|
set: "zigbee2mqtt/0xf0d1b80000151aca/set"
|
||||||
@@ -41,12 +39,11 @@ devices:
|
|||||||
model: "AC10691"
|
model: "AC10691"
|
||||||
vendor: "OSRAM"
|
vendor: "OSRAM"
|
||||||
- device_id: lampe_naehtischchen_wohnzimmer
|
- device_id: lampe_naehtischchen_wohnzimmer
|
||||||
type: light
|
type: relay
|
||||||
cap_version: "light@1.2.0"
|
cap_version: "relay@1.0.0"
|
||||||
technology: zigbee2mqtt
|
technology: zigbee2mqtt
|
||||||
features:
|
features:
|
||||||
power: true
|
power: true
|
||||||
brightness: false
|
|
||||||
topics:
|
topics:
|
||||||
state: "zigbee2mqtt/0x842e14fffee560ee"
|
state: "zigbee2mqtt/0x842e14fffee560ee"
|
||||||
set: "zigbee2mqtt/0x842e14fffee560ee/set"
|
set: "zigbee2mqtt/0x842e14fffee560ee/set"
|
||||||
@@ -56,12 +53,11 @@ devices:
|
|||||||
model: "HG06337"
|
model: "HG06337"
|
||||||
vendor: "Lidl"
|
vendor: "Lidl"
|
||||||
- device_id: kleine_lampe_rechts_esszimmer
|
- device_id: kleine_lampe_rechts_esszimmer
|
||||||
type: light
|
type: relay
|
||||||
cap_version: "light@1.2.0"
|
cap_version: "relay@1.0.0"
|
||||||
technology: zigbee2mqtt
|
technology: zigbee2mqtt
|
||||||
features:
|
features:
|
||||||
power: true
|
power: true
|
||||||
brightness: false
|
|
||||||
topics:
|
topics:
|
||||||
state: "zigbee2mqtt/0xf0d1b80000156645"
|
state: "zigbee2mqtt/0xf0d1b80000156645"
|
||||||
set: "zigbee2mqtt/0xf0d1b80000156645/set"
|
set: "zigbee2mqtt/0xf0d1b80000156645/set"
|
||||||
@@ -71,12 +67,11 @@ devices:
|
|||||||
model: "AC10691"
|
model: "AC10691"
|
||||||
vendor: "OSRAM"
|
vendor: "OSRAM"
|
||||||
- device_id: kleine_lampe_links_esszimmer
|
- device_id: kleine_lampe_links_esszimmer
|
||||||
type: light
|
type: relay
|
||||||
cap_version: "light@1.2.0"
|
cap_version: "relay@1.0.0"
|
||||||
technology: zigbee2mqtt
|
technology: zigbee2mqtt
|
||||||
features:
|
features:
|
||||||
power: true
|
power: true
|
||||||
brightness: false
|
|
||||||
topics:
|
topics:
|
||||||
state: "zigbee2mqtt/0xf0d1b80000153099"
|
state: "zigbee2mqtt/0xf0d1b80000153099"
|
||||||
set: "zigbee2mqtt/0xf0d1b80000153099/set"
|
set: "zigbee2mqtt/0xf0d1b80000153099/set"
|
||||||
@@ -101,12 +96,11 @@ devices:
|
|||||||
model: "LED1842G3"
|
model: "LED1842G3"
|
||||||
vendor: "IKEA"
|
vendor: "IKEA"
|
||||||
- device_id: medusalampe_schlafzimmer
|
- device_id: medusalampe_schlafzimmer
|
||||||
type: light
|
type: relay
|
||||||
cap_version: "light@1.2.0"
|
cap_version: "relay@1.0.0"
|
||||||
technology: zigbee2mqtt
|
technology: zigbee2mqtt
|
||||||
features:
|
features:
|
||||||
power: true
|
power: true
|
||||||
brightness: false
|
|
||||||
topics:
|
topics:
|
||||||
state: "zigbee2mqtt/0xf0d1b80000154c7c"
|
state: "zigbee2mqtt/0xf0d1b80000154c7c"
|
||||||
set: "zigbee2mqtt/0xf0d1b80000154c7c/set"
|
set: "zigbee2mqtt/0xf0d1b80000154c7c/set"
|
||||||
@@ -192,12 +186,11 @@ devices:
|
|||||||
model: "8718699673147"
|
model: "8718699673147"
|
||||||
vendor: "Philips"
|
vendor: "Philips"
|
||||||
- device_id: schranklicht_vorne_patty
|
- device_id: schranklicht_vorne_patty
|
||||||
type: light
|
type: relay
|
||||||
cap_version: "light@1.2.0"
|
cap_version: "relay@1.0.0"
|
||||||
technology: zigbee2mqtt
|
technology: zigbee2mqtt
|
||||||
features:
|
features:
|
||||||
power: true
|
power: true
|
||||||
brightness: false
|
|
||||||
topics:
|
topics:
|
||||||
state: "zigbee2mqtt/0xf0d1b80000154cf5"
|
state: "zigbee2mqtt/0xf0d1b80000154cf5"
|
||||||
set: "zigbee2mqtt/0xf0d1b80000154cf5/set"
|
set: "zigbee2mqtt/0xf0d1b80000154cf5/set"
|
||||||
@@ -504,12 +497,11 @@ devices:
|
|||||||
peer_id: "48"
|
peer_id: "48"
|
||||||
channel: "1"
|
channel: "1"
|
||||||
- device_id: sterne_wohnzimmer
|
- device_id: sterne_wohnzimmer
|
||||||
type: light
|
type: relay
|
||||||
cap_version: "light@1.2.0"
|
cap_version: "relay@1.0.0"
|
||||||
technology: zigbee2mqtt
|
technology: zigbee2mqtt
|
||||||
features:
|
features:
|
||||||
power: true
|
power: true
|
||||||
brightness: false
|
|
||||||
topics:
|
topics:
|
||||||
state: "zigbee2mqtt/0xf0d1b80000155fc2"
|
state: "zigbee2mqtt/0xf0d1b80000155fc2"
|
||||||
set: "zigbee2mqtt/0xf0d1b80000155fc2/set"
|
set: "zigbee2mqtt/0xf0d1b80000155fc2/set"
|
||||||
@@ -518,7 +510,6 @@ devices:
|
|||||||
ieee_address: "0xf0d1b80000155fc2"
|
ieee_address: "0xf0d1b80000155fc2"
|
||||||
model: "AC10691"
|
model: "AC10691"
|
||||||
vendor: "OSRAM"
|
vendor: "OSRAM"
|
||||||
|
|
||||||
- device_id: kontakt_schlafzimmer_strasse
|
- device_id: kontakt_schlafzimmer_strasse
|
||||||
type: contact
|
type: contact
|
||||||
name: Kontakt Schlafzimmer Straße
|
name: Kontakt Schlafzimmer Straße
|
||||||
@@ -719,5 +710,52 @@ devices:
|
|||||||
topics:
|
topics:
|
||||||
state: zigbee2mqtt/0x00158d0009421422
|
state: zigbee2mqtt/0x00158d0009421422
|
||||||
features: {}
|
features: {}
|
||||||
|
- device_id: licht_spuele_kueche
|
||||||
|
type: relay
|
||||||
|
cap_version: "relay@1.0.0"
|
||||||
|
technology: shelly
|
||||||
|
features:
|
||||||
|
power: true
|
||||||
|
topics:
|
||||||
|
set: "shellies/LightKitchenSink/relay/0/command"
|
||||||
|
state: "shellies/LightKitchenSink/relay/0"
|
||||||
|
- device_id: licht_schrank_esszimmer
|
||||||
|
type: relay
|
||||||
|
cap_version: "relay@1.0.0"
|
||||||
|
technology: shelly
|
||||||
|
features:
|
||||||
|
power: true
|
||||||
|
topics:
|
||||||
|
set: "shellies/schrankesszimmer/relay/0/command"
|
||||||
|
state: "shellies/schrankesszimmer/relay/0"
|
||||||
|
- device_id: licht_regal_wohnzimmer
|
||||||
|
type: relay
|
||||||
|
cap_version: "relay@1.0.0"
|
||||||
|
technology: shelly
|
||||||
|
features:
|
||||||
|
power: true
|
||||||
|
topics:
|
||||||
|
set: "shellies/wohnzimmer-regal/relay/0/command"
|
||||||
|
state: "shellies/wohnzimmer-regal/relay/0"
|
||||||
|
- device_id: licht_flur_schrank
|
||||||
|
type: relay
|
||||||
|
cap_version: "relay@1.0.0"
|
||||||
|
technology: shelly
|
||||||
|
features:
|
||||||
|
power: true
|
||||||
|
topics:
|
||||||
|
set: "shellies/schrankflur/relay/0/command"
|
||||||
|
state: "shellies/schrankflur/relay/0"
|
||||||
|
- device_id: licht_terasse
|
||||||
|
type: relay
|
||||||
|
cap_version: "relay@1.0.0"
|
||||||
|
technology: shelly
|
||||||
|
features:
|
||||||
|
power: true
|
||||||
|
topics:
|
||||||
|
set: "shellies/lichtterasse/relay/0/command"
|
||||||
|
state: "shellies/lichtterasse/relay/0"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -51,12 +51,16 @@ rooms:
|
|||||||
title: kleine Lampe rechts Esszimmer
|
title: kleine Lampe rechts Esszimmer
|
||||||
icon: 💡
|
icon: 💡
|
||||||
rank: 90
|
rank: 90
|
||||||
|
- device_id: licht_schrank_esszimmer
|
||||||
|
title: Schranklicht Esszimmer
|
||||||
|
icon: 💡
|
||||||
|
rank: 92
|
||||||
- device_id: thermostat_esszimmer
|
- device_id: thermostat_esszimmer
|
||||||
title: Thermostat Esszimmer
|
title: Thermostat Esszimmer
|
||||||
icon: 🌡️
|
icon: 🌡️
|
||||||
rank: 95
|
rank: 95
|
||||||
- device_id: kontakt_esszimmer_strasse_rechts
|
- device_id: kontakt_esszimmer_strasse_rechts
|
||||||
title: Kontakt Straße rechts
|
title: Kontakt Straße rechtsFtest
|
||||||
icon: 🪟
|
icon: 🪟
|
||||||
rank: 96
|
rank: 96
|
||||||
- device_id: kontakt_esszimmer_strasse_links
|
- device_id: kontakt_esszimmer_strasse_links
|
||||||
@@ -81,6 +85,10 @@ rooms:
|
|||||||
title: grosse Lampe Wohnzimmer
|
title: grosse Lampe Wohnzimmer
|
||||||
icon: 💡
|
icon: 💡
|
||||||
rank: 130
|
rank: 130
|
||||||
|
- device_id: licht_regal_wohnzimmer
|
||||||
|
title: Regallicht Wohnzimmer
|
||||||
|
icon: 💡
|
||||||
|
rank: 132
|
||||||
- device_id: thermostat_wohnzimmer
|
- device_id: thermostat_wohnzimmer
|
||||||
title: Thermostat Wohnzimmer
|
title: Thermostat Wohnzimmer
|
||||||
icon: 🌡️
|
icon: 🌡️
|
||||||
@@ -103,6 +111,10 @@ rooms:
|
|||||||
title: Küche Deckenlampe
|
title: Küche Deckenlampe
|
||||||
icon: 💡
|
icon: 💡
|
||||||
rank: 140
|
rank: 140
|
||||||
|
- device_id: licht_spuele_kueche
|
||||||
|
title: Küche Spüle
|
||||||
|
icon: 💡
|
||||||
|
rank: 142
|
||||||
- device_id: thermostat_kueche
|
- device_id: thermostat_kueche
|
||||||
title: Kueche
|
title: Kueche
|
||||||
icon: 🌡️
|
icon: 🌡️
|
||||||
@@ -189,6 +201,10 @@ rooms:
|
|||||||
title: Haustür
|
title: Haustür
|
||||||
icon: 💡
|
icon: 💡
|
||||||
rank: 220
|
rank: 220
|
||||||
|
- device_id: licht_flur_schrank
|
||||||
|
title: Schranklicht Flur
|
||||||
|
icon: 💡
|
||||||
|
rank: 222
|
||||||
- device_id: licht_flur_oben_am_spiegel
|
- device_id: licht_flur_oben_am_spiegel
|
||||||
title: Licht Flur oben am Spiegel
|
title: Licht Flur oben am Spiegel
|
||||||
icon: 💡
|
icon: 💡
|
||||||
@@ -249,4 +265,10 @@ rooms:
|
|||||||
title: Temperatur & Luftfeuchte
|
title: Temperatur & Luftfeuchte
|
||||||
icon: 🌡️
|
icon: 🌡️
|
||||||
rank: 290
|
rank: 290
|
||||||
|
- name: Outdoor
|
||||||
|
devices:
|
||||||
|
- device_id: licht_terasse
|
||||||
|
title: Licht Terasse
|
||||||
|
icon: 💡
|
||||||
|
rank: 290
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ from packages.home_capabilities.contact_sensor import CAP_VERSION as CONTACT_SEN
|
|||||||
from packages.home_capabilities.contact_sensor import ContactState
|
from packages.home_capabilities.contact_sensor import ContactState
|
||||||
from packages.home_capabilities.temp_humidity_sensor import CAP_VERSION as TEMP_HUMIDITY_SENSOR_VERSION
|
from packages.home_capabilities.temp_humidity_sensor import CAP_VERSION as TEMP_HUMIDITY_SENSOR_VERSION
|
||||||
from packages.home_capabilities.temp_humidity_sensor import TempHumidityState
|
from packages.home_capabilities.temp_humidity_sensor import TempHumidityState
|
||||||
|
from packages.home_capabilities.relay import CAP_VERSION as RELAY_VERSION
|
||||||
|
from packages.home_capabilities.relay import RelayState
|
||||||
from packages.home_capabilities.layout import DeviceTile, Room, UiLayout, load_layout
|
from packages.home_capabilities.layout import DeviceTile, Room, UiLayout, load_layout
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
@@ -19,6 +21,8 @@ __all__ = [
|
|||||||
"CONTACT_SENSOR_VERSION",
|
"CONTACT_SENSOR_VERSION",
|
||||||
"TempHumidityState",
|
"TempHumidityState",
|
||||||
"TEMP_HUMIDITY_SENSOR_VERSION",
|
"TEMP_HUMIDITY_SENSOR_VERSION",
|
||||||
|
"RelayState",
|
||||||
|
"RELAY_VERSION",
|
||||||
"DeviceTile",
|
"DeviceTile",
|
||||||
"Room",
|
"Room",
|
||||||
"UiLayout",
|
"UiLayout",
|
||||||
|
|||||||
21
packages/home_capabilities/relay.py
Normal file
21
packages/home_capabilities/relay.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
"""
|
||||||
|
Relay capability model.
|
||||||
|
A relay is essentially a simple on/off switch, like a light with only power control.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
|
# Capability version
|
||||||
|
CAP_VERSION = "relay@1.0.0"
|
||||||
|
DISPLAY_NAME = "Relay"
|
||||||
|
|
||||||
|
|
||||||
|
class RelayState(BaseModel):
|
||||||
|
"""State model for relay devices (on/off only)"""
|
||||||
|
power: Literal["on", "off"] = Field(..., description="Power state: on or off")
|
||||||
|
|
||||||
|
|
||||||
|
class RelaySetPayload(BaseModel):
|
||||||
|
"""Payload for setting relay state"""
|
||||||
|
power: Literal["on", "off"] = Field(..., description="Desired power state: on or off")
|
||||||
Reference in New Issue
Block a user