Compare commits

..

9 Commits

Author SHA1 Message Date
Bastian Schnorbus
f35c774373 changed shelly device in bedroom 2026-05-06 19:08:12 +02:00
Bastian Schnorbus
d74d35156f cleanup ccu leftover templates 2026-05-06 19:05:46 +02:00
Bastian Schnorbus
74cfb9b961 waste calendar 2026-05-06 19:05:00 +02:00
Bastian Schnorbus
450aaac38b hm integration switch 2026-05-05 20:18:45 +02:00
Bastian Schnorbus
ff1a3d54dc update cover id after hm integration switch 2026-05-05 20:16:54 +02:00
Bastian Schnorbus
a29b3a03b1 fix wrong key placement 2026-03-22 21:38:17 +01:00
Bastian Schnorbus
2598457dc8 rollo script for joel 2026-03-22 13:11:46 +01:00
Bastian Schnorbus
aaab248f6c support for hm cover w/o specific action state 2026-03-22 13:08:27 +01:00
Bastian Schnorbus
168ffa4a46 sz cover l support 2026-03-22 12:15:12 +01:00
4 changed files with 296 additions and 123 deletions

View File

@@ -74,23 +74,13 @@ automation: !include automations.yaml
script: !include scripts.yaml
scene: !include scenes.yaml
homematic:
local_port: 8060
interfaces:
wireless:
host: !secret ccu_host
username: !secret ccu_user
password: !secret ccu_password
callback_ip: !secret ccu_callback_ip
callback_port: 8060
ip:
host: !secret ccu_host
port: 2010
hosts:
ccu2:
host: !secret ccu_host
username: !secret ccu_user
password: !secret ccu_password
waste_collection_schedule:
sources:
- name: heidelberg_de
args:
street: "Ilse-Krall-Strasse"
collect_residual_waste_weekly: True
even_house_number: False
switch:
- platform: wake_on_lan
@@ -152,22 +142,6 @@ influxdb:
- sensor
template:
- switch:
- turn_on:
- data:
entity_id: homematic.ccu2
name: ThunderstormMode
value: true
action: homematic.set_variable_value
- turn_off:
- data:
entity_id: homematic.ccu2
name: ThunderstormMode
value: false
action: homematic.set_variable_value
default_entity_id: switch.ccu_tsm
state: '{{ is_state("binary_sensor.ccu_thunderstorm_mode","on") }}'
name: ccu_tsm
- switch:
- turn_on:
- action: script.fernsehschauenstarten
@@ -250,11 +224,6 @@ template:
state: '{{ states.sun.sun.attributes.elevation }}'
name: sun_elevation
- sensor:
- default_entity_id: sensor.ccu_service_meldungen
name: CCU Servicemeldungen
state: '{{ states.homematic.ccu2.state | int }}'
- sensor:
- name: Wohnzimmer Harmony
default_entity_id: sensor.remote_wohnzimmer_activity

View File

@@ -1,8 +1,8 @@
- platform: mqtt
name: bedroom_cover_r
command_topic: "shellies/shellyswitch25-76D19E/roller/0/command"
position_topic: "shellies/shellyswitch25-76D19E/roller/0/pos"
set_position_topic: "shellies/shellyswitch25-76D19E/roller/0/command/pos"
command_topic: "shellies/shelly2pmg4-ccba97d54bd4/roller/0/command"
position_topic: "shellies/shelly2pmg4-ccba97d54bd4/roller/0/pos"
set_position_topic: "shellies/shelly2pmg4-ccba97d54bd4/roller/0/command/pos"
payload_available: "true"
payload_not_available: "false"
qos: 1

View File

