From 6c2ad11d6aada5c93d797b9f343d1ddbc698c754 Mon Sep 17 00:00:00 2001 From: tthden Date: Fri, 15 May 2026 10:23:00 +0200 Subject: [PATCH] make a mqtt ble bridge service for meterkast --- mqtt_meterkast_bt_bridge.py | 221 ++++++++++++++++++++++++++++++++++++ 1 file changed, 221 insertions(+) create mode 100644 mqtt_meterkast_bt_bridge.py diff --git a/mqtt_meterkast_bt_bridge.py b/mqtt_meterkast_bt_bridge.py new file mode 100644 index 0000000..c2c6c28 --- /dev/null +++ b/mqtt_meterkast_bt_bridge.py @@ -0,0 +1,221 @@ +import asyncio +import paho.mqtt.client as mqtt +from bleak import BleakClient, BleakScanner +import time +import sys +from collections import deque # Efficiƫnte wachtrij voor de cach + +MAX_CACHE_SIZE = 1000 + +# BLE CONFIGURATIE (Moet exact matchen met de ESP32) +ble_pin = 341055 +ESP32_NAME = "ESP32_Meterkast_BLE" +SERVICE_UUID = "538b94e9-d5a3-4f16-877f-d208af9e9db9" +CHARACTERISTIC_TX_UUID = "637ce3a9-9203-42a6-87f0-bdbecf7b5b5e" # ESP32 -> PC +CHARACTERISTIC_RX_UUID = "56e9ab79-d1a8-497e-b08e-19922f7eeacf" # PC -> ESP32 + +MQTT_PORT = 1883 +MQTT_BROKER = "192.168.200.26" +MQTT_USER = "vlc_viewer" +MQTT_PASSWORD = "vlc_viewer12" +mqttClientId = "MeterKastController" + +# Topics configureren +# De PC luistert naar dit topic en stuurt ALLES wat hier binnenkomt door naar de ESP32 via BT +mqttTopicBelVoorTest = "BelVoorTest" +mqttTopicRestarCamera = "RestartCamera" +mqttTopicDisableVAlarm = "DisableVAlarm" +mqttTopicEnableVAlarm = "EnableVAlarm" +mqttTopicActivateFan = "ActivateFanMeterkast" +mqttTopicDeactivateFan = "DeactivateFanMeterkast" +mqttTopicActivateBuzzer = "ActivateAlarmBuzzer" +mqttTopicDeactivateBuzzer = "DeactivateAlarmBuzzer" +mqttTopicAlarmLampAan = "AlarmLampAan" +mqttTopicAlarmLampUit = "AlarmLAmpUit" + +# mqttTopicDalVerbruikt = "DalVerbruikt_kwh" +# mqttTopicPiekVerbruikt = "PiekVerbruikt_kwh" +# mqttTopicDalGeleverd = "DalGeleverd_kwh" +# mqttTopicPiekGeleverd = "PiekGeleverd_kwh" +# mqttTopicHuidigVerbruik = "HuidigVerbruik_kw" +# mqttTopicHuidigGeleverd = "HuidigGeleverd_kw" +# mqttTopicGasTotaal = "gas_totaal_m3" +# +# mqttTopicTempMeterKast = "TempMeterKast" +# mqttTopicTempThermostaat = "TempThermostaat" +# mqttTopicTempBuiten = "TempBuiten" +# mqttTopicTempKamer = "TempKamer" +# mqttTopicTempOverloop = "TempOverloop" +# mqttTopicTempMeterKastController = "TempMeterKastController" +# +# mqttTopicHcProtected = "HcProtected" +# mqttTopicHcAlarm = "HcAlarm" +# mqttTopicHcVAlarm = "HcVAlarm" +# mqttTopicHcAlarmBuzzer = "HcAlarmBuzzer" +# mqttTopicHcAlarmLamp = "HCLampAlarm" +# mqttTopicFanMeterkast = "FanMeterKast" +# mqttTopicAlarmStatus = "AlarmStatus" +# mqttTopicP1Line = "P1Line" +# mqttTopicP1InvalidLine = "P1InvalidLine" +# mqttTopicSerialOutput = "SerialOutput" +# +# mqtt_temperature_topic = "BrievenbusTemperatuur" +# mqtt_bel_topic = "BelVoor" +# mqtt_bel_state_topic = "BelVoorStatus" +# mqtt_test_topic = "BrievenbusTest" +# mqtt_debug_topic = "BrievenbusDebug" +# mqtt_box_state_topic = "BrievenbusStatus" +# mqtt_box_opened_topic = "BrievenbusOpen" +# mqtt_box_closed_topic = "BrievenbusDicht" +# mqtt_info_topic = "BrievenbusInfo" +# ====================================================== + +mqtt_client = None +mqtt_connected = False +message_cache = deque(maxlen=MAX_CACHE_SIZE) +ble_client = None + + +# --- CALLBACKS: MQTT STATUS EN ONTVANGST --- +def on_connect(client, userdata, flags, rc): + """Wordt aangeroepen als de verbinding met de MQTT broker slaagt of faalt.""" + global mqtt_connected + if rc == 0: + print("[MQTT] Succesvol ingelogd en verbonden met de broker!") + mqtt_connected = True + client.subscribe(mqttTopicBelVoorTest) + client.subscribe(mqttTopicRestarCamera) + client.subscribe(mqttTopicDisableVAlarm) + client.subscribe(mqttTopicEnableVAlarm) + client.subscribe(mqttTopicActivateFan) + client.subscribe(mqttTopicDeactivateFan) + client.subscribe(mqttTopicActivateBuzzer) + client.subscribe(mqttTopicDeactivateBuzzer) + client.subscribe(mqttTopicAlarmLampAan) + client.subscribe(mqttTopicAlarmLampUit) + print(f"[MQTT] Geabonneerd op topics") + flush_cache() + elif rc == 4: + print("[MQTT Fout] Verbinding geweigerd: Verkeerde gebruikersnaam of wachtwoord!") + mqtt_connected = False + else: + print(f"[MQTT Fout] Verbinding geweigerd met code: {rc}") + mqtt_connected = False + +def on_disconnect(client, userdata, rc): + """Wordt aangeroepen als de verbinding met de MQTT broker wegvalt.""" + global mqtt_connected + mqtt_connected = False + print("[MQTT] Verbinding verloren! Inkomende Bluetooth data wordt vanaf nu gecacht...") + +def on_message(client, userdata, msg): + """Wordt geactiveerd als er een MQTT bericht binnenkomt voor de ESP32.""" + global ser + try: + payload = msg.payload.decode('utf-8') + print(f"[MQTT -> BT] Ontvangen op {msg.topic}: {payload}") + + if ble_client and ble_client.is_connected: + # BLE vereist een asyncio-aanroep, paho-mqtt draait in een aparte thread. + # We schrijven de data asynchroon weg via de loop. + coro = ble_client.write_gatt_char(CHARACTERISTIC_RX_UUID, f"{msg.topic}\n".encode('utf-8')) + asyncio.run_coroutine_threadsafe(coro, asyncio.get_event_loop()) + except Exception as e: + print(f"[Fout] Probleem bij doorsturen naar Bluetooth: {e}") + +# --- BLE NOTIFICATIE CALLBACK --- +def ble_notification_handler(sender: int, data: bytearray): + """Wordt getriggerd als de ESP32 data pushed (ESP32 -> PC)""" + global mqtt_connected, mqtt_client + try: + line = data.decode('utf-8').strip() + if "|" in line: + topic, value = line.split("|", 1) + if mqtt_connected: + print(f"[BLE -> MQTT] Publiceren op {topic}: {value}") + mqtt_client.publish(topic, value) + else: + message_cache.append((topic, value)) + print(f"[Cache] MQTT offline. Opgeslagen: {topic} -> {value}") + except Exception as e: + print(f"[BLE Fout] Data verwerkingsfout: {e}") + +# --- CACHE LOGICA --- +def flush_cache(): + """Stuurt alle opgeslagen berichten in de cache alsnog naar MQTT.""" + global mqtt_client, mqtt_connected + if not message_cache: + return + + print(f"[Cache] MQTT hersteld. {len(message_cache)} berichten in de wachtrij verzenden...") + + while message_cache and mqtt_connected: + try: + topic, value = message_cache.popleft() + mqtt_client.publish(topic, value) + time.sleep(0.02) + except Exception as e: + print(f"[Cache Fout] Kon gecacht bericht niet verzenden: {e}") + break + + if not message_cache: + print("[Cache] Alle gecachte berichten succesvol verzonden!") + +# ==================== HOOFDPROGRAMMA ==================== +async def main(): + global ser, mqtt_client, mqtt_connected + + # 1. MQTT Client initialiseren en beveiliging instellen + print("[MQTT] Initialiseren...") + mqtt_client = mqtt.Client() + mqtt_client.on_connect = on_connect + mqtt_client.on_disconnect = on_disconnect + mqtt_client.on_message = on_message + + # Gebruikersnaam en wachtwoord koppelen aan de MQTT sessie + if MQTT_USER and MQTT_PASSWORD: + mqtt_client.username_pw_set(username=MQTT_USER, password=MQTT_PASSWORD) + + mqtt_client.loop_start() + + try: + mqtt_client.connect(MQTT_BROKER, MQTT_PORT, 60) + except Exception as e: + print(f"[MQTT Waarschuwing] Eerste broker-verbinding mislukt ({e}). Controleer inloggegevens en broker-status.") + + print("[Systeem] Bridge opgestart. Starten van Bluetooth-verbinding...") + + # 2. BLE Verbindings- en herstelloop + print("[Systeem] BLE Bridge actief. Zoeken naar ESP32...") + while True: + try: + # Zoek het apparaat op basis van de naam + device = await BleakScanner.find_device_by_name(ESP32_NAME) + if not device: + print(f"[BLE] Apparaat '{ESP32_NAME}' niet gevonden. Opnieuw scannen in 5s...") + await asyncio.sleep(5) + continue + + print(f"[BLE] Apparaat gevonden ({device.address}). Verbinden en authenticeren...") + + async with BleakClient(device.address) as client: + ble_client = client + print("[BLE] Verbonden! Activeren van data-notificaties...") + + # Start met luisteren naar de TX-characteristic van de ESP32 + await client.start_notify(CHARACTERISTIC_TX_UUID, ble_notification_handler) + + # Blijf in deze loop zolang de BLE verbinding in stand blijft + while client.is_connected: + await asyncio.sleep(1) + + except Exception as e: + print(f"[BLE Fout] Verbinding verbroken of geweigerd ({e}). Herstellen over 5s...") + ble_client = None + await asyncio.sleep(5) + +if __name__ == '__main__': + try: + asyncio.run(main()) + except KeyboardInterrupt: + print("\n[Systeem] Gestopt.")