Compare commits

...

36 Commits

Author SHA1 Message Date
55937d5900 fix window setback logic for multiple windows, fix 1
All checks were successful
ci/woodpecker/tag/build/6 Pipeline was successful
ci/woodpecker/tag/build/5 Pipeline was successful
ci/woodpecker/tag/build/4 Pipeline was successful
ci/woodpecker/tag/build/1 Pipeline was successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/build/2 Pipeline was successful
ci/woodpecker/tag/build/3 Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
ci/woodpecker/tag/build/7 Pipeline was successful
ci/woodpecker/tag/deploy/2 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/6 Pipeline was successful
ci/woodpecker/tag/deploy/1 Pipeline was successful
ci/woodpecker/tag/ingress Pipeline was successful
2026-01-13 15:33:57 +01:00
38762d60f2 fix window setback logic for multiple windows
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/3 Pipeline was successful
ci/woodpecker/tag/build/4 Pipeline was successful
ci/woodpecker/tag/build/7 Pipeline was successful
ci/woodpecker/tag/build/1 Pipeline was successful
ci/woodpecker/tag/build/2 Pipeline was successful
ci/woodpecker/tag/config 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/2 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
2026-01-13 15:25:18 +01:00
4f5bcd7dbf test devices
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/build/7 Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
ci/woodpecker/tag/build/2 Pipeline was successful
ci/woodpecker/tag/build/3 Pipeline was successful
ci/woodpecker/tag/deploy/5 Pipeline was successful
ci/woodpecker/tag/deploy/6 Pipeline was successful
ci/woodpecker/tag/deploy/2 Pipeline was successful
ci/woodpecker/tag/deploy/3 Pipeline was successful
ci/woodpecker/tag/deploy/1 Pipeline was successful
ci/woodpecker/tag/deploy/4 Pipeline was successful
ci/woodpecker/tag/ingress Pipeline was successful
2026-01-13 13:04:04 +01:00
3bcaa93570 merged 2026-01-13 12:59:25 +01:00
331945f789 changes in devices.yaml 2026-01-13 12:56:15 +01:00
52235be637 changes in rules.yaml 2026-01-13 12:55:43 +01:00
94589f52d7 initial 2026-01-13 12:52:49 +01:00
474b41ffce deploy confguuration script 2026-01-06 14:02:11 +01:00
79081e7480 thermostat bad unten replaced 2 2026-01-06 13:48:29 +01:00
424f1d6743 thermostat bad unten replaced 2026-01-06 13:47:56 +01:00
7212a3bd5a Lampentausch
All checks were successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
2026-01-03 20:52:10 +01:00
7e0801d21a event_generator fix
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/3 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/2 Pipeline was successful
ci/woodpecker/tag/ingress Pipeline was successful
2025-12-25 19:48:07 +01:00
49e555ce51 redis_state_listener fix 2025-12-25 19:36:19 +01:00
62f68fb513 Merge branch 'main' of gitea.hottis.de:wn/home-automation
All checks were successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
2025-12-22 19:18:32 +01:00
66f180755b heating rules 2025-12-22 19:18:23 +01:00
b9ba9cbd16 herdlicht again 2
All checks were successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
2025-12-18 19:04:26 +01:00
14c4c7c850 herdlicht again 2
All checks were successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
2025-12-18 19:01:33 +01:00
edb8b3313b herdlicht again
All checks were successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
2025-12-18 18:57:42 +01:00
68015905b0 herdlicht 2
All checks were successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
2025-12-17 16:22:11 +01:00
223c6e58b9 herdlicht
All checks were successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
2025-12-17 16:18:42 +01:00
0548996110 steckdose strandkorb 3
All checks were successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
2025-12-15 12:15:38 +01:00
35141f71a4 steckdose strandkorb 2
All checks were successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
2025-12-15 12:11:48 +01:00
eb5532739c steckdose strandkorb
All checks were successful
ci/woodpecker/tag/build/7 Pipeline was successful
ci/woodpecker/tag/deploy/4 Pipeline was successful
ci/woodpecker/tag/deploy/6 Pipeline was successful
ci/woodpecker/tag/ingress Pipeline was successful
ci/woodpecker/tag/deploy/3 Pipeline was successful
ci/woodpecker/tag/deploy/1 Pipeline was successful
ci/woodpecker/tag/deploy/2 Pipeline was successful
ci/woodpecker/tag/deploy/5 Pipeline was 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/build/3 Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
ci/woodpecker/tag/build/2 Pipeline was successful
2025-12-15 11:52:57 +01:00
42411b1377 regallicht flur 3
All checks were successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
2025-12-12 22:17:44 +01:00
b99158fd25 regallicht flur 2
All checks were successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
2025-12-12 22:14:59 +01:00
d86e7eecc9 regallicht flur
All checks were successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
2025-12-12 22:13:48 +01:00
8ab9db796c regallicht kueche 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/1 Pipeline was successful
ci/woodpecker/tag/build/4 Pipeline was successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/build/2 Pipeline was successful
ci/woodpecker/tag/build/3 Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
ci/woodpecker/tag/build/7 Pipeline was successful
ci/woodpecker/tag/deploy/2 Pipeline was successful
ci/woodpecker/tag/deploy/3 Pipeline was successful
ci/woodpecker/tag/deploy/5 Pipeline was successful
ci/woodpecker/tag/deploy/4 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-12 22:08:06 +01:00
a2ddcf7de2 regallicht kueche
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/4 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/3 Pipeline was successful
ci/woodpecker/tag/deploy/4 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/deploy/5 Pipeline was successful
ci/woodpecker/tag/ingress Pipeline was successful
2025-12-12 22:01:29 +01:00
3cc3683e8c group
All checks were successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
2025-12-12 20:53:18 +01:00
e0810c72ea group
All checks were successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
2025-12-12 20:50:11 +01:00
3c1253da08 group
All checks were successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
2025-12-12 20:40:02 +01:00
0efb6fab02 group
All checks were successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
2025-12-12 20:30:50 +01:00
a48d189f85 group
All checks were successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
2025-12-12 20:14:20 +01:00
40c3faa128 loglevel
All checks were successful
ci/woodpecker/tag/build/5 Pipeline was successful
ci/woodpecker/tag/build/6 Pipeline was successful
ci/woodpecker/tag/build/1 Pipeline was successful
ci/woodpecker/tag/build/4 Pipeline was successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/build/3 Pipeline was successful
ci/woodpecker/tag/build/7 Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
ci/woodpecker/tag/build/2 Pipeline was successful
ci/woodpecker/tag/deploy/1 Pipeline was successful
ci/woodpecker/tag/deploy/2 Pipeline was successful
ci/woodpecker/tag/deploy/5 Pipeline was successful
ci/woodpecker/tag/deploy/3 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-11 13:53:00 +01:00
5cca44638c aid in homekit 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/4 Pipeline was successful
ci/woodpecker/tag/build/7 Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
ci/woodpecker/tag/build/1 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/1 Pipeline was successful
ci/woodpecker/tag/deploy/4 Pipeline was successful
ci/woodpecker/tag/deploy/6 Pipeline was successful
ci/woodpecker/tag/deploy/2 Pipeline was successful
ci/woodpecker/tag/deploy/3 Pipeline was successful
ci/woodpecker/tag/ingress Pipeline was successful
2025-12-11 12:16:51 +01:00
fb2eef2a42 aid in homekit
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/build/1 Pipeline was successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/build/2 Pipeline was successful
ci/woodpecker/tag/build/7 Pipeline was successful
ci/woodpecker/tag/build/3 Pipeline was successful
ci/woodpecker/tag/config 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/deploy/2 Pipeline was successful
ci/woodpecker/tag/deploy/5 Pipeline was successful
ci/woodpecker/tag/ingress Pipeline was successful
2025-12-11 11:51:54 +01:00
15 changed files with 586 additions and 186 deletions

