Compare commits

..

9 Commits

Author SHA1 Message Date
3d5010b4a1 herdlicht
All checks were successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
2025-12-10 21:40:13 +01:00
b471ab5edc hottis wifi relay 4
All checks were successful
ci/woodpecker/tag/build/5 Pipeline was successful
ci/woodpecker/tag/build/6 Pipeline was successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/build/1 Pipeline was successful
ci/woodpecker/tag/build/4 Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
ci/woodpecker/tag/build/3 Pipeline was successful
ci/woodpecker/tag/build/7 Pipeline was successful
ci/woodpecker/tag/build/2 Pipeline was successful
ci/woodpecker/tag/deploy/4 Pipeline was successful
ci/woodpecker/tag/deploy/2 Pipeline was successful
ci/woodpecker/tag/deploy/5 Pipeline was successful
ci/woodpecker/tag/deploy/1 Pipeline was successful
ci/woodpecker/tag/deploy/6 Pipeline was successful
ci/woodpecker/tag/deploy/3 Pipeline was successful
ci/woodpecker/tag/ingress Pipeline was successful
2025-12-10 21:26:19 +01:00
3e0a1b49ab hottis wifi relay 3
All checks were successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
2025-12-10 21:21:29 +01:00
befdc8a46c hottis wifi relay 2
All checks were successful
ci/woodpecker/tag/build/5 Pipeline was successful
ci/woodpecker/tag/build/6 Pipeline was successful
ci/woodpecker/tag/build/4 Pipeline was successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/build/1 Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
ci/woodpecker/tag/build/7 Pipeline was successful
ci/woodpecker/tag/build/3 Pipeline was successful
ci/woodpecker/tag/build/2 Pipeline was successful
ci/woodpecker/tag/deploy/5 Pipeline was successful
ci/woodpecker/tag/deploy/3 Pipeline was successful
ci/woodpecker/tag/deploy/4 Pipeline was successful
ci/woodpecker/tag/deploy/2 Pipeline was successful
ci/woodpecker/tag/deploy/6 Pipeline was successful
ci/woodpecker/tag/deploy/1 Pipeline was successful
ci/woodpecker/tag/ingress Pipeline was successful
2025-12-10 21:15:49 +01:00
da16c59238 hottis wifi relay
All checks were successful
ci/woodpecker/tag/build/5 Pipeline was successful
ci/woodpecker/tag/build/6 Pipeline was successful
ci/woodpecker/tag/build/4 Pipeline was successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/build/1 Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
ci/woodpecker/tag/build/7 Pipeline was successful
ci/woodpecker/tag/build/3 Pipeline was successful
ci/woodpecker/tag/build/2 Pipeline was successful
ci/woodpecker/tag/deploy/4 Pipeline was successful
ci/woodpecker/tag/deploy/5 Pipeline was successful
ci/woodpecker/tag/deploy/1 Pipeline was successful
ci/woodpecker/tag/deploy/3 Pipeline was successful
ci/woodpecker/tag/deploy/6 Pipeline was successful
ci/woodpecker/tag/deploy/2 Pipeline was successful
ci/woodpecker/tag/ingress Pipeline was successful
2025-12-10 21:13:00 +01:00
5f3185894d licht keller flur 3
All checks were successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
2025-12-10 20:58:45 +01:00
fb828c9a2c licht keller flur 2
All checks were successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
2025-12-10 20:50:34 +01:00
064ee6bbed licht keller flur
All checks were successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
2025-12-10 20:47:40 +01:00
d39bcfce26 excluded 2
All checks were successful
ci/woodpecker/tag/build/5 Pipeline was successful
ci/woodpecker/tag/build/6 Pipeline was successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/build/1 Pipeline was successful
ci/woodpecker/tag/build/4 Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
ci/woodpecker/tag/build/3 Pipeline was successful
ci/woodpecker/tag/build/7 Pipeline was successful
ci/woodpecker/tag/build/2 Pipeline was successful
ci/woodpecker/tag/deploy/5 Pipeline was successful
ci/woodpecker/tag/deploy/2 Pipeline was successful
ci/woodpecker/tag/deploy/4 Pipeline was successful
ci/woodpecker/tag/deploy/1 Pipeline was successful
ci/woodpecker/tag/deploy/6 Pipeline was successful
ci/woodpecker/tag/deploy/3 Pipeline was successful
ci/woodpecker/tag/ingress Pipeline was successful
2025-12-09 17:38:46 +01:00
5 changed files with 175 additions and 37 deletions

