Compare commits

...

13 Commits

Author SHA1 Message Date
5346d1b72c licht flur haustuer
All checks were successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
2025-12-11 09:29:38 +01:00
d8780b1790 herdlicht 2
All checks were successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
2025-12-10 21:41:35 +01:00
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
1fd275186a excluded
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/2 Pipeline was successful
ci/woodpecker/tag/build/3 Pipeline was successful
ci/woodpecker/tag/deploy/3 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/2 Pipeline was successful
ci/woodpecker/tag/deploy/6 Pipeline was successful
ci/woodpecker/tag/ingress Pipeline was successful
2025-12-09 17:28:32 +01:00
da370c9050 room id
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/2 Pipeline was successful
ci/woodpecker/tag/build/3 Pipeline was successful
ci/woodpecker/tag/deploy/3 Pipeline was successful
ci/woodpecker/tag/deploy/2 Pipeline was successful
ci/woodpecker/tag/deploy/1 Pipeline was successful
ci/woodpecker/tag/deploy/5 Pipeline was successful
ci/woodpecker/tag/deploy/6 Pipeline was successful
ci/woodpecker/tag/deploy/4 Pipeline was successful
ci/woodpecker/tag/ingress Pipeline was successful
2025-12-09 17:13:51 +01:00
8 changed files with 416 additions and 36 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

@@ -8,9 +8,9 @@ ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \ PYTHONUNBUFFERED=1 \
MQTT_BROKER=172.16.2.16 \ MQTT_BROKER=172.16.2.16 \
MQTT_PORT=1883 \ MQTT_PORT=1883 \
REDIS_HOST=localhost \ REDIS_HOST=172.23.1.116 \
REDIS_PORT=6379 \ REDIS_PORT=6379 \
REDIS_DB=0 \ REDIS_DB=8 \
REDIS_CHANNEL=ui:updates REDIS_CHANNEL=ui:updates
# Create non-root user # Create non-root user

View File

@@ -121,7 +121,10 @@ async def get_device_layout(device_id: str):
async def startup_event(): async def startup_event():
"""Include routers after app is initialized to avoid circular imports.""" """Include routers after app is initialized to avoid circular imports."""
from apps.api.routes.groups_scenes import router as groups_scenes_router from apps.api.routes.groups_scenes import router as groups_scenes_router
from apps.api.routes.rooms import router as rooms_router
app.include_router(groups_scenes_router, prefix="") app.include_router(groups_scenes_router, prefix="")
app.include_router(rooms_router, prefix="")
@app.get("/health") @app.get("/health")

219
apps/api/routes/rooms.py Normal file
View File