@@ -1,98 +1,150 @@
# Payload: {
# 'trigger_type': 'event',
# 'event_type': 'homematic.keypress',
# 'context': <homeassistant.core.Context object at 0x7f8c9eebe890>,
# 'name': '000B5D898D535E',
# 'param': 'PRESS_SHORT',
# 'channel': 3
# }
# =============================================================================
# Homematic 6x Multischalter - Rollosteuerung (3 Rollos)
# =============================================================================
# Datei ablegen unter: config/pyscript/rollo_multischalter.py
#
# Tastenbelegung pro Rollo:
# Linke Taste - kurzer Druck -> Hochfahren / Stopp (Wechsel)
# Linke Taste - langer Druck -> Position 70 %
# Rechte Taste - kurzer Druck -> Runterfahren / Stopp (Wechsel)
# Rechte Taste - langer Druck -> Position 20 %
#
# no_state_feedback: True -> fuer Rollos die keinen opening/closing Status
# zurueckgeben. Der Fahrzustand wird dann intern
# im Script getrackt (toggle-Logik per Flag).
# Nach TRAVEL_TIMEOUT Sekunden wird der interne
# State automatisch auf "stopped" zurueckgesetzt.
# =============================================================================
"""
This script is designed to work with a Home Assistant setup using the Homematic IP WRC6 remote control.
It listens for keypress events from the remote and performs actions based on the key pressed and the type of press (short or long).
# ── Anpassen ──────────────────────────────────────────────────────────────────
DEVICE_ADDRESS = "000B5D898D535E" # Homematic-Geräteadresse ← anpassen!
Documentation https://hacs-pyscript.readthedocs.io/en/latest/overview.html
"""
ROLLO_CONFIG = [
{"entity_id": "cover.hmip_froll_00115a498e0439", "subtype_left": 5, "subtype_right": 6, "no_state_feedback": True},
]
TASTER_ID_JOEL = "000B5D898D535E" # Replace with your actual Taster ID
ROLLO_ID_JOEL = "cover.00115a498e0439" # Replace with your actual Rollo ID
POSITION_UP = 66
POSITION_DOWN = 20
# Maximale Fahrzeit in Sekunden - danach wird der interne State automatisch
# auf "stopped" zurueckgesetzt (Rollo ist dann am Anschlag angekommen)
TRAVEL_TIMEOUT = 20
# ─────────────────────────────────────────────────────────────────────────────
last_action = dict()
# Lookup-Tabelle: subtype -> (entity_id, Seite, no_state_feedback)
_SUBTYPE_MAP = {}
for _rollo in ROLLO_CONFIG:
_SUBTYPE_MAP[_rollo["subtype_left"]] = (_rollo["entity_id"], "left", _rollo["no_state_feedback"])
_SUBTYPE_MAP[_rollo["subtype_right"]] = (_rollo["entity_id"], "right", _rollo["no_state_feedback"])
state.persist('pyscript.last_action')
# Interner Fahrzustand fuer Rollos ohne State-Feedback
# Moegliche Werte: "moving_up" | "moving_down" | "stopped"
_internal_state = {}
""" The state of the short-presses will be stored in the pyscript.last_action variable."""
""" In the pattern of {key}_{0|1} - 0 = not pressed, 1 = pressed """
def toggle_keystate(key):
"""Toggle keypress state."""
prefixed_key = f'key_{key}'
if prefixed_key in last_action:
last_action[prefixed_key] = 1 if last_action.get(prefixed_key) == 0 else 0
else:
last_action[prefixed_key] = 1
log.info(f"Key {key} toggled to {last_action[prefixed_key]}")
log.info(f"Key dict {last_action}")
# Laufende Timeout-Tasks je Entity - damit ein neuer Befehl den alten Timer abbricht
_timeout_tasks = {}
def get_keystate(key):
prefixed_key = f'key_{key}'
return last_action.get(prefixed_key, 0)
def _is_moving_internal(entity_id: str) -> bool:
"""Gibt True zurueck, wenn das Rollo laut internem Tracking gerade faehrt."""
return _internal_state.get(entity_id, "stopped") in ("moving_up", "moving_down")
@event_trigger('homematic.keypress')
def taster(**payload):
if payload.get("name") != TASTER_ID_JOEL:
def _is_moving(entity_id: str) -> bool:
"""Gibt True zurueck, wenn das Rollo laut HA gerade faehrt."""
current_state = state.get(entity_id)
if current_state is None:
return False
return current_state in ("opening", "closing")
async def _schedule_timeout(entity_id: str):
"""Setzt den internen State nach TRAVEL_TIMEOUT Sekunden auf 'stopped'."""
await task.sleep(TRAVEL_TIMEOUT)
if _internal_state.get(entity_id) in ("moving_up", "moving_down"):
log.info(f"[Rollo] Timeout - Fahrt abgeschlossen -> {entity_id}")
_internal_state[entity_id] = "stopped"
def _set_moving(entity_id: str, direction: str):
"""Setzt internen State und startet den Timeout-Timer."""
_internal_state[entity_id] = direction
# Vorherigen Timer abbrechen falls noch aktiv
if entity_id in _timeout_tasks:
_timeout_tasks[entity_id].cancel()
_timeout_tasks[entity_id] = task.create(_schedule_timeout(entity_id))
def _set_stopped(entity_id: str):
"""Setzt internen State auf stopped und bricht den Timeout-Timer ab."""
_internal_state[entity_id] = "stopped"
if entity_id in _timeout_tasks:
_timeout_tasks[entity_id].cancel()
del _timeout_tasks[entity_id]
@event_trigger("homematic.keypress")
def handle_keypress(**kwargs):
"""Wertet Homematic-Keypressereignisse aus."""
address = kwargs.get("address", "")
subtype = kwargs.get("subtype")
action = kwargs.get("type", "") # "press_short" | "press_long"
# Nur Events fuer unseren Schalter verarbeiten
if address != DEVICE_ADDRESS:
return
param = payload.get("param")
if param == "PRESS_SHORT":
# Check if the event is a short press
log.info("Short press detected")
if subtype not in _SUBTYPE_MAP:
return
channel_no = payload.get("channel")
entity_id, side, no_state_feedback = _SUBTYPE_MAP[subtype]
""" Offen """
if channel_no == 5:
# Perform action for channel 5
if get_keystate("5") == 1:
cover.open_cover(entity_id=ROLLO_ID_JOEL)
else:
cover.stop_cover(entity_id=ROLLO_ID_JOEL)
toggle_keystate("5")
elif channel_no == 6:
# Perform action for channel 6
if get_keystate("6") == 1:
cover.close_cover(entity_id=ROLLO_ID_JOEL)
else:
cover.stop_cover(entity_id=ROLLO_ID_JOEL)
toggle_keystate("6")
elif channel_no == 3:
# Perform action for channel 3
log.info("Channel 3 action")
# Add your action here, e.g., toggle a light
if param == "PRESS_LONG":
# Check if the event is a long press
log.info("Long press detected")
channel_no = payload.get("channel")
if channel_no == 5:
log.info("Channel 5 action")
cover.set_cover_position(entity_id=ROLLO_ID_JOEL, position=POSITION_UP)
elif channel_no == 6:
log.info("Channel 6 action")
cover.set_cover_position(entity_id=ROLLO_ID_JOEL, position=POSITION_DOWN)
if side == "left":
if action == "press_short":
_toggle_up(entity_id, no_state_feedback)
elif action == "press_long":
_set_position(entity_id, 70)
if no_state_feedback:
_set_stopped(entity_id)
elif side == "right":
if action == "press_short":
_toggle_down(entity_id, no_state_feedback)
elif action == "press_long":
_set_position(entity_id, 20)
if no_state_feedback:
_set_stopped(entity_id)
log.info(f"taster: {payload}")
def _toggle_up(entity_id: str, no_state_feedback: bool):
"""Kurzer Druck links: Hochfahren - oder Stopp, wenn das Rollo faehrt."""
moving = _is_moving_internal(entity_id) if no_state_feedback else _is_moving(entity_id)
if moving:
log.info(f"[Rollo] Stopp (war in Bewegung) -> {entity_id}")
cover.stop_cover(entity_id=entity_id)
if no_state_feedback:
_set_stopped(entity_id)
else:
log.info(f"[Rollo] Hochfahren -> {entity_id}")
cover.open_cover(entity_id=entity_id)
if no_state_feedback:
_set_moving(entity_id, "moving_up")
def _toggle_down(entity_id: str, no_state_feedback: bool):
"""Kurzer Druck rechts: Runterfahren - oder Stopp, wenn das Rollo faehrt."""
moving = _is_moving_internal(entity_id) if no_state_feedback else _is_moving(entity_id)
if moving:
log.info(f"[Rollo] Stopp (war in Bewegung) -> {entity_id}")
cover.stop_cover(entity_id=entity_id)
if no_state_feedback:
_set_stopped(entity_id)
else:
log.info(f"[Rollo] Runterfahren -> {entity_id}")
cover.close_cover(entity_id=entity_id)
if no_state_feedback:
_set_moving(entity_id, "moving_down")
def _set_position(entity_id: str, position: int):
"""Faehrt das Rollo auf eine bestimmte Position (0 = ganz zu, 100 = ganz auf)."""
log.info(f"[Rollo] Zielposition {position}% -> {entity_id}")
cover.set_cover_position(entity_id=entity_id, position=position)

