Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
0548996110
|
|||
|
35141f71a4
|
|||
|
eb5532739c
|
|||
|
42411b1377
|
|||
|
b99158fd25
|
|||
|
d86e7eecc9
|
|||
|
8ab9db796c
|
|||
|
a2ddcf7de2
|
|||
|
3cc3683e8c
|
|||
|
e0810c72ea
|
|||
|
3c1253da08
|
|||
|
0efb6fab02
|
|||
|
a48d189f85
|
|||
|
40c3faa128
|
|||
|
5cca44638c
|
|||
|
fb2eef2a42
|
|||
|
0a2007ee65
|
|||
|
bdb25e3550
|
@@ -20,6 +20,7 @@ from apps.abstraction.vendors import (
|
||||
hottis_pv_modbus,
|
||||
hottis_wago_modbus,
|
||||
hottis_wifi_relay,
|
||||
hottis_led_stripe
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -44,6 +45,7 @@ for vendor_name, vendor_module in [
|
||||
("hottis_pv_modbus", hottis_pv_modbus),
|
||||
("hottis_wago_modbus", hottis_wago_modbus),
|
||||
("hottis_wifi_relay", hottis_wifi_relay),
|
||||
("hottis_led_stripe", hottis_led_stripe),
|
||||
]:
|
||||
for (device_type, direction), handler in vendor_module.HANDLERS.items():
|
||||
key = (device_type, vendor_name, direction)
|
||||
|
||||
46
apps/abstraction/vendors/hottis_led_stripe.py
vendored
Normal file
46
apps/abstraction/vendors/hottis_led_stripe.py
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
"""Hottis LED Stripe vendor transformations."""
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def transform_light_to_vendor(payload: dict[str, Any]) -> str:
|
||||
"""Transform abstract relay payload to Hottis LED Stripe format.
|
||||
|
||||
Hottis LED Stripe expects plain text 'on' or 'off' (not JSON).
|
||||
|
||||
Example:
|
||||
- Abstract: {'power': 'on'}
|
||||
- Hottis LED Stripe: 'ON'
|
||||
"""
|
||||
|
||||
bri = 89.0 / 254.0
|
||||
r = int(255 * bri)
|
||||
g = int(103 * bri)
|
||||
b = int(25 * bri)
|
||||
|
||||
cmd = f"{r} {g} {b}" if payload.get("power", "off").lower() == "on" else "0 0 0"
|
||||
return cmd
|
||||
|
||||
|
||||
def transform_light_to_abstract(payload: str) -> dict[str, Any]:
|
||||
"""Transform Hottis LED Stripe relay payload to abstract format.
|
||||
|
||||
Hottis LED Stripe sends plain text 'on' or 'off'.
|
||||
|
||||
Example:
|
||||
- Hottis LED Stripe: 'ON'
|
||||
- Abstract: {'power': 'on'}
|
||||
"""
|
||||
|
||||
power = "on" if payload.strip() != "0 0 0" else "off"
|
||||
return {"power": power}
|
||||
|
||||
|
||||
# Registry of handlers for this vendor
|
||||
HANDLERS = {
|
||||
("light", "to_vendor"): transform_light_to_vendor,
|
||||
("light", "to_abstract"): transform_light_to_abstract,
|
||||
}
|
||||
146
apps/api/config.py
Normal file
146
apps/api/config.py
Normal file
@@ -0,0 +1,146 @@
|
||||
"""Configuration loading and caching for API application.
|
||||
|
||||
This module provides centralized configuration management for devices and layout,
|
||||
with startup validation and in-memory caching for performance.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import yaml
|
||||
|
||||
from packages.home_capabilities.layout import UiLayout
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Global caches (loaded once at startup)
|
||||
devices_cache: list[dict[str, Any]] = []
|
||||
layout_cache: UiLayout | None = None
|
||||
|
||||
|
||||
def load_devices_from_file() -> list[dict[str, Any]]:
|
||||
"""Load devices from configuration file and validate.
|
||||
|
||||
Returns:
|
||||
list: List of device configurations
|
||||
|
||||
Raises:
|
||||
FileNotFoundError: If devices.yaml doesn't exist
|
||||
KeyError: If any device is missing required homekit_aid field
|
||||
ValueError: If devices.yaml is invalid or contains duplicate homekit_aid values
|
||||
"""
|
||||
config_path = Path(__file__).parent.parent.parent / "config" / "devices.yaml"
|
||||
|
||||
if not config_path.exists():
|
||||
raise FileNotFoundError(f"devices.yaml not found at {config_path}")
|
||||
|
||||
with open(config_path, "r") as f:
|
||||
config = yaml.safe_load(f)
|
||||
|
||||
if not config or "devices" not in config:
|
||||
raise ValueError("devices.yaml must contain 'devices' key")
|
||||
|
||||
# Normalize device entries: accept both 'id' and 'device_id', use 'device_id' internally
|
||||
devices = config.get("devices", [])
|
||||
for device in devices:
|
||||
device["device_id"] = device.pop("device_id", device.pop("id", None))
|
||||
|
||||
# Validate required homekit_aid field
|
||||
if "homekit_aid" not in device:
|
||||
raise KeyError(f"Device {device.get('device_id', 'unknown')} is missing required 'homekit_aid' field")
|
||||
|
||||
# Validate unique homekit_aid values
|
||||
aids = [d["homekit_aid"] for d in devices]
|
||||
if len(aids) != len(set(aids)):
|
||||
duplicates = [aid for aid in aids if aids.count(aid) > 1]
|
||||
raise ValueError(f"Duplicate homekit_aid values found: {set(duplicates)}")
|
||||
|
||||
logger.info(f"Loaded {len(devices)} devices with unique homekit_aid values (range: {min(aids)}-{max(aids)})")
|
||||
|
||||
return devices
|
||||
|
||||
|
||||
def load_layout_from_file() -> UiLayout:
|
||||
"""Load UI layout from configuration file and validate.
|
||||
|
||||
Returns:
|
||||
UiLayout: Parsed and validated layout configuration
|
||||
|
||||
Raises:
|
||||
FileNotFoundError: If layout.yaml doesn't exist
|
||||
ValueError: If layout validation fails
|
||||
yaml.YAMLError: If YAML parsing fails
|
||||
"""
|
||||
config_path = Path(__file__).parent.parent.parent / "config" / "layout.yaml"
|
||||
|
||||
if not config_path.exists():
|
||||
raise FileNotFoundError(
|
||||
f"Layout configuration not found: {config_path}. "
|
||||
f"Please create a layout.yaml file with room and device definitions."
|
||||
)
|
||||
|
||||
try:
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
data = yaml.safe_load(f)
|
||||
except yaml.YAMLError as e:
|
||||
raise yaml.YAMLError(f"Failed to parse YAML in {config_path}: {e}")
|
||||
|
||||
if data is None:
|
||||
raise ValueError(f"Layout file is empty: {config_path}")
|
||||
|
||||
try:
|
||||
layout = UiLayout(**data)
|
||||
except Exception as e:
|
||||
raise ValueError(f"Invalid layout configuration in {config_path}: {e}")
|
||||
|
||||
total_devices = layout.total_devices()
|
||||
room_names = [room.name for room in layout.rooms]
|
||||
logger.info(
|
||||
f"Loaded layout: {len(layout.rooms)} rooms, "
|
||||
f"{total_devices} total devices (Rooms: {', '.join(room_names)})"
|
||||
)
|
||||
|
||||
return layout
|
||||
|
||||
|
||||
def load_devices() -> list[dict[str, Any]]:
|
||||
"""Get devices from in-memory cache.
|
||||
|
||||
Returns:
|
||||
list: List of device configurations (loaded at startup)
|
||||
"""
|
||||
return devices_cache
|
||||
|
||||
|
||||
def load_layout() -> UiLayout:
|
||||
"""Get layout from in-memory cache.
|
||||
|
||||
Returns:
|
||||
UiLayout: Layout configuration (loaded at startup)
|
||||
|
||||
Raises:
|
||||
RuntimeError: If layout cache is not initialized
|
||||
"""
|
||||
if layout_cache is None:
|
||||
raise RuntimeError("Layout cache not initialized. Application startup may have failed.")
|
||||
return layout_cache
|
||||
|
||||
|
||||
def initialize_config() -> None:
|
||||
"""Initialize configuration by loading devices and layout.
|
||||
|
||||
This function should be called once during application startup.
|
||||
|
||||
Raises:
|
||||
Exception: If configuration loading or validation fails
|
||||
"""
|
||||
global devices_cache, layout_cache
|
||||
|
||||
# Load devices with validation
|
||||
devices_cache = load_devices_from_file()
|
||||
|
||||
# Load layout with validation
|
||||
layout_cache = load_layout_from_file()
|
||||
|
||||
logger.info("Configuration initialization complete")
|
||||
@@ -24,9 +24,11 @@ from packages.home_capabilities import (
|
||||
ContactState,
|
||||
TempHumidityState,
|
||||
RelayState,
|
||||
load_layout,
|
||||
)
|
||||
|
||||
# Import configuration management
|
||||
from apps.api.config import initialize_config, load_devices, load_layout
|
||||
|
||||
# Import resolvers (must be before router imports to avoid circular dependency)
|
||||
from apps.api.resolvers import (
|
||||
DeviceDTO,
|
||||
@@ -51,9 +53,6 @@ logger = logging.getLogger(__name__)
|
||||
# Will be populated from Redis pub/sub messages
|
||||
device_states: dict[str, dict[str, Any]] = {}
|
||||
|
||||
# Devices configuration cache (loaded once at startup)
|
||||
devices_cache: list[dict[str, Any]] = []
|
||||
|
||||
# Background task reference
|
||||
background_task: asyncio.Task | None = None
|
||||
|
||||
@@ -102,24 +101,6 @@ async def get_device_state(device_id: str):
|
||||
except KeyError:
|
||||
raise HTTPException(status_code=404, detail="Device state not found")
|
||||
|
||||
# --- Minimal-invasive: Einzelgerät-Layout-Endpunkt ---
|
||||
@app.get("/devices/{device_id}/layout")
|
||||
async def get_device_layout(device_id: str):
|
||||
"""Gibt die layout-spezifischen Informationen für ein einzelnes Gerät zurück."""
|
||||
layout = load_layout()
|
||||
for room in layout.get("rooms", []):
|
||||
for device in room.get("devices", []):
|
||||
if device.get("device_id") == device_id:
|
||||
# Rückgabe: Layout-Infos + Raumname
|
||||
return {
|
||||
"device_id": device_id,
|
||||
"room": room.get("name"),
|
||||
"title": device.get("title"),
|
||||
"icon": device.get("icon"),
|
||||
"rank": device.get("rank"),
|
||||
}
|
||||
raise HTTPException(status_code=404, detail="Device layout not found")
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
async def health() -> dict[str, str]:
|
||||
@@ -184,7 +165,7 @@ async def redis_state_listener():
|
||||
@app.on_event("startup")
|
||||
async def startup_event():
|
||||
"""Start background tasks on application startup."""
|
||||
global background_task, devices_cache
|
||||
global background_task
|
||||
|
||||
# Include routers
|
||||
from apps.api.routes.groups_scenes import router as groups_scenes_router
|
||||
@@ -193,11 +174,11 @@ async def startup_event():
|
||||
app.include_router(groups_scenes_router, prefix="")
|
||||
app.include_router(rooms_router, prefix="")
|
||||
|
||||
# Load and validate devices configuration
|
||||
# Load and validate configuration (devices + layout)
|
||||
try:
|
||||
devices_cache = load_devices_from_file()
|
||||
initialize_config()
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to load devices configuration: {e}")
|
||||
logger.error(f"Failed to initialize configuration: {e}")
|
||||
raise # Fatal error - application will not start
|
||||
|
||||
background_task = asyncio.create_task(redis_state_listener())
|
||||
@@ -252,57 +233,6 @@ class DeviceInfo(BaseModel):
|
||||
|
||||
|
||||
# Configuration helpers
|
||||
def load_devices_from_file() -> list[dict[str, Any]]:
|
||||
"""Load devices from configuration file and validate.
|
||||
|
||||
Returns:
|
||||
list: List of device configurations
|
||||
|
||||
Raises:
|
||||
FileNotFoundError: If devices.yaml doesn't exist
|
||||
KeyError: If any device is missing required homekit_aid field
|
||||
ValueError: If devices.yaml is invalid or contains duplicate homekit_aid values
|
||||
"""
|
||||
config_path = Path(__file__).parent.parent.parent / "config" / "devices.yaml"
|
||||
|
||||
if not config_path.exists():
|
||||
raise FileNotFoundError(f"devices.yaml not found at {config_path}")
|
||||
|
||||
with open(config_path, "r") as f:
|
||||
config = yaml.safe_load(f)
|
||||
|
||||
if not config or "devices" not in config:
|
||||
raise ValueError("devices.yaml must contain 'devices' key")
|
||||
|
||||
# Normalize device entries: accept both 'id' and 'device_id', use 'device_id' internally
|
||||
devices = config.get("devices", [])
|
||||
for device in devices:
|
||||
device["device_id"] = device.pop("device_id", device.pop("id", None))
|
||||
|
||||
# Validate required homekit_aid field
|
||||
if "homekit_aid" not in device:
|
||||
raise KeyError(f"Device {device.get('device_id', 'unknown')} is missing required 'homekit_aid' field")
|
||||
|
||||
# Validate unique homekit_aid values
|
||||
aids = [d["homekit_aid"] for d in devices]
|
||||
if len(aids) != len(set(aids)):
|
||||
duplicates = [aid for aid in aids if aids.count(aid) > 1]
|
||||
raise ValueError(f"Duplicate homekit_aid values found: {set(duplicates)}")
|
||||
|
||||
logger.info(f"Loaded {len(devices)} devices with unique homekit_aid values (range: {min(aids)}-{max(aids)})")
|
||||
|
||||
return devices
|
||||
|
||||
|
||||
def load_devices() -> list[dict[str, Any]]:
|
||||
"""Get devices from in-memory cache.
|
||||
|
||||
Returns:
|
||||
list: List of device configurations (loaded at startup)
|
||||
"""
|
||||
return devices_cache
|
||||
|
||||
|
||||
def get_mqtt_settings() -> tuple[str, int]:
|
||||
"""Get MQTT broker settings from environment.
|
||||
|
||||
|
||||
@@ -4,12 +4,12 @@ import logging
|
||||
from pathlib import Path
|
||||
from typing import Any, TypedDict
|
||||
|
||||
from apps.api.config import load_layout
|
||||
from packages.home_capabilities import (
|
||||
GroupConfig,
|
||||
GroupsConfigRoot,
|
||||
SceneStep,
|
||||
get_group_by_id,
|
||||
load_layout,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -12,7 +12,7 @@ from typing import Any
|
||||
from fastapi import APIRouter, HTTPException, status
|
||||
from pydantic import BaseModel
|
||||
|
||||
from packages.home_capabilities import load_layout
|
||||
from apps.api.config import load_layout
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ FROM python:3.12-slim
|
||||
|
||||
# Environment defaults (can be overridden at runtime)
|
||||
ENV PYTHONUNBUFFERED=1 \
|
||||
LOG_LEVEL="INFO" \
|
||||
HOMEKIT_NAME="Home Automation Bridge" \
|
||||
HOMEKIT_PIN="031-45-154" \
|
||||
HOMEKIT_PORT="51826" \
|
||||
|
||||
@@ -18,6 +18,7 @@ class Device:
|
||||
device_id: str
|
||||
type: str # "light", "thermostat", "relay", "contact", "temp_humidity", "cover"
|
||||
name: str # Short name from /devices
|
||||
homekit_aid: int # HomeKit Accessory ID
|
||||
features: Dict[str, bool] # Feature flags (e.g., {"power": true, "brightness": true})
|
||||
read_only: bool # True for sensors that don't accept commands
|
||||
|
||||
@@ -57,6 +58,12 @@ class DeviceRegistry:
|
||||
logger.warning(f"Device without device_id: {dev_data}")
|
||||
continue
|
||||
|
||||
# Check for required homekit_aid field
|
||||
homekit_aid = dev_data.get('homekit_aid')
|
||||
if homekit_aid is None:
|
||||
logger.error(f"Device {device_id} is missing required homekit_aid field - skipping")
|
||||
continue
|
||||
|
||||
# Determine if read-only (sensors don't accept set commands)
|
||||
device_type = dev_data.get('type', '')
|
||||
read_only = device_type in ['contact', 'temp_humidity', 'motion', 'smoke']
|
||||
@@ -65,6 +72,7 @@ class DeviceRegistry:
|
||||
device_id=device_id,
|
||||
type=device_type,
|
||||
name=device_id,
|
||||
homekit_aid=homekit_aid,
|
||||
features=dev_data.get('features', {}),
|
||||
read_only=read_only
|
||||
)
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
services:
|
||||
homekit-bridge:
|
||||
image: gitea.hottis.de/wn/home-automation/homekit:0.5.0
|
||||
build:
|
||||
context: ../../
|
||||
dockerfile: apps/homekit/Dockerfile
|
||||
container_name: homekit-bridge
|
||||
|
||||
# Required for mDNS/Bonjour to work properly
|
||||
network_mode: host
|
||||
|
||||
environment:
|
||||
- LOG_LEVEL=INFO
|
||||
- HOMEKIT_NAME=Hottis Home Automation Bridge
|
||||
- HOMEKIT_PIN=031-45-154
|
||||
- HOMEKIT_PORT=51826
|
||||
|
||||
@@ -31,8 +31,9 @@ from .api_client import ApiClient
|
||||
from .device_registry import DeviceRegistry
|
||||
|
||||
# Configure logging
|
||||
LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO").upper()
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
level=getattr(logging, LOG_LEVEL, logging.INFO),
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -71,9 +72,11 @@ def build_bridge(driver: AccessoryDriver, api_client: ApiClient) -> Bridge:
|
||||
try:
|
||||
accessory = create_accessory_for_device(device, api_client, driver)
|
||||
if accessory:
|
||||
# Set AID from device configuration
|
||||
accessory.aid = device.homekit_aid
|
||||
bridge.add_accessory(accessory)
|
||||
accessory_map[device.device_id] = accessory
|
||||
logger.info(f"Added accessory: {device.name} ({device.type}, {accessory.__class__.__name__})")
|
||||
logger.info(f"Added accessory: {device.name} ({device.type}, AID={device.homekit_aid}, {accessory.__class__.__name__})")
|
||||
else:
|
||||
logger.warning(f"No accessory mapping for device: {device.name} ({device.type})")
|
||||
except Exception as e:
|
||||
|
||||
@@ -1042,3 +1042,138 @@ devices:
|
||||
topics:
|
||||
state: "zigbee2mqtt/herdlicht"
|
||||
set: "zigbee2mqtt/herdlicht/set"
|
||||
|
||||
- device_id: regallicht_kueche
|
||||
homekit_aid: 83
|
||||
name: Regallicht
|
||||
type: light
|
||||
cap_version: "relay@1.0.0"
|
||||
technology: hottis_led_stripe
|
||||
features:
|
||||
power: true
|
||||
topics:
|
||||
state: "IoT/RgbLedStripeKitchen/ColorCommand"
|
||||
set: "IoT/RgbLedStripeKitchen/ColorCommand"
|
||||
|
||||
- device_id: regallicht_flur
|
||||
homekit_aid: 84
|
||||
name: Regallicht Flur
|
||||
type: relay
|
||||
cap_version: "relay@1.0.0"
|
||||
technology: hottis_wifi_relay
|
||||
features:
|
||||
power: true
|
||||
topics:
|
||||
set: "deconzhelper/flurregallist"
|
||||
state: "deconzhelper/flurregallist"
|
||||
|
||||
- device_id: steckdose_strandkorb
|
||||
homekit_aid: 85
|
||||
name: Steckdose Strandkorb
|
||||
type: relay
|
||||
cap_version: "relay@1.0.0"
|
||||
technology: hottis_wago_modbus
|
||||
features:
|
||||
power: true
|
||||
topics:
|
||||
set: "dt1/coil/8"
|
||||
state: "dt1/ci/8"
|
||||
- device_id: steckdose_vor_waschkueche
|
||||
homekit_aid: 86
|
||||
name: Steckdose vor Waschküche
|
||||
type: relay
|
||||
cap_version: "relay@1.0.0"
|
||||
technology: hottis_wago_modbus
|
||||
features:
|
||||
power: true
|
||||
topics:
|
||||
set: "dt1/coil/9"
|
||||
state: "dt1/ci/9"
|
||||
- device_id: wasser_vorne
|
||||
homekit_aid: 87
|
||||
name: Wasser Vorgarten
|
||||
type: relay
|
||||
cap_version: "relay@1.0.0"
|
||||
technology: hottis_wago_modbus
|
||||
features:
|
||||
power: true
|
||||
topics:
|
||||
set: "dt1/coil/13"
|
||||
state: "dt1/ci/13"
|
||||
- device_id: wasser_hinten
|
||||
homekit_aid: 88
|
||||
name: Wasser Garten
|
||||
type: relay
|
||||
cap_version: "relay@1.0.0"
|
||||
technology: hottis_wago_modbus
|
||||
features:
|
||||
power: true
|
||||
topics:
|
||||
set: "dt1/coil/12"
|
||||
state: "dt1/ci/12"
|
||||
- device_id: lampe_haustuer
|
||||
homekit_aid: 89
|
||||
name: Lampe Haustür
|
||||
type: relay
|
||||
cap_version: "relay@1.0.0"
|
||||
technology: hottis_wago_modbus
|
||||
features:
|
||||
power: true
|
||||
topics:
|
||||
set: "dt1/coil/3"
|
||||
state: "dt1/ci/3"
|
||||
- device_id: power_relay_oven
|
||||
homekit_aid: 90
|
||||
name: Schütz Herd
|
||||
type: relay
|
||||
cap_version: "relay@1.0.0"
|
||||
technology: hottis_wago_modbus
|
||||
features:
|
||||
power: true
|
||||
topics:
|
||||
set: "dt1/coil/1"
|
||||
state: "dt1/di/1"
|
||||
- device_id: power_relay_kitchen
|
||||
homekit_aid: 91
|
||||
name: Schütz Küche
|
||||
type: relay
|
||||
cap_version: "relay@1.0.0"
|
||||
technology: hottis_wago_modbus
|
||||
features:
|
||||
power: true
|
||||
topics:
|
||||
set: "dt1/coil/0"
|
||||
state: "dt1/di/0"
|
||||
- device_id: power_relay_laundry
|
||||
homekit_aid: 92
|
||||
name: Schütz Waschküche
|
||||
type: relay
|
||||
cap_version: "relay@1.0.0"
|
||||
technology: hottis_wago_modbus
|
||||
features:
|
||||
power: true
|
||||
topics:
|
||||
set: "dt1/coil/2"
|
||||
state: "dt1/di/2"
|
||||
- device_id: spot_garden
|
||||
homekit_aid: 93
|
||||
name: Spot Garten
|
||||
type: relay
|
||||
cap_version: "relay@1.0.0"
|
||||
technology: hottis_wago_modbus
|
||||
features:
|
||||
power: true
|
||||
topics:
|
||||
set: "dt1/coil/6"
|
||||
state: "dt1/ci/6"
|
||||
- device_id: licht_schuppen
|
||||
homekit_aid: 94
|
||||
name: Licht Schuppen
|
||||
type: relay
|
||||
cap_version: "relay@1.0.0"
|
||||
technology: hottis_wago_modbus
|
||||
features:
|
||||
power: true
|
||||
topics:
|
||||
set: "pulsegen/command/5/18"
|
||||
state: "pulsegen/status/5"
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
version: 1
|
||||
groups:
|
||||
- id: "kueche_lichter"
|
||||
name: "Küche – alle Lampen"
|
||||
selector:
|
||||
type: "light"
|
||||
room: "Küche"
|
||||
name: "Küche – alle Lampen ausser Putzlicht"
|
||||
device_ids:
|
||||
- kueche_deckenlampe
|
||||
- licht_spuele_kueche
|
||||
- herdlicht
|
||||
- kueche_fensterbank_licht
|
||||
- regallicht_kueche
|
||||
capabilities:
|
||||
power: true
|
||||
brightness: true
|
||||
@@ -16,21 +19,25 @@ groups:
|
||||
capabilities:
|
||||
power: true
|
||||
|
||||
- id: "schlafzimmer_lichter"
|
||||
name: "Schlafzimmer – alle Lampen"
|
||||
selector:
|
||||
type: "light"
|
||||
room: "Schlafzimmer"
|
||||
capabilities:
|
||||
power: true
|
||||
brightness: true
|
||||
|
||||
- id: "schlafzimmer_schlummer_licht"
|
||||
name: "Schlafzimmer – Schlummerlicht"
|
||||
device_ids:
|
||||
- bettlicht_patty
|
||||
- bettlicht_wolfgang
|
||||
- medusalampe_schlafzimmer
|
||||
- licht_kommode_schlafzimmer
|
||||
capabilities:
|
||||
power: true
|
||||
brightness: true
|
||||
|
||||
- id: "arbeitslicht_patty"
|
||||
name: "Patty – Arbeitslicht"
|
||||
device_ids:
|
||||
- schranklicht_hinten_patty
|
||||
- schranklicht_vorne_patty
|
||||
- leselampe_patty
|
||||
- kugellampe_patty
|
||||
- licht_schreibtisch_patty
|
||||
capabilities:
|
||||
power: true
|
||||
brightness: true
|
||||
|
||||
@@ -148,6 +148,10 @@ rooms:
|
||||
title: Herdlicht
|
||||
icon: 💡
|
||||
rank: 145
|
||||
- device_id: regallicht_kueche
|
||||
title: Regallicht Küche
|
||||
icon: 💡
|
||||
rank: 146
|
||||
- device_id: thermostat_kueche
|
||||
title: Kueche
|
||||
icon: 🌡️
|
||||
@@ -261,6 +265,14 @@ rooms:
|
||||
title: Schranklicht vor Küche
|
||||
icon: 💡
|
||||
rank: 232
|
||||
- device_id: regallicht_flur
|
||||
title: Regallicht Flur
|
||||
icon: 💡
|
||||
rank: 233
|
||||
- device_id: lampe_haustuer
|
||||
title: Lampe Haustür
|
||||
icon: 💡
|
||||
rank: 234
|
||||
- device_id: sensor_flur
|
||||
title: Temperatur & Luftfeuchte
|
||||
icon: 🌡️
|
||||
@@ -341,6 +353,30 @@ rooms:
|
||||
title: Gartenlicht vorne
|
||||
icon: 💡
|
||||
rank: 291
|
||||
- device_id: spot_garden
|
||||
title: Spot Garten
|
||||
icon: 💡
|
||||
rank: 292
|
||||
- device_id: licht_schuppen
|
||||
title: Licht Schuppen
|
||||
icon: 💡
|
||||
rank: 293
|
||||
- device_id: steckdose_strandkorb
|
||||
title: Steckdose Strandkorb
|
||||
icon: 🔌
|
||||
rank: 294
|
||||
- device_id: steckdose_vor_waschkueche
|
||||
title: Steckdose vor Waschküche
|
||||
icon: 🔌
|
||||
rank: 295
|
||||
- device_id: wasser_vorne
|
||||
title: Wasser Vorgarten
|
||||
icon: 💧
|
||||
rank: 296
|
||||
- device_id: wasser_hinten
|
||||
title: Wasser Garten
|
||||
icon: 💧
|
||||
rank: 297
|
||||
- id: garage
|
||||
name: Garage
|
||||
devices:
|
||||
@@ -367,6 +403,21 @@ rooms:
|
||||
title: Werkstatt Licht
|
||||
icon: 💡
|
||||
rank: 350
|
||||
- id: devices
|
||||
name: Devices
|
||||
devices:
|
||||
- device_id: power_relay_oven
|
||||
title: Schütz Herd
|
||||
icon: ⚡
|
||||
rank: 400
|
||||
- device_id: power_relay_kitchen
|
||||
title: Schütz Küche
|
||||
icon: ⚡
|
||||
rank: 405
|
||||
- device_id: power_relay_laundry
|
||||
title: Schütz Waschküche
|
||||
icon: ⚡
|
||||
rank: 410
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user