@@ -0,0 +1,219 @@
"""
Room-based device control endpoints.
Provides bulk control operations for devices within rooms:
- /rooms/{room_name}/lights - Control all lights in a room
- /rooms/{room_name}/heating - Control all thermostats in a room
"""
import logging
from typing import Any
from fastapi import APIRouter, HTTPException, status
from pydantic import BaseModel
from packages.home_capabilities import load_layout
logger = logging.getLogger(__name__)
router = APIRouter(tags=["Rooms"])
@router.get("/rooms")
async def get_rooms() -> list[dict[str, str]]:
"""Get list of all room IDs and names.
Returns:
List of dicts with room id and name
"""
layout = load_layout()
return [
{
"id": room.id,
"name": room.name
}
for room in layout.rooms
]
class LightsControlRequest(BaseModel):
"""Request model for controlling lights in a room."""
power: str # "on" or "off"
brightness: int | None = None # Optional brightness 0-100
class HeatingControlRequest(BaseModel):
"""Request model for controlling heating in a room."""
target: float # Target temperature
def get_room_devices(room_id: str) -> list[dict[str, Any]]:
"""Get all devices in a specific room from layout.
Args:
room_id: ID of the room
Returns:
List of device dicts with device_id, title, icon, rank, excluded
Raises:
HTTPException: If room not found
"""
layout = load_layout()
for room in layout.rooms:
if room.id == room_id:
return [
{
"device_id": device.device_id,
"title": device.title,
"icon": device.icon,
"rank": device.rank,
"excluded": device.excluded
}
for device in room.devices
]
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Room '{room_id}' not found"
)
@router.post("/rooms/{room_id}/lights", status_code=status.HTTP_202_ACCEPTED)
async def control_room_lights(room_id: str, request: LightsControlRequest) -> dict[str, Any]:
"""Control all lights (light and relay devices) in a room.
Args:
room_id: ID of the room
request: Light control parameters
Returns:
dict with affected device_ids and command summary
"""
from apps.api.main import load_devices, publish_abstract_set
# Get all devices in room
room_devices = get_room_devices(room_id)
# Filter out excluded devices
room_device_ids = {d["device_id"] for d in room_devices if not d.get("excluded", False)}
# Load all devices to filter by type
all_devices = load_devices()
# Filter for light/relay devices in this room
light_devices = [
d for d in all_devices
if d["device_id"] in room_device_ids and d["type"] in ("light", "relay")
]
if not light_devices:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"No light devices found in room '{room_id}'"
)
# Build payload
payload = {"power": request.power}
if request.brightness is not None and request.power == "on":
payload["brightness"] = request.brightness
# Send commands to all light devices
affected_ids = []
errors = []
for device in light_devices:
try:
await publish_abstract_set(
device_type=device["type"],
device_id=device["device_id"],
payload=payload
)
affected_ids.append(device["device_id"])
logger.info(f"Sent command to {device['device_id']}: {payload}")
except Exception as e:
logger.error(f"Failed to control {device['device_id']}: {e}")
errors.append({
"device_id": device["device_id"],
"error": str(e)
})
return {
"room": room_id,
"command": "lights",
"payload": payload,
"affected_devices": affected_ids,
"success_count": len(affected_ids),
"error_count": len(errors),
"errors": errors if errors else None
}
@router.post("/rooms/{room_id}/heating", status_code=status.HTTP_202_ACCEPTED)
async def control_room_heating(room_id: str, request: HeatingControlRequest) -> dict[str, Any]:
"""Control all thermostats in a room.
Args:
room_id: ID of the room
request: Heating control parameters
Returns:
dict with affected device_ids and command summary
"""
from apps.api.main import load_devices, publish_abstract_set
# Get all devices in room
room_devices = get_room_devices(room_id)
# Filter out excluded devices
room_device_ids = {d["device_id"] for d in room_devices if not d.get("excluded", False)}
# Load all devices to filter by type
all_devices = load_devices()
# Filter for thermostat devices in this room
thermostat_devices = [
d for d in all_devices
if d["device_id"] in room_device_ids and d["type"] == "thermostat"
]
if not thermostat_devices:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"No thermostat devices found in room '{room_name}'"
)
# Build payload
payload = {"target": request.target}
# Send commands to all thermostat devices
affected_ids = []
errors = []
for device in thermostat_devices:
try:
await publish_abstract_set(
device_type="thermostat",
device_id=device["device_id"],
payload=payload
)
affected_ids.append(device["device_id"])
logger.info(f"Sent heating command to {device['device_id']}: {payload}")
except Exception as e:
logger.error(f"Failed to control {device['device_id']}: {e}")
errors.append({
"device_id": device["device_id"],
"error": str(e)
})
return {
"room": room_id,
"command": "heating",
"payload": payload,
"affected_devices": affected_ids,
"success_count": len(affected_ids),
"error_count": len(errors),
"errors": errors if errors else None
}

View File