View File

@@ -19,6 +19,7 @@ from apps.abstraction.vendors import (
tasmota, tasmota,
hottis_pv_modbus, hottis_pv_modbus,
hottis_wago_modbus, hottis_wago_modbus,
hottis_wifi_relay,
) )
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -42,6 +43,7 @@ for vendor_name, vendor_module in [
("tasmota", tasmota), ("tasmota", tasmota),
("hottis_pv_modbus", hottis_pv_modbus), ("hottis_pv_modbus", hottis_pv_modbus),
("hottis_wago_modbus", hottis_wago_modbus), ("hottis_wago_modbus", hottis_wago_modbus),
("hottis_wifi_relay", hottis_wifi_relay),
]: ]:
for (device_type, direction), handler in vendor_module.HANDLERS.items(): for (device_type, direction), handler in vendor_module.HANDLERS.items():
key = (device_type, vendor_name, direction) key = (device_type, vendor_name, direction)

View File

@@ -0,0 +1,38 @@
"""Hottis WiFi Relay vendor transformations."""
import logging
from typing import Any
logger = logging.getLogger(__name__)
def transform_relay_to_vendor(payload: dict[str, Any]) -> str:
"""Transform abstract relay payload to Hottis WiFi Relay format.
Hottis WiFi Relay expects plain text 'on' or 'off' (not JSON).
Example:
- Abstract: {'power': 'on'}
- Hottis WiFi Relay: 'ON'
"""
power = payload.get("power", "off").upper()
return power
def transform_relay_to_abstract(payload: str) -> dict[str, Any]:
"""Transform Hottis WiFi Relay relay payload to abstract format.
Hottis WiFi Relay sends plain text 'on' or 'off'.
Example:
- Hottis WiFi Relay: 'ON'
- Abstract: {'power': 'on'}
"""
return {"power": payload.strip().lower()}
# Registry of handlers for this vendor
HANDLERS = {
("relay", "to_vendor"): transform_relay_to_vendor,
("relay", "to_abstract"): transform_relay_to_abstract,
}

View File

