From aaab248f6c8335181857fd80c31f247be796c999 Mon Sep 17 00:00:00 2001 From: Bastian Schnorbus Date: Sun, 22 Mar 2026 13:08:27 +0100 Subject: [PATCH] support for hm cover w/o specific action state --- pyscript/hmip_wrc6_schlafzimmer.py | 142 +++++++++++++++++++++-------- 1 file changed, 106 insertions(+), 36 deletions(-) diff --git a/pyscript/hmip_wrc6_schlafzimmer.py b/pyscript/hmip_wrc6_schlafzimmer.py index b67553e..b994686 100644 --- a/pyscript/hmip_wrc6_schlafzimmer.py +++ b/pyscript/hmip_wrc6_schlafzimmer.py @@ -1,37 +1,89 @@ # ============================================================================= -# Homematic 6x Multischalter – Rollosteuerung +# Homematic 6x Multischalter - Rollosteuerung (3 Rollos) # ============================================================================= # Datei ablegen unter: config/pyscript/rollo_multischalter.py # -# Konfiguration: -# DEVICE_ADDRESS → Homematic-Adresse des Multischalters (aus HA-Gerätebeschreibung) -# COVER_ENTITY → Entity-ID des Rollos in Home Assistant -# SUBTYPE_LEFT → subtype-Wert der linken Taste (aus Event auslesen) -# SUBTYPE_RIGHT → subtype-Wert der rechten Taste (aus Event auslesen) +# 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 % # -# Tastenbelegung: -# 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! -COVER_ENTITY = "cover.shelly_c4a2f1" # Entity-ID des Rollos -SUBTYPE_LEFT = 1 # subtype linke Taste ← ggf. anpassen -SUBTYPE_RIGHT = 2 # subtype rechte Taste ← ggf. 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.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 zurück, wenn das Rollo laut HA gerade fährt.""" + """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.""" @@ -39,44 +91,62 @@ def handle_keypress(**kwargs): subtype = kwargs.get("subtype") action = kwargs.get("type", "") # "press_short" | "press_long" - # Nur Events für unseren Schalter verarbeiten + # Nur Events fuer unseren Schalter verarbeiten if address != DEVICE_ADDRESS: return - if subtype == SUBTYPE_LEFT: + 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(COVER_ENTITY) + _toggle_up(entity_id, no_state_feedback) elif action == "press_long": - _set_position(COVER_ENTITY, 70) - - elif subtype == SUBTYPE_RIGHT: + _set_position(entity_id, 70) + if no_state_feedback: + _set_stopped(entity_id) + elif side == "right": if action == "press_short": - _toggle_down(COVER_ENTITY) + _toggle_down(entity_id, no_state_feedback) elif action == "press_long": - _set_position(COVER_ENTITY, 20) + _set_position(entity_id, 20) + if no_state_feedback: + _set_stopped(entity_id) -def _toggle_up(entity_id: str): - """Kurzer Druck links: Hochfahren – oder Stopp, wenn das Rollo fährt.""" - if _is_moving(entity_id): - log.info(f"[Rollo] Stopp (war in Bewegung) → {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}") + 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): - """Kurzer Druck rechts: Runterfahren – oder Stopp, wenn das Rollo fährt.""" - if _is_moving(entity_id): - log.info(f"[Rollo] Stopp (war in Bewegung) → {entity_id}") +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}") + 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): - """Fährt das Rollo auf eine bestimmte Position (0 = ganz zu, 100 = ganz auf).""" - log.info(f"[Rollo] Zielposition {position}% → {entity_id}") + """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) \ No newline at end of file