@@ -747,16 +747,6 @@ devices:
topics: topics:
set: "shellies/wohnzimmer-regal/relay/0/command" set: "shellies/wohnzimmer-regal/relay/0/command"
state: "shellies/wohnzimmer-regal/relay/0" state: "shellies/wohnzimmer-regal/relay/0"
- device_id: licht_flur_schrank
type: relay
name: Schrank
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 - device_id: licht_terasse
name: Terrasse name: Terrasse
type: relay type: relay
@@ -829,7 +819,7 @@ devices:
topics: topics:
set: "cmnd/tasmota/01/POWER" set: "cmnd/tasmota/01/POWER"
state: "stat/tasmota/01/POWER" state: "stat/tasmota/01/POWER"
- device_id: schrank_flur_haustür - device_id: schrank_flur_haustuer
name: Schrank Flur Haustür name: Schrank Flur Haustür
type: relay type: relay
cap_version: "relay@1.0.0" cap_version: "relay@1.0.0"
@@ -898,3 +888,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,6 @@
rooms: rooms:
- name: Schlafzimmer - id: schlafzimmer
name: Schlafzimmer
devices: devices:
- device_id: bettlicht_patty - device_id: bettlicht_patty
title: Bettlicht Patty title: Bettlicht Patty
@@ -33,7 +34,8 @@ rooms:
title: Temperatur & Luftfeuchte title: Temperatur & Luftfeuchte
icon: 🌡️ icon: 🌡️
rank: 47 rank: 47
- name: Esszimmer - id: esszimmer
name: Esszimmer
devices: devices:
- device_id: deckenlampe_esszimmer - device_id: deckenlampe_esszimmer
title: Deckenlampe Esszimmer title: Deckenlampe Esszimmer
@@ -59,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: 💡
@@ -79,7 +81,8 @@ rooms:
title: Kontakt Straße links title: Kontakt Straße links
icon: 🪟 icon: 🪟
rank: 97 rank: 97
- name: Wohnzimmer - id: wohnzimmer
name: Wohnzimmer
devices: devices:
- device_id: lampe_naehtischchen_wohnzimmer - device_id: lampe_naehtischchen_wohnzimmer
title: Lampe Naehtischchen Wohnzimmer title: Lampe Naehtischchen Wohnzimmer
@@ -121,7 +124,8 @@ rooms:
title: Temperatur & Luftfeuchte title: Temperatur & Luftfeuchte
icon: 🌡️ icon: 🌡️
rank: 138 rank: 138
- name: che - id: kueche
name: Küche
devices: devices:
- device_id: kueche_deckenlampe - device_id: kueche_deckenlampe
title: Küche Deckenlampe title: Küche Deckenlampe
@@ -135,10 +139,15 @@ rooms:
title: Küche Putzlicht title: Küche Putzlicht
icon: 💡 icon: 💡
rank: 143 rank: 143
excluded: true
- device_id: kueche_fensterbank_licht - device_id: kueche_fensterbank_licht
title: Küche Fensterbank title: Küche Fensterbank
icon: 💡 icon: 💡
rank: 144 rank: 144
- device_id: herdlicht
title: Herdlicht
icon: 💡
rank: 145
- device_id: thermostat_kueche - device_id: thermostat_kueche
title: Kueche title: Kueche
icon: 🌡️ icon: 🌡️
@@ -163,30 +172,35 @@ rooms:
title: Temperatur & Luftfeuchte title: Temperatur & Luftfeuchte
icon: 🌡️ icon: 🌡️
rank: 155 rank: 155
- name: Arbeitszimmer Patty - id: arbeitszimmer_patty
name: Arbeitszimmer Patty
devices: devices:
- device_id: leselampe_patty - device_id: leselampe_patty
title: Leselampe Patty title: Leselampe Patty
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
@@ -205,7 +219,8 @@ rooms:
title: Temperatur & Luftfeuchte title: Temperatur & Luftfeuchte
icon: 🌡️ icon: 🌡️
rank: 189 rank: 189
- name: Arbeitszimmer Wolfgang - id: arbeitszimmer_wolfgang
name: Arbeitszimmer Wolfgang
devices: devices:
- device_id: thermostat_wolfgang - device_id: thermostat_wolfgang
title: Wolfgang title: Wolfgang
@@ -223,7 +238,8 @@ rooms:
title: Temperatur & Luftfeuchte title: Temperatur & Luftfeuchte
icon: 🌡️ icon: 🌡️
rank: 202 rank: 202
- name: Flur - id: flur
name: Flur
devices: devices:
- device_id: deckenlampe_flur_oben - device_id: deckenlampe_flur_oben
title: Deckenlampe Flur oben title: Deckenlampe Flur oben
@@ -237,7 +253,7 @@ rooms:
title: Licht oben am Spiegel title: Licht oben am Spiegel
icon: 💡 icon: 💡
rank: 230 rank: 230
- device_id: schrank_flur_haustür - device_id: schrank_flur_haustuer
title: Schranklicht an der Haustür title: Schranklicht an der Haustür
icon: 💡 icon: 💡
rank: 231 rank: 231
@@ -249,7 +265,8 @@ rooms:
title: Temperatur & Luftfeuchte title: Temperatur & Luftfeuchte
icon: 🌡️ icon: 🌡️
rank: 235 rank: 235
- name: Sportzimmer - id: sportzimmer
name: Sportzimmer
devices: devices:
- device_id: sportlicht_regal - device_id: sportlicht_regal
title: Sportlicht Regal title: Sportlicht Regal
@@ -263,11 +280,16 @@ 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
- name: Bad Oben - id: bad_oben
name: Bad Oben
devices: devices:
- device_id: thermostat_bad_oben - device_id: thermostat_bad_oben
title: Thermostat Bad Oben title: Thermostat Bad Oben
@@ -281,7 +303,8 @@ rooms:
title: Temperatur & Luftfeuchte title: Temperatur & Luftfeuchte
icon: 🌡️ icon: 🌡️
rank: 272 rank: 272
- name: Bad Unten - id: bad_unten
name: Bad Unten
devices: devices:
- device_id: thermostat_bad_unten - device_id: thermostat_bad_unten
title: Thermostat Bad Unten title: Thermostat Bad Unten
@@ -295,13 +318,20 @@ rooms:
title: Temperatur & Luftfeuchte title: Temperatur & Luftfeuchte
icon: 🌡️ icon: 🌡️
rank: 282 rank: 282
- name: Waschküche - id: waschkueche
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
- name: Outdoor - device_id: waschkueche_licht
title: Waschküche Licht
icon: 💡
rank: 340
- id: outdoor
name: Outdoor
devices: devices:
- device_id: licht_terasse - device_id: licht_terasse
title: Licht Terasse title: Licht Terasse
@@ -311,7 +341,8 @@ rooms:
title: Gartenlicht vorne title: Gartenlicht vorne
icon: 💡 icon: 💡
rank: 291 rank: 291
- name: Garage - id: garage
name: Garage
devices: devices:
- device_id: power_relay_caroutlet - device_id: power_relay_caroutlet
title: Ladestrom title: Ladestrom
@@ -325,5 +356,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

View File

@@ -18,6 +18,7 @@ class DeviceTile(BaseModel):
title: Display title for the device title: Display title for the device
icon: Icon name or emoji for the device icon: Icon name or emoji for the device
rank: Sort order within the room (lower = first) rank: Sort order within the room (lower = first)
excluded: Optional flag to exclude device from certain operations
""" """
device_id: str = Field( device_id: str = Field(
@@ -40,16 +41,27 @@ class DeviceTile(BaseModel):
ge=0, ge=0,
description="Sort order (lower values appear first)" description="Sort order (lower values appear first)"
) )
excluded: bool = Field(
default=False,
description="Exclude device from bulk operations"
)
class Room(BaseModel): class Room(BaseModel):
"""Represents a room containing devices. """Represents a room containing devices.
Attributes: Attributes:
id: Unique room identifier (used for API endpoints)
name: Room name (e.g., "Wohnzimmer", "Küche") name: Room name (e.g., "Wohnzimmer", "Küche")
devices: List of device tiles in this room devices: List of device tiles in this room
""" """
id: str = Field(
...,
description="Unique room identifier"
)
name: str = Field( name: str = Field(
..., ...,
description="Room name" description="Room name"