Hier werden die Unterschiede zwischen zwei Versionen angezeigt.
| Beide Seiten der vorigen Revision Vorhergehende Überarbeitung Nächste Überarbeitung | Vorhergehende Überarbeitung | ||
|
fhemvswiz [2026/03/23 15:39] admin |
fhemvswiz [2026/03/23 15:45] (aktuell) admin |
||
|---|---|---|---|
| Zeile 1: | Zeile 1: | ||
| ====== Philips WiZ Lampe in FHEM einbinden ====== | ====== Philips WiZ Lampe in FHEM einbinden ====== | ||
| - | Voraussetzung: Parallel zu FHEM läuft ein Homebridge-Server. Dieser existiert bei mir, um FHEM Geräte auch über die Apple Homekit App bzw. via Siri zu steuern. Die zweite Voraussetzung ist ein MQTT-Server. Diesen benötige ich zum Schalten meiner Shellys und zum Auslesen meiner 433 Mhz Sensoren und Aktoren. | + | Voraussetzung: Parallel zu FHEM läuft ein Homebridge-Server. Dieser existiert bei mir, um FHEM Geräte auch über die Apple Homekit App bzw. via Siri zu steuern. Die zweite Voraussetzung ist ein MQTT-Server. Diesen benötige ich zum Schalten meiner Shellys und zum Auslesen meiner 433 Mhz Sensoren und Aktoren. Damit dieser via Python kommuniziert muss zuerst |
| + | |||
| + | pip install pywizlight paho-mqtt | ||
| + | |||
| + | dieses Modul installiert werden. Das Python3 auf Deinem System installiert ist, davon gehe ich aus. | ||
| **Beispiel:** WiZ Lampe im Schlafzimmer mit dem namen TVLicht | **Beispiel:** WiZ Lampe im Schlafzimmer mit dem namen TVLicht | ||
| Zeile 45: | Zeile 49: | ||
| import paho.mqtt.client as mqtt | import paho.mqtt.client as mqtt | ||
| from pywizlight import wizlight, PilotBuilder | from pywizlight import wizlight, PilotBuilder | ||
| + | | ||
| WIZ_IP = "192.168.1.67" | WIZ_IP = "192.168.1.67" | ||
| MQTT_HOST = "192.168.1.15" | MQTT_HOST = "192.168.1.15" | ||
| MQTT_PORT = 1883 | MQTT_PORT = 1883 | ||
| + | | ||
| TOPIC_SET = "wiz/tvlicht/set" | TOPIC_SET = "wiz/tvlicht/set" | ||
| TOPIC_STATE = "wiz/tvlicht/state" | TOPIC_STATE = "wiz/tvlicht/state" | ||
| TOPIC_PCT = "wiz/tvlicht/pct" | TOPIC_PCT = "wiz/tvlicht/pct" | ||
| TOPIC_STATUS = "wiz/tvlicht/status" | TOPIC_STATUS = "wiz/tvlicht/status" | ||
| + | | ||
| loop = None | loop = None | ||
| light = None | light = None | ||
| mqtt_client = None | mqtt_client = None | ||
| - | + | | |
| + | |||
| def clamp_pct(val): | def clamp_pct(val): | ||
| val = int(val) | val = int(val) | ||
| Zeile 67: | Zeile 71: | ||
| return 100 | return 100 | ||
| return val | return val | ||
| - | + | | |
| + | |||
| def fhem_to_brightness(pct): | def fhem_to_brightness(pct): | ||
| pct = clamp_pct(pct) | pct = clamp_pct(pct) | ||
| if pct == 0: | if pct == 0: | ||
| return 0 | return 0 | ||
| + | | ||
| # Arbeitsversion: pywizlight reagiert bei dir auf brightness=... | # Arbeitsversion: pywizlight reagiert bei dir auf brightness=... | ||
| # Kleine Werte strecken, damit 1..25 nicht so hart zusammenfallen | # Kleine Werte strecken, damit 1..25 nicht so hart zusammenfallen | ||
| if pct < 26: | if pct < 26: | ||
| pct = 26 | pct = 26 | ||
| + | | ||
| return pct | return pct | ||
| - | + | | |
| + | |||
| def raw_to_pct(raw): | def raw_to_pct(raw): | ||
| if raw is None: | if raw is None: | ||
| return 0 | return 0 | ||
| + | | ||
| raw = int(raw) | raw = int(raw) | ||
| + | | ||
| # Bei dir kommen offenbar direkt Prozentwerte zurück | # Bei dir kommen offenbar direkt Prozentwerte zurück | ||
| if raw <= 100: | if raw <= 100: | ||
| Zeile 93: | Zeile 97: | ||
| else: | else: | ||
| pct = round(raw * 100 / 255) | pct = round(raw * 100 / 255) | ||
| + | | ||
| # kosmetisch | # kosmetisch | ||
| if pct >= 99: | if pct >= 99: | ||
| pct = 100 | pct = 100 | ||
| + | | ||
| return max(0, min(100, pct)) | return max(0, min(100, pct)) | ||
| - | + | | |
| + | |||
| async def publish_state(): | async def publish_state(): | ||
| global light, mqtt_client | global light, mqtt_client | ||
| + | | ||
| state = await light.updateState() | state = await light.updateState() | ||
| is_on = state.get_state() | is_on = state.get_state() | ||
| raw = state.get_brightness() | raw = state.get_brightness() | ||
| pct = raw_to_pct(raw) | pct = raw_to_pct(raw) | ||
| + | | ||
| print(f"STATE -> on={is_on}, raw={raw}, pct={pct}") | print(f"STATE -> on={is_on}, raw={raw}, pct={pct}") | ||
| + | | ||
| mqtt_client.publish(TOPIC_STATE, "on" if is_on else "off", retain=True) | mqtt_client.publish(TOPIC_STATE, "on" if is_on else "off", retain=True) | ||
| mqtt_client.publish(TOPIC_PCT, str(pct), retain=True) | mqtt_client.publish(TOPIC_PCT, str(pct), retain=True) | ||
| Zeile 122: | Zeile 126: | ||
| retain=True | retain=True | ||
| ) | ) | ||
| - | + | | |
| + | |||
| async def set_pct(pct): | async def set_pct(pct): | ||
| global light | global light | ||
| + | | ||
| pct = clamp_pct(pct) | pct = clamp_pct(pct) | ||
| print(f"SET -> fhem_pct={pct}") | print(f"SET -> fhem_pct={pct}") | ||
| + | | ||
| if pct == 0: | if pct == 0: | ||
| await light.turn_off() | await light.turn_off() | ||
| return | return | ||
| + | | ||
| brightness = fhem_to_brightness(pct) | brightness = fhem_to_brightness(pct) | ||
| print(f"SET -> brightness={brightness}") | print(f"SET -> brightness={brightness}") | ||
| + | | ||
| await light.turn_on(PilotBuilder(brightness=brightness)) | await light.turn_on(PilotBuilder(brightness=brightness)) | ||
| - | + | | |
| + | |||
| async def handle_command(payload): | async def handle_command(payload): | ||
| payload = payload.strip() | payload = payload.strip() | ||
| print(f"MQTT CMD -> {payload}") | print(f"MQTT CMD -> {payload}") | ||
| + | | ||
| if payload == "on": | if payload == "on": | ||
| await light.turn_on() | await light.turn_on() | ||
| + | | ||
| elif payload == "off": | elif payload == "off": | ||
| await light.turn_off() | await light.turn_off() | ||
| + | | ||
| else: | else: | ||
| try: | try: | ||
| data = json.loads(payload) | data = json.loads(payload) | ||
| + | | ||
| if "brightness" in data: | if "brightness" in data: | ||
| await set_pct(data["brightness"]) | await set_pct(data["brightness"]) | ||
| Zeile 167: | Zeile 171: | ||
| else: | else: | ||
| return | return | ||
| + | | ||
| except Exception: | except Exception: | ||
| try: | try: | ||
| Zeile 174: | Zeile 178: | ||
| print(f"PARSE ERROR -> {e}") | print(f"PARSE ERROR -> {e}") | ||
| return | return | ||
| + | | ||
| await asyncio.sleep(1) | await asyncio.sleep(1) | ||
| await publish_state() | await publish_state() | ||
| - | + | | |
| + | |||
| async def poll_loop(): | async def poll_loop(): | ||
| global light | global light | ||
| light = wizlight(WIZ_IP) | light = wizlight(WIZ_IP) | ||
| + | | ||
| try: | try: | ||
| await publish_state() | await publish_state() | ||
| except Exception as e: | except Exception as e: | ||
| print(f"Initial publish error -> {e}") | print(f"Initial publish error -> {e}") | ||
| + | | ||
| while True: | while True: | ||
| try: | try: | ||
| Zeile 193: | Zeile 197: | ||
| except Exception as e: | except Exception as e: | ||
| print(f"Polling error -> {e}") | print(f"Polling error -> {e}") | ||
| + | | ||
| await asyncio.sleep(15) | await asyncio.sleep(15) | ||
| - | + | | |
| + | |||
| def on_connect(client, userdata, flags, rc, properties=None): | def on_connect(client, userdata, flags, rc, properties=None): | ||
| print("MQTT connected") | print("MQTT connected") | ||
| client.subscribe(TOPIC_SET) | client.subscribe(TOPIC_SET) | ||
| - | + | | |
| + | |||
| def on_message(client, userdata, msg): | def on_message(client, userdata, msg): | ||
| payload = msg.payload.decode("utf-8") | payload = msg.payload.decode("utf-8") | ||
| asyncio.run_coroutine_threadsafe(handle_command(payload), loop) | asyncio.run_coroutine_threadsafe(handle_command(payload), loop) | ||
| - | + | | |
| + | |||
| def start_mqtt(): | def start_mqtt(): | ||
| global mqtt_client | global mqtt_client | ||
| + | | ||
| mqtt_client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2) | mqtt_client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2) | ||
| mqtt_client.on_connect = on_connect | mqtt_client.on_connect = on_connect | ||
| Zeile 215: | Zeile 219: | ||
| mqtt_client.connect(MQTT_HOST, MQTT_PORT, 60) | mqtt_client.connect(MQTT_HOST, MQTT_PORT, 60) | ||
| mqtt_client.loop_start() | mqtt_client.loop_start() | ||
| - | + | | |
| + | |||
| def main(): | def main(): | ||
| global loop | global loop | ||
| + | | ||
| loop = asyncio.new_event_loop() | loop = asyncio.new_event_loop() | ||
| asyncio.set_event_loop(loop) | asyncio.set_event_loop(loop) | ||
| + | | ||
| start_mqtt() | start_mqtt() | ||
| + | | ||
| try: | try: | ||
| loop.run_until_complete(poll_loop()) | loop.run_until_complete(poll_loop()) | ||
| Zeile 232: | Zeile 236: | ||
| mqtt_client.disconnect() | mqtt_client.disconnect() | ||
| loop.close() | loop.close() | ||
| - | + | | |
| + | |||
| if __name__ == "__main__": | if __name__ == "__main__": | ||
| main() | main() | ||
| + | | ||