View File

@@ -1,5 +1,8 @@
when: when:
event: [tag] event: [tag]
ref:
exclude:
- refs/tags/*-configchange
steps: steps:
create_namespace: create_namespace:

View File

@@ -15,11 +15,13 @@ from apps.abstraction.vendors import (
simulator, simulator,
zigbee2mqtt, zigbee2mqtt,
max, max,
test,
shelly, shelly,
tasmota, tasmota,
hottis_pv_modbus, hottis_pv_modbus,
hottis_wago_modbus, hottis_wago_modbus,
hottis_wifi_relay, hottis_wifi_relay,
hottis_led_stripe
) )
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -39,11 +41,13 @@ for vendor_name, vendor_module in [
("simulator", simulator), ("simulator", simulator),
("zigbee2mqtt", zigbee2mqtt), ("zigbee2mqtt", zigbee2mqtt),
("max", max), ("max", max),
("test", test),
("shelly", shelly), ("shelly", shelly),
("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), ("hottis_wifi_relay", hottis_wifi_relay),
("hottis_led_stripe", hottis_led_stripe),
]: ]:
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,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,
}

62
apps/abstraction/vendors/test.py vendored Normal file
View File

@@ -0,0 +1,62 @@
"""test vendor transformations."""
import json
import logging
from typing import Any
logger = logging.getLogger(__name__)
def transform_contact_sensor_to_vendor(payload: dict[str, Any]) -> str:
"""Transform abstract contact sensor payload to MAX! format.
Contact sensors are read-only.
"""
logger.warning("Contact sensors are read-only - SET commands should not be used")
return json.dumps(payload)
def transform_contact_sensor_to_abstract(payload: str) -> dict[str, Any]:
try:
contact_value = payload.strip().lower()
return {
"contact": "open" if (contact_value == "open") else "closed"
}
except (ValueError, TypeError) as e:
logger.error(f"contact sensor failed to parse: {payload}, error: {e}")
return {"contact": "closed"}
def transform_thermostat_to_vendor(payload: dict[str, Any]) -> str:
if "target" not in payload:
logger.warning(f"thermostat payload missing 'target': {payload}")
return "21"
target_temp = payload["target"]
if isinstance(target_temp, (int, float)):
int_temp = int(round(target_temp))
return str(int_temp)
logger.warning(f"invalid target temperature type: {type(target_temp)}")
return "21"
def transform_thermostat_to_abstract(payload: str) -> dict[str, Any]:
target_temp = float(payload.strip())
return {
"target": target_temp,
"mode": "heat"
}
# Registry of handlers for this vendor
HANDLERS = {
("contact_sensor", "to_vendor"): transform_contact_sensor_to_vendor,
("contact_sensor", "to_abstract"): transform_contact_sensor_to_abstract,
("contact", "to_vendor"): transform_contact_sensor_to_vendor,
("contact", "to_abstract"): transform_contact_sensor_to_abstract,
("thermostat", "to_vendor"): transform_thermostat_to_vendor,
("thermostat", "to_abstract"): transform_thermostat_to_abstract,
}

View File

@@ -127,27 +127,19 @@ async def redis_state_listener():
logger.info("Redis state listener connected") logger.info("Redis state listener connected")
while True: # listen() blocks async and waits for messages - prevents busy loop
try: async for message in pubsub.listen():
message = await asyncio.wait_for( if message["type"] == "message":
pubsub.get_message(ignore_subscribe_messages=True), data = message["data"]
timeout=1.0 try:
) state_data = json.loads(data)
if state_data.get("type") == "state" and state_data.get("device_id"):
if message and message["type"] == "message": device_id = state_data["device_id"]
data = message["data"] payload = state_data.get("payload", {})
try: device_states[device_id] = payload
state_data = json.loads(data) logger.debug(f"Updated state cache for {device_id}: {payload}")
if state_data.get("type") == "state" and state_data.get("device_id"): except Exception as e:
device_id = state_data["device_id"] logger.warning(f"Failed to parse state data: {e}")
payload = state_data.get("payload", {})
device_states[device_id] = payload
logger.debug(f"Updated state cache for {device_id}: {payload}")
except Exception as e:
logger.warning(f"Failed to parse state data: {e}")
except asyncio.TimeoutError:
pass # No message, continue
except asyncio.CancelledError: except asyncio.CancelledError:
logger.info("Redis state listener cancelled") logger.info("Redis state listener cancelled")
@@ -567,25 +559,31 @@ async def event_generator(request: Request) -> AsyncGenerator[str, None]:
redis_client = None redis_client = None
pubsub = None pubsub = None
# Heartbeat tracking
last_heartbeat = asyncio.get_event_loop().time()
heartbeat_interval = 15 # Safari-friendly: shorter interval heartbeat_interval = 15 # Safari-friendly: shorter interval
# Use listen() iterator for blocking reads with heartbeat timeout
if pubsub:
listener = pubsub.listen()
else:
listener = None
while True: while True:
# Check if client disconnected # Check if client disconnected
if await request.is_disconnected(): if await request.is_disconnected():
logger.info("SSE client disconnected") logger.info("SSE client disconnected")
break break
# Try to get message from Redis (if available) # Try to get message from Redis with timeout for heartbeat
if pubsub: if listener:
try: try:
# Wait for message with heartbeat timeout
# If no message arrives within timeout, send heartbeat
message = await asyncio.wait_for( message = await asyncio.wait_for(
pubsub.get_message(ignore_subscribe_messages=True), anext(listener),
timeout=0.1 timeout=heartbeat_interval
) )
if message and message["type"] == "message": if message["type"] == "message":
data = message["data"] data = message["data"]
logger.debug(f"Sending SSE message: {data[:100]}...") logger.debug(f"Sending SSE message: {data[:100]}...")
@@ -598,24 +596,21 @@ async def event_generator(request: Request) -> AsyncGenerator[str, None]:
logger.warning(f"Failed to parse state data for cache: {e}") logger.warning(f"Failed to parse state data for cache: {e}")
yield f"event: message\ndata: {data}\n\n" yield f"event: message\ndata: {data}\n\n"
last_heartbeat = asyncio.get_event_loop().time()
continue # Skip sleep, check for more messages immediately
except asyncio.TimeoutError: except asyncio.TimeoutError:
pass # No message, continue to heartbeat check # No message within heartbeat interval - send heartbeat
yield ": ping\n\n"
except StopAsyncIteration:
logger.warning("Redis listener stopped")
break
except Exception as e: except Exception as e:
logger.error(f"Redis error: {e}") logger.error(f"Redis error: {e}")
# Continue with heartbeats even if Redis fails # Continue with heartbeat-only mode
listener = None
# Sleep briefly to avoid busy loop else:
await asyncio.sleep(0.1) # Heartbeat-only mode (no Redis)
await asyncio.sleep(heartbeat_interval)
# Send heartbeat if interval elapsed
current_time = asyncio.get_event_loop().time()
if current_time - last_heartbeat >= heartbeat_interval:
# Comment-style ping (Safari-compatible, no event type)
yield ": ping\n\n" yield ": ping\n\n"
last_heartbeat = current_time
except asyncio.CancelledError: except asyncio.CancelledError:
logger.info("SSE connection cancelled by client") logger.info("SSE connection cancelled by client")

View File

@@ -2,6 +2,7 @@ FROM python:3.12-slim
# Environment defaults (can be overridden at runtime) # Environment defaults (can be overridden at runtime)
ENV PYTHONUNBUFFERED=1 \ ENV PYTHONUNBUFFERED=1 \
LOG_LEVEL="INFO" \
HOMEKIT_NAME="Home Automation Bridge" \ HOMEKIT_NAME="Home Automation Bridge" \
HOMEKIT_PIN="031-45-154" \ HOMEKIT_PIN="031-45-154" \
HOMEKIT_PORT="51826" \ HOMEKIT_PORT="51826" \

View File

@@ -18,6 +18,7 @@ class Device:
device_id: str device_id: str
type: str # "light", "thermostat", "relay", "contact", "temp_humidity", "cover" type: str # "light", "thermostat", "relay", "contact", "temp_humidity", "cover"
name: str # Short name from /devices name: str # Short name from /devices
homekit_aid: int # HomeKit Accessory ID
features: Dict[str, bool] # Feature flags (e.g., {"power": true, "brightness": true}) features: Dict[str, bool] # Feature flags (e.g., {"power": true, "brightness": true})
read_only: bool # True for sensors that don't accept commands 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}") logger.warning(f"Device without device_id: {dev_data}")
continue 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) # Determine if read-only (sensors don't accept set commands)
device_type = dev_data.get('type', '') device_type = dev_data.get('type', '')
read_only = device_type in ['contact', 'temp_humidity', 'motion', 'smoke'] read_only = device_type in ['contact', 'temp_humidity', 'motion', 'smoke']
@@ -65,6 +72,7 @@ class DeviceRegistry:
device_id=device_id, device_id=device_id,
type=device_type, type=device_type,
name=device_id, name=device_id,
homekit_aid=homekit_aid,
features=dev_data.get('features', {}), features=dev_data.get('features', {}),
read_only=read_only read_only=read_only
) )

View File

@@ -1,12 +1,16 @@
services: services:
homekit-bridge: homekit-bridge:
image: gitea.hottis.de/wn/home-automation/homekit:0.5.0 image: gitea.hottis.de/wn/home-automation/homekit:0.5.0
build:
context: ../../
dockerfile: apps/homekit/Dockerfile
container_name: homekit-bridge container_name: homekit-bridge
# Required for mDNS/Bonjour to work properly # Required for mDNS/Bonjour to work properly
network_mode: host network_mode: host
environment: environment:
- LOG_LEVEL=INFO
- HOMEKIT_NAME=Hottis Home Automation Bridge - HOMEKIT_NAME=Hottis Home Automation Bridge
- HOMEKIT_PIN=031-45-154 - HOMEKIT_PIN=031-45-154
- HOMEKIT_PORT=51826 - HOMEKIT_PORT=51826

View File

@@ -31,8 +31,9 @@ from .api_client import ApiClient
from .device_registry import DeviceRegistry from .device_registry import DeviceRegistry
# Configure logging # Configure logging
LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO").upper()
logging.basicConfig( logging.basicConfig(
level=logging.INFO, level=getattr(logging, LOG_LEVEL, logging.INFO),
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
) )
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -71,9 +72,11 @@ def build_bridge(driver: AccessoryDriver, api_client: ApiClient) -> Bridge:
try: try:
accessory = create_accessory_for_device(device, api_client, driver) accessory = create_accessory_for_device(device, api_client, driver)
if accessory: if accessory:
# Set AID from device configuration
accessory.aid = device.homekit_aid
bridge.add_accessory(accessory) bridge.add_accessory(accessory)
accessory_map[device.device_id] = 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: else:
logger.warning(f"No accessory mapping for device: {device.name} ({device.type})") logger.warning(f"No accessory mapping for device: {device.name} ({device.type})")
except Exception as e: except Exception as e:

View File

@@ -18,6 +18,7 @@ class WindowSetbackObjects(BaseModel):
thermostats: list[str] = Field(..., min_length=1, description="Thermostats to control") thermostats: list[str] = Field(..., min_length=1, description="Thermostats to control")
class WindowSetbackRule(Rule): class WindowSetbackRule(Rule):
""" """
Window setback automation rule. Window setback automation rule.
@@ -31,22 +32,37 @@ class WindowSetbackRule(Rule):
thermostats: List of thermostat device IDs to control (required, min 1) thermostats: List of thermostat device IDs to control (required, min 1)
params: params:
eco_target: Temperature to set when window opens (default: 16.0) eco_target: Temperature to set when window opens (default: 16.0)
open_min_secs: Minimum seconds window must be open before triggering (default: 20)
close_min_secs: Minimum seconds window must be closed before restoring (default: 20)
previous_target_ttl_secs: How long to remember previous temperature (default: 86400) previous_target_ttl_secs: How long to remember previous temperature (default: 86400)
State storage (Redis keys): State storage (Redis keys):
rule:{rule_id}:contact:{device_id}:state -> "open" | "closed"
rule:{rule_id}:contact:{device_id}:ts -> ISO timestamp of last change
rule:{rule_id}:thermo:{device_id}:current_target -> Current target temp (updated on every STATE) rule:{rule_id}:thermo:{device_id}:current_target -> Current target temp (updated on every STATE)
rule:{rule_id}:thermo:{device_id}:previous -> Previous target temp (saved on window open, deleted on restore) rule:{rule_id}:thermo:{device_id}:previous -> Previous target temp (saved on window open, deleted on restore)
rule:{rule_id}:contact:{device_id}:is_open -> "1" if open, "0" if closed
rule:{rule_id}:state -> Overall rule state -> "1" if thermostats set to eco, "0" otherwise
Logic: Logic:
1. Thermostat STATE events → update current_target in Redis 1. Thermostat STATE events → update current_target in Redis
2. Window opens → copy current_target to previous, then set to eco_target 2. Window opens → copy current_target to previous, then set to eco_target
3. Window closes → restore from previous, then delete previous key 3. Window closes → restore from previous, then delete previous key
""" """
@staticmethod
def __get_redis_key_current_target(rule_id: str, thermo_id: str) -> str:
"""Get Redis key for current target temperature of a thermostat"""
return f"rule:{rule_id}:thermo:{thermo_id}:current_target"
@staticmethod
def __get_redis_key_previous_target(rule_id: str, thermo_id: str) -> str:
"""Get Redis key for previous target temperature of a thermostat"""
return f"rule:{rule_id}:thermo:{thermo_id}:previous"
@staticmethod
def __get_redis_key_contact_state(rule_id: str, contact_id: str) -> str:
"""Get Redis key for contact sensor state"""
return f"rule:{rule_id}:contact:{contact_id}:is_open"
@staticmethod
def __get_redis_key_rule_state(rule_id: str) -> str:
"""Get Redis key for overall rule state"""
return f"rule:{rule_id}:state"
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self._validated_objects: dict[str, WindowSetbackObjects] = {} self._validated_objects: dict[str, WindowSetbackObjects] = {}
@@ -129,43 +145,50 @@ class WindowSetbackRule(Rule):
if not contact_state: if not contact_state:
ctx.logger.warning(f"Contact event missing 'contact' field: {evt}") ctx.logger.warning(f"Contact event missing 'contact' field: {evt}")
return return
# Store current state and timestamp contact_state_key = WindowSetbackRule.__get_redis_key_contact_state(desc.id, device_id)
state_key = f"rule:{desc.id}:contact:{device_id}:state" await ctx.redis.set(contact_state_key, '1' if contact_state == 'open' else '0')
ts_key = f"rule:{desc.id}:contact:{device_id}:ts"
# Check if any contact is open
await ctx.redis.set(state_key, contact_state) is_open = False
await ctx.redis.set(ts_key, event_ts) for contact_id in desc.objects.get('contacts', []):
state_key = WindowSetbackRule.__get_redis_key_contact_state(desc.id, contact_id)
if contact_state == 'open': state_val = await ctx.redis.get(state_key)
await self._on_window_opened(desc, ctx) if state_val == '1':
elif contact_state == 'closed': is_open = True
await self._on_window_closed(desc, ctx) break
async def _on_window_opened(self, desc: RuleDescriptor, ctx: RuleContext) -> None: rule_state_key = WindowSetbackRule.__get_redis_key_rule_state(desc.id)
""" current_rule_state = await ctx.redis.get(rule_state_key)
Window opened - save current temperatures, then set thermostats to eco. if is_open and current_rule_state != '1':
# At least one contact is open, and we are not already in eco mode
Important: We must save the current target BEFORE setting to eco, await self._set_eco_mode(desc, ctx)
otherwise we'll save the eco temperature instead of the original. await ctx.redis.set(rule_state_key, '1')
""" elif not is_open and current_rule_state != '0':
eco_target = desc.params.get('eco_target', 16.0) # All contacts are closed, and we are currently in eco mode
await self._unset_eco_mode(desc, ctx)
await ctx.redis.set(rule_state_key, '0')
async def _set_eco_mode(self, desc: RuleDescriptor, ctx: RuleContext) -> None:
"""Set thermostats to eco temperature when window opens."""
eco_target = desc.params.get('eco_target', 7.0)
target_thermostats = desc.objects.get('thermostats', []) target_thermostats = desc.objects.get('thermostats', [])
ttl_secs = desc.params.get('previous_target_ttl_secs', 86400) ttl_secs = desc.params.get('previous_target_ttl_secs', 86400)
ctx.logger.info( ctx.logger.info(
f"Rule {desc.id}: Window opened, setting {len(target_thermostats)} " f"Rule {desc.id}: At least one window is opened, setting {len(target_thermostats)} "
f"thermostats to eco temperature {eco_target}°C" f"thermostats to eco temperature {eco_target}°C"
) )
# FIRST: Save current target temperatures as "previous" (before we change them!) # FIRST: Save current target temperatures as "previous" (before we change them!)
for thermo_id in target_thermostats: for thermo_id in target_thermostats:
current_key = f"rule:{desc.id}:thermo:{thermo_id}:current_target" current_key = WindowSetbackRule.__get_redis_key_current_target(desc.id, thermo_id)
current_temp_str = await ctx.redis.get(current_key) current_temp_str = await ctx.redis.get(current_key)
if current_temp_str: if current_temp_str:
# Save current as previous (with TTL) # Save current as previous (with TTL)
prev_key = f"rule:{desc.id}:thermo:{thermo_id}:previous" prev_key = WindowSetbackRule.__get_redis_key_previous_target(desc.id, thermo_id)
await ctx.redis.set(prev_key, current_temp_str, ttl_secs=ttl_secs) await ctx.redis.set(prev_key, current_temp_str, ttl_secs=ttl_secs)
ctx.logger.debug( ctx.logger.debug(
f"Saved previous target for {thermo_id}: {current_temp_str}°C" f"Saved previous target for {thermo_id}: {current_temp_str}°C"
@@ -182,24 +205,20 @@ class WindowSetbackRule(Rule):
ctx.logger.debug(f"Set {thermo_id} to {eco_target}°C") ctx.logger.debug(f"Set {thermo_id} to {eco_target}°C")
except Exception as e: except Exception as e:
ctx.logger.error(f"Failed to set {thermo_id}: {e}") ctx.logger.error(f"Failed to set {thermo_id}: {e}")
async def _on_window_closed(self, desc: RuleDescriptor, ctx: RuleContext) -> None:
""" async def _unset_eco_mode(self, desc: RuleDescriptor, ctx: RuleContext) -> None:
Window closed - restore previous temperatures. """Restore thermostats to previous temperature when window closes."""
Note: This is simplified. A production implementation would check
close_min_secs and use a timer/scheduler.
"""
target_thermostats = desc.objects.get('thermostats', []) target_thermostats = desc.objects.get('thermostats', [])
ctx.logger.info( ctx.logger.info(
f"Rule {desc.id}: Window closed, restoring {len(target_thermostats)} " f"Rule {desc.id}: All windows closed, restoring {len(target_thermostats)} "
f"thermostats to previous temperatures" f"thermostats to previous temperatures"
) )
# Restore previous temperatures # Restore previous temperatures
for thermo_id in target_thermostats: for thermo_id in target_thermostats:
prev_key = f"rule:{desc.id}:thermo:{thermo_id}:previous" prev_key = WindowSetbackRule.__get_redis_key_previous_target(desc.id, thermo_id)
prev_temp_str = await ctx.redis.get(prev_key) prev_temp_str = await ctx.redis.get(prev_key)
if prev_temp_str: if prev_temp_str:
@@ -240,7 +259,7 @@ class WindowSetbackRule(Rule):
return # No target in this state update return # No target in this state update
# Store current target (always update, even if it's the eco temperature) # Store current target (always update, even if it's the eco temperature)
current_key = f"rule:{desc.id}:thermo:{device_id}:current_target" current_key = WindowSetbackRule.__get_redis_key_current_target(desc.id, device_id)
ttl_secs = desc.params.get('previous_target_ttl_secs', 86400) ttl_secs = desc.params.get('previous_target_ttl_secs', 86400)
await ctx.redis.set(current_key, str(current_target), ttl_secs=ttl_secs) await ctx.redis.set(current_key, str(current_target), ttl_secs=ttl_secs)

View File

@@ -326,41 +326,6 @@ devices:
ieee_address: "0xf0d1b8be2409f569" ieee_address: "0xf0d1b8be2409f569"
model: "4058075729063" model: "4058075729063"
vendor: "LEDVANCE" vendor: "LEDVANCE"
- device_id: licht_flur_oben_am_spiegel
homekit_aid: 22
name: Spiegel
type: light
cap_version: "light@1.2.0"
technology: zigbee2mqtt
features:
power: true
brightness: true
color_temperature: true
topics:
state: "zigbee2mqtt/0x842e14fffefe4ba4"
set: "zigbee2mqtt/0x842e14fffefe4ba4/set"
metadata:
friendly_name: "Licht Flur oben am Spiegel"
ieee_address: "0x842e14fffefe4ba4"
model: "LED1732G11"
vendor: "IKEA"
- device_id: experimentlabtest
homekit_aid: 23
name: Test Lampe
type: light
cap_version: "light@1.2.0"
technology: zigbee2mqtt
features:
power: true
brightness: true
topics:
state: "zigbee2mqtt/0xf0d1b80000195038"
set: "zigbee2mqtt/0xf0d1b80000195038/set"
metadata:
friendly_name: "ExperimentLabTest"
ieee_address: "0xf0d1b80000195038"
model: "4058075208421"
vendor: "LEDVANCE"
- device_id: thermostat_wolfgang - device_id: thermostat_wolfgang
homekit_aid: 24 homekit_aid: 24
name: Heizung name: Heizung
@@ -506,21 +471,16 @@ devices:
name: Heizung name: Heizung
type: thermostat type: thermostat
cap_version: "thermostat@1.0.0" cap_version: "thermostat@1.0.0"
technology: max technology: zigbee2mqtt
features: features:
mode: true heating: true
target: true temperature_range:
current: false - 5
- 30
temperature_step: 0.5
topics: topics:
set: "homegear/instance1/set/48/1/SET_TEMPERATURE" state: "zigbee2mqtt/0x003c84fffebdcc28"
state: "homegear/instance1/plain/48/1/SET_TEMPERATURE" set: "zigbee2mqtt/0x003c84fffebdcc28/set"
metadata:
friendly_name: "Thermostat Bad Unten"
location: "Bad Unten"
vendor: "eQ-3"
model: "MAX! Thermostat"
peer_id: "48"
channel: "1"
- device_id: sterne_wohnzimmer - device_id: sterne_wohnzimmer
homekit_aid: 32 homekit_aid: 32
name: Sterne name: Sterne
@@ -843,17 +803,6 @@ devices:
topics: topics:
state: "zigbee2mqtt/0xf0d1b8000017515d" state: "zigbee2mqtt/0xf0d1b8000017515d"
set: "zigbee2mqtt/0xf0d1b8000017515d/set" set: "zigbee2mqtt/0xf0d1b8000017515d/set"
- device_id: licht_kommode_schlafzimmer
homekit_aid: 65
name: Kommode Schlafzimmer
type: relay
cap_version: "relay@1.0.0"
technology: tasmota
features:
power: true
topics:
set: "cmnd/tasmota/04/POWER"
state: "stat/tasmota/04/POWER"
- device_id: licht_fensterbank_esszimmer - device_id: licht_fensterbank_esszimmer
homekit_aid: 66 homekit_aid: 66
name: Fensterbank Esszimmer name: Fensterbank Esszimmer
@@ -1034,11 +983,207 @@ devices:
homekit_aid: 82 homekit_aid: 82
name: Herdlicht name: Herdlicht
type: light type: light
cap_version: "relay@1.0.0" cap_version: "light@1.2.0"
technology: zigbee2mqtt technology: zigbee2mqtt
features: features:
power: true power: true
brightness: true brightness: true
topics: topics:
state: "zigbee2mqtt/herdlicht" state: "zigbee2mqtt/0x64028ffffe50e79e"
set: "zigbee2mqtt/herdlicht/set" set: "zigbee2mqtt/0x64028ffffe50e79e/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"
- device_id: licht_flur_oben_am_spiegel
homekit_aid: 95
name: Spiegel
type: light
cap_version: "light@1.2.0"
technology: zigbee2mqtt
features:
power: true
brightness: true
topics:
state: "zigbee2mqtt/0xf0d1b80000195038"
set: "zigbee2mqtt/0xf0d1b80000195038/set"
- device_id: licht_kommode_schlafzimmer
homekit_aid: 96
name: Kommode Schlafzimmer
type: light
cap_version: "light@1.2.0"
technology: zigbee2mqtt
features:
power: true
brightness: true
color_temperature: true
topics:
state: "zigbee2mqtt/0x842e14fffefe4ba4"
set: "zigbee2mqtt/0x842e14fffefe4ba4/set"
- device_id: kontakt_test_1
homekit_aid: 97
name: Kontakt Test 1
type: contact
cap_version: contact_sensor@1.0.0
technology: test
topics:
state: test/kontakt1/state
features: {}
- device_id: kontakt_test_2
homekit_aid: 98
name: Kontakt Test 2
type: contact
cap_version: contact_sensor@1.0.0
technology: test
topics:
state: test/kontakt2/state
features: {}
- device_id: thermostat_test
homekit_aid: 99
name: Thermostat Test
type: thermostat
cap_version: "thermostat@1.0.0"
technology: test
features:
heating: true
temperature_range:
- 5
- 30
temperature_step: 0.5
topics:
state: "test/thermostat1/state"
set: "test/thermostat1/set"

View File

@@ -1,10 +1,13 @@
version: 1 version: 1
groups: groups:
- id: "kueche_lichter" - id: "kueche_lichter"
name: "Küche alle Lampen" name: "Küche alle Lampen ausser Putzlicht"
selector: device_ids:
type: "light" - kueche_deckenlampe
room: "Küche" - licht_spuele_kueche
- herdlicht
- kueche_fensterbank_licht
- regallicht_kueche
capabilities: capabilities:
power: true power: true
brightness: true brightness: true
@@ -16,21 +19,25 @@ groups:
capabilities: capabilities:
power: true power: true
- id: "schlafzimmer_lichter"
name: "Schlafzimmer alle Lampen"
selector:
type: "light"
room: "Schlafzimmer"
capabilities:
power: true
brightness: true
- id: "schlafzimmer_schlummer_licht" - id: "schlafzimmer_schlummer_licht"
name: "Schlafzimmer Schlummerlicht" name: "Schlafzimmer Schlummerlicht"
device_ids: device_ids:
- bettlicht_patty - bettlicht_patty
- bettlicht_wolfgang - bettlicht_wolfgang
- medusalampe_schlafzimmer - 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: capabilities:
power: true power: true
brightness: true brightness: true

View File

@@ -148,6 +148,10 @@ rooms:
title: Herdlicht title: Herdlicht
icon: 💡 icon: 💡
rank: 145 rank: 145
- device_id: regallicht_kueche
title: Regallicht Küche
icon: 💡
rank: 146
- device_id: thermostat_kueche - device_id: thermostat_kueche
title: Kueche title: Kueche
icon: 🌡️ icon: 🌡️
@@ -261,6 +265,14 @@ rooms:
title: Schranklicht vor Küche title: Schranklicht vor Küche
icon: 💡 icon: 💡
rank: 232 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 - device_id: sensor_flur
title: Temperatur & Luftfeuchte title: Temperatur & Luftfeuchte
icon: 🌡️ icon: 🌡️
@@ -341,6 +353,30 @@ rooms:
title: Gartenlicht vorne title: Gartenlicht vorne
icon: 💡 icon: 💡
rank: 291 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 - id: garage
name: Garage name: Garage
devices: devices:
@@ -367,6 +403,21 @@ rooms:
title: Werkstatt Licht title: Werkstatt Licht
icon: 💡 icon: 💡
rank: 350 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

View File

@@ -1,9 +1,19 @@
# Rules Configuration
# Auto-generated from devices.yaml
rules: rules:
- id: window_setback_bad_unten
enabled: true
name: Fensterabsenkung Bad Unten
type: window_setback@1.0
objects:
contacts:
- kontakt_bad_unten_strasse
thermostats:
- thermostat_bad_unten
params:
eco_target: 5.0
open_min_secs: 20
close_min_secs: 20
- id: window_setback_esszimmer - id: window_setback_esszimmer
enabled: false enabled: true
name: Fensterabsenkung Esszimmer name: Fensterabsenkung Esszimmer
type: window_setback@1.0 type: window_setback@1.0
objects: objects:
@@ -13,12 +23,27 @@ rules:
thermostats: thermostats:
- thermostat_esszimmer - thermostat_esszimmer
params: params:
eco_target: 16.0 eco_target: 5.0
open_min_secs: 20
close_min_secs: 20
previous_target_ttl_secs: 86400
- id: window_setback_wohnzimmer
enabled: true
name: Fensterabsenkung Wohnzimmer
type: window_setback@1.0
objects:
contacts:
- kontakt_wohnzimmer_garten_links
- kontakt_wohnzimmer_garten_rechts
thermostats:
- thermostat_wohnzimmer
params:
eco_target: 5.0
open_min_secs: 20 open_min_secs: 20
close_min_secs: 20 close_min_secs: 20
previous_target_ttl_secs: 86400 previous_target_ttl_secs: 86400
- id: window_setback_kueche - id: window_setback_kueche
enabled: false enabled: true
name: Fensterabsenkung Küche name: Fensterabsenkung Küche
type: window_setback@1.0 type: window_setback@1.0
objects: objects:
@@ -30,12 +55,12 @@ rules:
thermostats: thermostats:
- thermostat_kueche - thermostat_kueche
params: params:
eco_target: 16.0 eco_target: 5.0
open_min_secs: 20 open_min_secs: 20
close_min_secs: 20 close_min_secs: 20
previous_target_ttl_secs: 86400 previous_target_ttl_secs: 86400
- id: window_setback_patty - id: window_setback_patty
enabled: false enabled: true
name: Fensterabsenkung Arbeitszimmer Patty name: Fensterabsenkung Arbeitszimmer Patty
type: window_setback@1.0 type: window_setback@1.0
objects: objects:
@@ -46,12 +71,12 @@ rules:
thermostats: thermostats:
- thermostat_patty - thermostat_patty
params: params:
eco_target: 16.0 eco_target: 5.0
open_min_secs: 20 open_min_secs: 20
close_min_secs: 20 close_min_secs: 20
previous_target_ttl_secs: 86400 previous_target_ttl_secs: 86400
- id: window_setback_schlafzimmer - id: window_setback_schlafzimmer
enabled: false enabled: true
name: Fensterabsenkung Schlafzimmer name: Fensterabsenkung Schlafzimmer
type: window_setback@1.0 type: window_setback@1.0
objects: objects:
@@ -60,22 +85,7 @@ rules:
thermostats: thermostats:
- thermostat_schlafzimmer - thermostat_schlafzimmer
params: params:
eco_target: 16.0 eco_target: 5.0
open_min_secs: 20
close_min_secs: 20
previous_target_ttl_secs: 86400
- id: window_setback_wohnzimmer
enabled: false
name: Fensterabsenkung Wohnzimmer
type: window_setback@1.0
objects:
contacts:
- kontakt_wohnzimmer_garten_links
- kontakt_wohnzimmer_garten_rechts
thermostats:
- thermostat_wohnzimmer
params:
eco_target: 16.0
open_min_secs: 20 open_min_secs: 20
close_min_secs: 20 close_min_secs: 20
previous_target_ttl_secs: 86400 previous_target_ttl_secs: 86400
@@ -89,6 +99,33 @@ rules:
thermostats: thermostats:
- thermostat_wolfgang - thermostat_wolfgang
params: params:
eco_target: 16.0 eco_target: 5.0
open_min_secs: 20
close_min_secs: 20
- id: window_setback_bad_oben
enabled: true
name: Fensterabsenkung Bad Oben
type: window_setback@1.0
objects:
contacts:
- kontakt_bad_oben_strasse
thermostats:
- thermostat_bad_oben
params:
eco_target: 5.0
open_min_secs: 20
close_min_secs: 20
- id: window_setback_test
enabled: true
name: Fensterabsenkung Test
type: window_setback@1.0
objects:
contacts:
- kontakt_test_1
- kontakt_test_2
thermostats:
- thermostat_test
params:
eco_target: 7.0
open_min_secs: 20 open_min_secs: 20
close_min_secs: 20 close_min_secs: 20

15
tools/deploy-configuration.sh Executable file
View File

@@ -0,0 +1,15 @@
#!/bin/bash
NAMESPACE=homea2
kubectl create configmap home-automation-config \
--from-file=devices.yaml=config/devices.yaml \
--from-file=groups.yaml=config/groups.yaml \
--from-file=layout.yaml=config/layout.yaml \
--from-file=rules.yaml=config/rules.yaml \
--from-file=scenes.yaml=config/scenes.yaml \
--namespace=$NAMESPACE \
--dry-run=client -o yaml | kubectl apply -f -
kubectl apply -f deployment/configmap.yaml -n $NAMESPACE