@@ -48,11 +48,11 @@ class HeatingControlRequest(BaseModel):
target: float # Target temperature target: float # Target temperature
def get_room_devices(room_name: str) -> list[dict[str, Any]]: def get_room_devices(room_id: str) -> list[dict[str, Any]]:
"""Get all devices in a specific room from layout. """Get all devices in a specific room from layout.
Args: Args:
room_name: Name of the room room_id: ID of the room
Returns: Returns:
List of device dicts with device_id, title, icon, rank, excluded List of device dicts with device_id, title, icon, rank, excluded
@@ -63,7 +63,7 @@ def get_room_devices(room_name: str) -> list[dict[str, Any]]:
layout = load_layout() layout = load_layout()
for room in layout.rooms: for room in layout.rooms:
if room.name == room_name: if room.id == room_id:
return [ return [
{ {
"device_id": device.device_id, "device_id": device.device_id,
@@ -77,16 +77,16 @@ def get_room_devices(room_name: str) -> list[dict[str, Any]]:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, status_code=status.HTTP_404_NOT_FOUND,
detail=f"Room '{room_name}' not found" detail=f"Room '{room_id}' not found"
) )
@router.post("/rooms/{room_name}/lights", status_code=status.HTTP_202_ACCEPTED) @router.post("/rooms/{room_id}/lights", status_code=status.HTTP_202_ACCEPTED)
async def control_room_lights(room_name: str, request: LightsControlRequest) -> dict[str, Any]: async def control_room_lights(room_id: str, request: LightsControlRequest) -> dict[str, Any]:
"""Control all lights (light and relay devices) in a room. """Control all lights (light and relay devices) in a room.
Args: Args:
room_name: Name of the room room_id: ID of the room
request: Light control parameters request: Light control parameters
Returns: Returns:
@@ -95,7 +95,7 @@ async def control_room_lights(room_name: str, request: LightsControlRequest) ->
from apps.api.main import load_devices, publish_abstract_set from apps.api.main import load_devices, publish_abstract_set
# Get all devices in room # Get all devices in room
room_devices = get_room_devices(room_name) room_devices = get_room_devices(room_id)
# Filter out excluded devices # Filter out excluded devices
room_device_ids = {d["device_id"] for d in room_devices if not d.get("excluded", False)} room_device_ids = {d["device_id"] for d in room_devices if not d.get("excluded", False)}
@@ -112,7 +112,7 @@ async def control_room_lights(room_name: str, request: LightsControlRequest) ->
if not light_devices: if not light_devices:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, status_code=status.HTTP_404_NOT_FOUND,
detail=f"No light devices found in room '{room_name}'" detail=f"No light devices found in room '{room_id}'"
) )
# Build payload # Build payload
@@ -141,7 +141,7 @@ async def control_room_lights(room_name: str, request: LightsControlRequest) ->
}) })
return { return {
"room": room_name, "room": room_id,
"command": "lights", "command": "lights",
"payload": payload, "payload": payload,
"affected_devices": affected_ids, "affected_devices": affected_ids,
@@ -151,12 +151,12 @@ async def control_room_lights(room_name: str, request: LightsControlRequest) ->
} }
@router.post("/rooms/{room_name}/heating", status_code=status.HTTP_202_ACCEPTED) @router.post("/rooms/{room_id}/heating", status_code=status.HTTP_202_ACCEPTED)
async def control_room_heating(room_name: str, request: HeatingControlRequest) -> dict[str, Any]: async def control_room_heating(room_id: str, request: HeatingControlRequest) -> dict[str, Any]:
"""Control all thermostats in a room. """Control all thermostats in a room.
Args: Args:
room_name: Name of the room room_id: ID of the room
request: Heating control parameters request: Heating control parameters
Returns: Returns:
@@ -165,7 +165,7 @@ async def control_room_heating(room_name: str, request: HeatingControlRequest) -
from apps.api.main import load_devices, publish_abstract_set from apps.api.main import load_devices, publish_abstract_set
# Get all devices in room # Get all devices in room
room_devices = get_room_devices(room_name) room_devices = get_room_devices(room_id)
# Filter out excluded devices # Filter out excluded devices
room_device_ids = {d["device_id"] for d in room_devices if not d.get("excluded", False)} room_device_ids = {d["device_id"] for d in room_devices if not d.get("excluded", False)}
@@ -209,7 +209,7 @@ async def control_room_heating(room_name: str, request: HeatingControlRequest) -
}) })
return { return {
"room": room_name, "room": room_id,
"command": "heating", "command": "heating",
"payload": payload, "payload": payload,
"affected_devices": affected_ids, "affected_devices": affected_ids,

View File

@@ -898,3 +898,75 @@ devices:
set: "zigbee2mqtt/0x842e14fffea72027/set" set: "zigbee2mqtt/0x842e14fffea72027/set"
- device_id: keller_flur_licht
name: Keller Flur Licht
type: relay
cap_version: "relay@1.0.0"
technology: hottis_wago_modbus
features:
power: true
topics:
set: "pulsegen/command/10/21"
state: "pulsegen/status/10"
- device_id: waschkueche_licht
name: Waschküche Licht
type: relay
cap_version: "relay@1.0.0"
technology: hottis_wago_modbus
features:
power: true
topics:
set: "pulsegen/command/8/22"
state: "pulsegen/status/8"
- device_id: werkstatt_licht
name: Werkstatt Licht
type: relay
cap_version: "relay@1.0.0"
technology: hottis_wago_modbus
features:
power: true
topics:
set: "pulsegen/command/7/19"
state: "pulsegen/status/7"
- device_id: sportzimmer_licht
name: Sportzimmer Licht
type: relay
cap_version: "relay@1.0.0"
technology: hottis_wago_modbus
features:
power: true
topics:
set: "pulsegen/command/9/20"
state: "pulsegen/status/9"
- device_id: deckenlampe_patty
name: Deckenlampe Patty
type: relay
cap_version: "relay@1.0.0"
technology: hottis_wago_modbus
features:
power: true
topics:
set: "pulsegen/command/4/16"
state: "pulsegen/status/4"
- device_id: regallampe_esszimmer
name: Regallampe Esszimmer
type: relay
cap_version: "relay@1.0.0"
technology: hottis_wifi_relay
features:
power: true
topics:
set: "IoT/WifiRelay1/State"
state: "IoT/WifiRelay1/State"
- device_id: herdlicht
name: Herdlicht
type: light
cap_version: "relay@1.0.0"
technology: zigbee2mqtt
features:
power: true
brightness: true
topics:
state: "zigbee2mqtt/herdlicht"
set: "zigbee2mqtt/herdlicht/set"

View File

@@ -1,5 +1,5 @@
rooms: rooms:
- id: Schlafzimmer - id: schlafzimmer
name: Schlafzimmer name: Schlafzimmer
devices: devices:
- device_id: bettlicht_patty - device_id: bettlicht_patty
@@ -34,7 +34,7 @@ rooms:
title: Temperatur & Luftfeuchte title: Temperatur & Luftfeuchte
icon: 🌡️ icon: 🌡️
rank: 47 rank: 47
- id: Esszimmer - id: esszimmer
name: Esszimmer name: Esszimmer
devices: devices:
- device_id: deckenlampe_esszimmer - device_id: deckenlampe_esszimmer
@@ -61,10 +61,10 @@ rooms:
title: Stehlampe Esszimmer Schrank title: Stehlampe Esszimmer Schrank
icon: 💡 icon: 💡
rank: 82 rank: 82
# - device_id: kleine_lampe_rechts_esszimmer - device_id: regallampe_esszimmer
# title: kleine Lampe rechts Esszimmer title: Regallampe Esszimmer
# icon: 💡 icon: 💡
# rank: 90 rank: 90
- device_id: licht_schrank_esszimmer - device_id: licht_schrank_esszimmer
title: Schranklicht Esszimmer title: Schranklicht Esszimmer
icon: 💡 icon: 💡
@@ -81,7 +81,7 @@ rooms:
title: Kontakt Straße links title: Kontakt Straße links
icon: 🪟 icon: 🪟
rank: 97 rank: 97
- id: Wohnzimmer - id: wohnzimmer
name: Wohnzimmer name: Wohnzimmer
devices: devices:
- device_id: lampe_naehtischchen_wohnzimmer - device_id: lampe_naehtischchen_wohnzimmer
@@ -124,7 +124,7 @@ rooms:
title: Temperatur & Luftfeuchte title: Temperatur & Luftfeuchte
icon: 🌡️ icon: 🌡️
rank: 138 rank: 138
- id: che - id: kueche
name: Küche name: Küche
devices: devices:
- device_id: kueche_deckenlampe - device_id: kueche_deckenlampe
@@ -168,7 +168,7 @@ rooms:
title: Temperatur & Luftfeuchte title: Temperatur & Luftfeuchte
icon: 🌡️ icon: 🌡️
rank: 155 rank: 155
- id: Arbeitszimmer Patty - id: arbeitszimmer_patty
name: Arbeitszimmer Patty name: Arbeitszimmer Patty
devices: devices:
- device_id: leselampe_patty - device_id: leselampe_patty
@@ -176,23 +176,27 @@ rooms:
icon: 💡 icon: 💡
rank: 160 rank: 160
- device_id: schranklicht_hinten_patty - device_id: schranklicht_hinten_patty
title: Schranklicht hinten Patty title: Schranklicht hinten
icon: 💡 icon: 💡
rank: 170 rank: 170
- device_id: schranklicht_vorne_patty - device_id: schranklicht_vorne_patty
title: Schranklicht vorne Patty title: Schranklicht vorne
icon: 💡 icon: 💡
rank: 180 rank: 180
- device_id: kugellampe_patty - device_id: kugellampe_patty
title: Kugellampe Patty title: Kugellampe
icon: 💡 icon: 💡
rank: 181 rank: 181
- device_id: licht_schreibtisch_patty - device_id: licht_schreibtisch_patty
title: Licht Schreibtisch Patty title: Licht Schreibtisch
icon: 💡 icon: 💡
rank: 182 rank: 182
- device_id: deckenlampe_patty
title: Deckenlampe
icon: 💡
rank: 183
- device_id: thermostat_patty - device_id: thermostat_patty
title: Thermostat Patty title: Thermostat
icon: 🌡️ icon: 🌡️
rank: 185 rank: 185
- device_id: kontakt_patty_garten_rechts - device_id: kontakt_patty_garten_rechts
@@ -211,7 +215,7 @@ rooms:
title: Temperatur & Luftfeuchte title: Temperatur & Luftfeuchte
icon: 🌡️ icon: 🌡️
rank: 189 rank: 189
- id: Arbeitszimmer Wolfgang - id: arbeitszimmer_wolfgang
name: Arbeitszimmer Wolfgang name: Arbeitszimmer Wolfgang
devices: devices:
- device_id: thermostat_wolfgang - device_id: thermostat_wolfgang
@@ -230,7 +234,7 @@ rooms:
title: Temperatur & Luftfeuchte title: Temperatur & Luftfeuchte
icon: 🌡️ icon: 🌡️
rank: 202 rank: 202
- id: Flur - id: flur
name: Flur name: Flur
devices: devices:
- device_id: deckenlampe_flur_oben - device_id: deckenlampe_flur_oben
@@ -257,7 +261,7 @@ rooms:
title: Temperatur & Luftfeuchte title: Temperatur & Luftfeuchte
icon: 🌡️ icon: 🌡️
rank: 235 rank: 235
- id: Sportzimmer - id: sportzimmer
name: Sportzimmer name: Sportzimmer
devices: devices:
- device_id: sportlicht_regal - device_id: sportlicht_regal
@@ -272,11 +276,15 @@ rooms:
title: Sportlicht am Fernseher, Studierzimmer title: Sportlicht am Fernseher, Studierzimmer
icon: 🏃 icon: 🏃
rank: 260 rank: 260
- device_id: sportzimmer_licht
title: Deckenlampe
icon: 💡
rank: 262
- device_id: sensor_sportzimmer - device_id: sensor_sportzimmer
title: Temperatur & Luftfeuchte title: Temperatur & Luftfeuchte
icon: 🌡️ icon: 🌡️
rank: 265 rank: 265
- id: Bad Oben - id: bad_oben
name: Bad Oben name: Bad Oben
devices: devices:
- device_id: thermostat_bad_oben - device_id: thermostat_bad_oben
@@ -291,7 +299,7 @@ rooms:
title: Temperatur & Luftfeuchte title: Temperatur & Luftfeuchte
icon: 🌡️ icon: 🌡️
rank: 272 rank: 272
- id: Bad Unten - id: bad_unten
name: Bad Unten name: Bad Unten
devices: devices:
- device_id: thermostat_bad_unten - device_id: thermostat_bad_unten
@@ -306,14 +314,19 @@ rooms:
title: Temperatur & Luftfeuchte title: Temperatur & Luftfeuchte
icon: 🌡️ icon: 🌡️
rank: 282 rank: 282
- id: Waschküche - id: waschkueche
name: Waschküche name: Waschküche
devices: devices:
- device_id: sensor_waschkueche - device_id: sensor_waschkueche
title: Temperatur & Luftfeuchte title: Temperatur & Luftfeuchte
icon: 🌡️ icon: 🌡️
rank: 290 rank: 290
- id: Outdoor - device_id: waschkueche_licht
title: Waschküche Licht
icon: 💡
rank: 340
- id: outdoor
name: Outdoor name: Outdoor
devices: devices:
- device_id: licht_terasse - device_id: licht_terasse
@@ -324,7 +337,7 @@ rooms:
title: Gartenlicht vorne title: Gartenlicht vorne
icon: 💡 icon: 💡
rank: 291 rank: 291
- id: Garage - id: garage
name: Garage name: Garage
devices: devices:
- device_id: power_relay_caroutlet - device_id: power_relay_caroutlet
@@ -339,5 +352,18 @@ rooms:
title: Messwerte title: Messwerte
icon: 📊 icon: 📊
rank: 320 rank: 320
- id: keller
name: Keller
devices:
- device_id: keller_flur_licht
title: Keller Flur Licht
icon: 💡
rank: 330
- device_id: werkstatt_licht
title: Werkstatt Licht
icon: 💡
rank: 350