View File

@@ -0,0 +1,152 @@
# =============================================================================
# Homematic 6x Multischalter - Rollosteuerung (3 Rollos)
# =============================================================================
# Datei ablegen unter: config/pyscript/rollo_multischalter.py
#
# Tastenbelegung pro Rollo:
# Linke Taste - kurzer Druck -> Hochfahren / Stopp (Wechsel)
# Linke Taste - langer Druck -> Position 70 %
# Rechte Taste - kurzer Druck -> Runterfahren / Stopp (Wechsel)
# Rechte Taste - langer Druck -> Position 20 %
#
# no_state_feedback: True -> fuer Rollos die keinen opening/closing Status
# zurueckgeben. Der Fahrzustand wird dann intern
# im Script getrackt (toggle-Logik per Flag).
# Nach TRAVEL_TIMEOUT Sekunden wird der interne
# State automatisch auf "stopped" zurueckgesetzt.
# =============================================================================
# ── Anpassen ──────────────────────────────────────────────────────────────────
DEVICE_ADDRESS = "000B5A4992908E" # Homematic-Geräteadresse ← anpassen!
ROLLO_CONFIG = [
{"entity_id": "cover.shelly_c4a2f1", "subtype_left": 1, "subtype_right": 2, "no_state_feedback": False},
{"entity_id": "cover.shelly2pmg4_ccba97d54bd4", "subtype_left": 3, "subtype_right": 4, "no_state_feedback": False},
{"entity_id": "cover.hmip_froll_00115a498e0439","subtype_left": 5, "subtype_right": 6, "no_state_feedback": True},
]
# Maximale Fahrzeit in Sekunden - danach wird der interne State automatisch
# auf "stopped" zurueckgesetzt (Rollo ist dann am Anschlag angekommen)
TRAVEL_TIMEOUT = 20
# ─────────────────────────────────────────────────────────────────────────────
# Lookup-Tabelle: subtype -> (entity_id, Seite, no_state_feedback)
_SUBTYPE_MAP = {}
for _rollo in ROLLO_CONFIG:
_SUBTYPE_MAP[_rollo["subtype_left"]] = (_rollo["entity_id"], "left", _rollo["no_state_feedback"])
_SUBTYPE_MAP[_rollo["subtype_right"]] = (_rollo["entity_id"], "right", _rollo["no_state_feedback"])
# Interner Fahrzustand fuer Rollos ohne State-Feedback
# Moegliche Werte: "moving_up" | "moving_down" | "stopped"
_internal_state = {}
# Laufende Timeout-Tasks je Entity - damit ein neuer Befehl den alten Timer abbricht
_timeout_tasks = {}
def _is_moving_internal(entity_id: str) -> bool:
"""Gibt True zurueck, wenn das Rollo laut internem Tracking gerade faehrt."""
return _internal_state.get(entity_id, "stopped") in ("moving_up", "moving_down")
def _is_moving(entity_id: str) -> bool:
"""Gibt True zurueck, wenn das Rollo laut HA gerade faehrt."""
current_state = state.get(entity_id)
if current_state is None:
return False
return current_state in ("opening", "closing")
async def _schedule_timeout(entity_id: str):
"""Setzt den internen State nach TRAVEL_TIMEOUT Sekunden auf 'stopped'."""
await task.sleep(TRAVEL_TIMEOUT)
if _internal_state.get(entity_id) in ("moving_up", "moving_down"):
log.info(f"[Rollo] Timeout - Fahrt abgeschlossen -> {entity_id}")
_internal_state[entity_id] = "stopped"
def _set_moving(entity_id: str, direction: str):
"""Setzt internen State und startet den Timeout-Timer."""
_internal_state[entity_id] = direction
# Vorherigen Timer abbrechen falls noch aktiv
if entity_id in _timeout_tasks:
_timeout_tasks[entity_id].cancel()
_timeout_tasks[entity_id] = task.create(_schedule_timeout(entity_id))
def _set_stopped(entity_id: str):
"""Setzt internen State auf stopped und bricht den Timeout-Timer ab."""
_internal_state[entity_id] = "stopped"
if entity_id in _timeout_tasks:
_timeout_tasks[entity_id].cancel()
del _timeout_tasks[entity_id]
@event_trigger("homematic.keypress")
def handle_keypress(**kwargs):
"""Wertet Homematic-Keypressereignisse aus."""
address = kwargs.get("address", "")
subtype = kwargs.get("subtype")
action = kwargs.get("type", "") # "press_short" | "press_long"
# Nur Events fuer unseren Schalter verarbeiten
if address != DEVICE_ADDRESS:
return
if subtype not in _SUBTYPE_MAP:
return
entity_id, side, no_state_feedback = _SUBTYPE_MAP[subtype]
if side == "left":
if action == "press_short":
_toggle_up(entity_id, no_state_feedback)
elif action == "press_long":
_set_position(entity_id, 70)
if no_state_feedback:
_set_stopped(entity_id)
elif side == "right":
if action == "press_short":
_toggle_down(entity_id, no_state_feedback)
elif action == "press_long":
_set_position(entity_id, 20)
if no_state_feedback:
_set_stopped(entity_id)
def _toggle_up(entity_id: str, no_state_feedback: bool):
"""Kurzer Druck links: Hochfahren - oder Stopp, wenn das Rollo faehrt."""
moving = _is_moving_internal(entity_id) if no_state_feedback else _is_moving(entity_id)
if moving:
log.info(f"[Rollo] Stopp (war in Bewegung) -> {entity_id}")
cover.stop_cover(entity_id=entity_id)
if no_state_feedback:
_set_stopped(entity_id)
else:
log.info(f"[Rollo] Hochfahren -> {entity_id}")
cover.open_cover(entity_id=entity_id)
if no_state_feedback:
_set_moving(entity_id, "moving_up")
def _toggle_down(entity_id: str, no_state_feedback: bool):
"""Kurzer Druck rechts: Runterfahren - oder Stopp, wenn das Rollo faehrt."""
moving = _is_moving_internal(entity_id) if no_state_feedback else _is_moving(entity_id)
if moving:
log.info(f"[Rollo] Stopp (war in Bewegung) -> {entity_id}")
cover.stop_cover(entity_id=entity_id)
if no_state_feedback:
_set_stopped(entity_id)
else:
log.info(f"[Rollo] Runterfahren -> {entity_id}")
cover.close_cover(entity_id=entity_id)
if no_state_feedback:
_set_moving(entity_id, "moving_down")
def _set_position(entity_id: str, position: int):
"""Faehrt das Rollo auf eine bestimmte Position (0 = ganz zu, 100 = ganz auf)."""
log.info(f"[Rollo] Zielposition {position}% -> {entity_id}")
cover.set_cover_position(entity_id=entity_id, position=position)