Benutzer-Werkzeuge

Webseiten-Werkzeuge


sonstiges:tuerklingel

Unterschiede

Hier werden die Unterschiede zwischen zwei Versionen angezeigt.

Link zu dieser Vergleichsansicht

Beide Seiten der vorigen RevisionVorhergehende Überarbeitung
Nächste Überarbeitung
Vorhergehende Überarbeitung
sonstiges:tuerklingel [2023-09-29 21:19] – [Sender in Haustelefon einbauen] schuballaasonstiges:tuerklingel [2026-05-07 02:21] (aktuell) – schuballaa
Zeile 1: Zeile 1:
-====== Türklingel ======+====== Klingeldetektor ======
  
-===== Übersicht =====+<note>Zurzeit ist die Funkbrücke in die E-Werkstatt und den Kreativraum außer Betrieb! Auch wird die Torklingel zurzeit überarbeitet!</note>
  
-Im Hackerspace ist am Eingang Bornstr. 14-15 eine Türklingel mit Sprechanlage (Türsprechstelle) installiert. Das Haustelefon befindet sich im Workshopraum.+Es wird unsere Telegram Gruppe gepostet sobald geklingelt wurde.
  
-Es wurde eine Möglichkeit gefunden, das Klingelsignal abzugreifen und in die Räume Bornstr. 16-17 zu verlängern (E- und Kreativwerkstatt).+{{:sonstiges:photo_2026-04-20_02-48-38.jpg?400|}}
  
-**Optional:**+[[https://schuballaa.hackerspace-bremen.de/telegram.php|HSHB Telegram Gruppe]]
  
-Es könnte zu jeder zeit an sender und oder Empfängern eine Li-ion Akku (via JST) angeschlossen werden und somit auch mobil betrieben werden z.b bei Veranstaltungen die im Hinnenhof stattfinden um kein Klingeln zu verpassen.+{{:sonstiges:photo_2026-04-19_21-24-52.jpg?400|}}
  
-Da sich die meisten Powerbanks bei geringer Last ausschalten, könnte es sein, dass die Powerbank bei mobilen betrieb die Funktion einstellen ([[https://apfelhirn.de/automatische-abschaltung-von-powerbanks-als-arduino-stromversorgung-bei-geringer-grundlast-verhindern/|https://apfelhirn.de/automatische-abschaltung-von-powerbanks-als-arduino-stromversorgung-bei-geringer-grundlast-verhindern/]]).+Der Klingeldetektor erkennt, ob an der Tür oder am Tor geklingelt wurde. 
 +===== Erkennungswege =====
  
 +  * **Tür:** Auswertung des Datenbusses des hauseigenen Klingelsystems (**QwikBus**)
 +  * **Tor:** Erkennung über **LDR (Lichtsensor)** am Empfänger der Torklingel
  
-===== Sprechanlage =====+===== Bei einem Klingelereignis =====
  
-Die Sprechanlage nutzt ein 2-Draht-Bussystem vom Typ [[http://www.str-elektronik.de/|STR]] QwikBus, welches die Türsprechstelle mit den Haustelefonen verbindet. Daher ist es nicht möglich, das Klingelsignal direkt abzugreifen, da es digital über den Bus übertragen wird.+  * LED-Visualisierung Ort (**Tor / Tür**) 
 +  * Akustische und visuelle Anzeige im Hauptraum 
 +  * Klingelsignal weitergabe an **E-Werkstatt** und **Kreativwerkstatt** (über eine **Funkbrücke**) 
 +  * Meldung an Telegram ([[https://t.me/joinchat/OwmGe1WZmRs1ZDJi|Gruppe]]) 
 +  * Meldung per **MQTT** im Hackerspace-System (für zukünftige Erweiterungen)
  
-Das Haustelefon ist vom Typ STR HT 3033. +===== Verhalten im Standby =====
  
-Das Steuergerät ist ein SP 333 ({{:sonstiges:einstellungen_sp333_version_2.pdf|Doku}}), welches im Hauskeller im Sicherungsschrank neben dem Netzteil NH 333 installiert ist. +  * Warten auf ein Klingelereignis 
-===== Pinout =====+  * LEDs werden ein- und ausgeblendet, um Standby und Funktionsbereitschaft zu visualisieren
  
-Öffnet man das Haustelefon über die einzig vorhandene Gehäuseschraube (die obere Gehäusehälfte lässt sich dann herunterklappen), so kommen innen zwei Steckverbinder zum Vorschein. 
  
-Die Belegung lässt sich durch ein Foto einer Relaisbox {{:sonstiges:aps658.pdf|AM333}}, die man hinzukaufen kann, erahnen. Ein Multimeter half bei der weiteren Analyse. Auf dem zweiten Pin von oben liegt Masse, auf dem zweiten Pin von unten wird das Klingelsignal ausgegeben (5 V, wenn ich mich recht erinnere).+===== Umgesetzte Extras =====
  
-{{:sonstiges:tuersprechstelle.png?200| }} +  * MQTT-Integration 
-{{:sonstiges:pinout.png?200| }}+  * Akustische und visuelle Anzeige im Hauptraum 
 +  * Funkverteilung in die anderen Räume 
 +  * LED-Visualisierung (Tor / Bornstraße)
  
-Mechanisch konnten die passenden Steckerweibchen nicht ermittelt werden. Daher greifen wir das Signal über zwei IC-Beinchen-Einsätze gedrehter IC-Sockel ab, die in den Steckverbindern verlässlich halten.+===== Danksagungen: =====
  
-===== Funk-Türklingel =====+  * **Marco** – Tips bei der Entwicklung der Hardware 
 +  * **Delpiño** – Unterstützung beim Auswertendes Datenbusses 
 +  * **Markus** – Idee zur LED-Visualisierung (Tor / Bornstraße) 
 +===== Technische Erklärung =====
  
-Da uns keine Leitung zwischen den Räumen Bornstr. 14-15 sowie 16-17 zur Verfügung steht, mussten wir auf eine [[http://www.amazon.de/dp/B00ISMW1DG|Funk-Türklingel]] mit zwei Empfängern zurückgreifen. Der Sender sowie die 2 Empfänger werden mit einem Standard 5 Volt USB Netzteil versorgt.+{{:sonstiges:photo_2026-04-04_01-50-41.jpg?400|}} 
 +==== Tür – QwikBus-Auswertung ====
  
-Wenn geklingelt wird, zieht ein n channel mosfet den Pin 4 des [[https://www.amazon.de/AZDelivery-Lolin-ESP32-Parent/dp/B086V1P4BL?th=1|esp32]] auf ground. Wenn dieses > 1000 ms auf Ground bleibt, wurde geklingelt und ein ESPNOW signal zu den Empfängern geschickt.+Das hauseigene Klingelsystem (QwikBus) überträgt Klingelereignisse als zeitkodierte elektrische Signale.
  
 +**Signalcharakteristik:**
 +  * Ruhespannung: ca. ~20 V
 +  * Aktivsignal: ca. ~18 V
 +  * Information in **zeitlichen Pulsfolgen**
  
-===== Sender in Haustelefon einbauen =====+**Hardware:** 
 +  * 1 nF um nur die Busänderungen zu dedektieren 
 +  * Spannungsteiler 
 +  * Schmitt-Trigger 
 +  * Mikrocontroller (ESP32)
  
-Der Sender ist ein ESP32 Lolin Lite welcher auf ein Trigger auf Pin 4 wartet (als sensor wird ein n chanel mosfet benutzt. (Bild: Patine mitte links)+**Verarbeitung:** 
 +  * Flankenerkennung (Interrupt) 
 +  * Zeitmessung im µs-Bereich 
 +  * Bildung von Sequenzen: 
 +  * Quantisierung zur Reduktion von Rauschen 
 +  * Mustervergleich (Pattern Matching) zur Klassifikation
  
-{{https://wiki.hackerspace-bremen.de/_media/sonstiges/klingel_sender.jpg?nolink&600x800|klingel_sender.jpg}}+**Ergebnis:** 
 +  * Zuordnung des Signals zu einem **Türklingel-Ereignis**
  
 +==== Tor – LDR-Auswertung ====
 +
 +Das Torklingeln wird über das optische Signal des Empfängers erkannt.
 +
 +**Signalquelle:**
 +  * Blinkende LED am Empfänger der Torklingel
 +
 +**Hardware:**
 +  * LDR (Lichtsensor)
 +  * Widerstand als Spannungsteiler
 +  * ADC-Eingang am Mikrocontroller
 +
 +**Verarbeitung:**
 +  * Kontinuierliche Helligkeitsmessung
 +  * Schwellwertbasierte Erkennung (Hell/Dunkel)
 +  * Zeitliche Analyse des Blinkmusters
 +  * Entprellung zur Vermeidung von Fehltriggern
 +
 +**Ergebnis:**
 +  * Zuordnung des Signals zu einem **Torklingel-Ereignis**
 +
 +----
 +
 +==== Ereignisverarbeitung ====
 +
 +Nach der Erkennung erfolgt die weitere Verarbeitung:
 +
 +  * Klassifikation: **Tür** oder **Tor**
 +  * Generierung eines Events
 +  * Veröffentlichung per **MQTT** im Hackerspace-System
 +  * Ansteuerung der **LED-Visualisierung (Tor / Bornstraße)**
 +  * Weitergabe der Information:
 +    * Akustisch und visuell im **Hauptraum**
 +    * Über **Funkbrücke** an **E-Werkstatt** und **Kreativwerkstatt**
 +{{:sonstiges:pxl_20260415_1832213091.jpg?400|}}
 +{{:sonstiges:mqtt.png?400|}}
 +{{:sonstiges:torsensor.jpg?400|}}
 +{{:sonstiges:photo_2026-04-04_01-50-57.jpg?400|}}
 +
 +==== Code ====
 +
 +<code>
 +
 +#include <WiFi.h>
 +#include <esp_wifi.h>
 +#include <PubSubClient.h>
 +#include <time.h>
 +#include <Preferences.h>
 +#include <WebServer.h>
 +#include <HTTPUpdateServer.h>
 +#include <ArduinoOTA.h>
 +#include <ESPmDNS.h>
 +#include <Arduino.h>
 +#include <cstring>
 +#include "esp32-hal-rmt.h"
 +#include <math.h>
 +
 +// =====================================================
 +// WLAN / MQTT
 +// =====================================================
 +const char* WIFI_SSID     = "Hackerspace";
 +const char* WIFI_PASSWORD = "****";
 +
 +const char* MQTT_HOST = "daniel-stingl.de";
 +const int   MQTT_PORT = ****;
 +const char* MQTT_USER = "***";
 +const char* MQTT_PASS = "***";
 +
 +const char* MQTT_TOPIC_TUER      = "/hshb/status/klingel/tuer";
 +const char* MQTT_TOPIC_TOR       = "/hshb/status/klingel/tor";
 +const char* MQTT_TOPIC_STATUS    = "/hshb/status/klingel/status";
 +const char* MQTT_TOPIC_TUER_TEST = "/hshb/status/klingel/tuertest";
 +const char* MQTT_TOPIC_TOR_TEST  = "/hshb/status/klingel/tortest";
 +
 +// =====================================================
 +// OTA / Web
 +// =====================================================
 +const char* OTA_HOSTNAME = "hshb-klingel";
 +const char* OTA_USER     = "****";
 +const char* OTA_PASS     = "****";
 +
 +// =====================================================
 +// NTP
 +// =====================================================
 +const char* NTP_SERVER = "pool.ntp.org";
 +const long  GMT_OFFSET_SEC = 3600;
 +const int   DAYLIGHT_OFFSET_SEC = 3600;
 +
 +// =====================================================
 +// MAC
 +// =====================================================
 +uint8_t newMACAddress[] = {0x30, 0xAF, 0xA0, 0x05, 0x1D, 0x63};
 +
 +// =====================================================
 +// Pins
 +// =====================================================
 +constexpr int bellPin    = 5;   // Trigger-Ausgang
 +constexpr int busPin     = 32;  // RMT RX
 +const int adcTorPin      = 33;  // ADC
 +const int testBellPin    = 26;  // Taster
 +
 +// =====================================================
 +// RMT / Nachrichten
 +// =====================================================
 +constexpr uint8_t hackerspaceBell[]  = {0x25, 0x2E, 0xC4, 0x00, 0xE8};
 +constexpr uint8_t hackerspaceBell2[] = {0x25, 0x2E, 0xE2, 0x00, 0xCA};
 +
 +constexpr size_t bufferSize = 16;
 +constexpr size_t maxSyms = RMT_MEM_NUM_BLOCKS_2 * RMT_SYMBOLS_PER_CHANNEL_BLOCK;
 +
 +rmt_data_t symbols[maxSyms];
 +size_t symbolCount = maxSyms;
 +
 +enum { P_BAD = -1, P_ZERO = 0, P_ONE = 1, P_START = 2, P_STOP = 3 };
 +
 +// =====================================================
 +// Zeiten / Zustände
 +// =====================================================
 +unsigned long lastBellTriggered       = 0;
 +unsigned long lastTorTriggerMillis    = 0;
 +unsigned long lastButtonTriggerMillis = 0;
 +unsigned long lastDebugMillis         = 0;
 +unsigned long lastStatusMillis        = 0;
 +unsigned long bellPulseStartMillis    = 0;
 +unsigned long lastWifiReconnectMs     = 0;
 +unsigned long lastMqttReconnectMs     = 0;
 +unsigned long lastNtpSyncMs           = 0;
 +
 +const unsigned long BellDebounceMs     = 300;
 +const unsigned long TorDebounceMs      = 11000;
 +const unsigned long ButtonDebounceMs   = 300;
 +const unsigned long Debuginterval      = 1000;
 +const unsigned long StatusMs           = 5000;
 +const unsigned long BellPulseMs        = 10;
 +const unsigned long WifiRetryMs        = 10000;
 +const unsigned long MqttRetryMs        = 5000;
 +const unsigned long NtpResyncMs        = 6UL * 60UL * 60UL * 1000UL;
 +const unsigned long LedEventDurationMs = 60000;  // 60 Sekunden
 +
 +const int adcTorThreshold = 1500;
 +
 +bool bellPulseActive = false;
 +bool timeValid = false;
 +bool lastButtonState = HIGH;
 +bool lastTorState = false;
 +
 +String lastState = "IDLE";
 +String lastSource = "-";
 +String lastDecodedHex = "-";
 +uint32_t lastDecodedLen = 0;
 +int adcTorValue = 0;
 +
 +// =========================
 +// LEDs
 +// =========================
 +const int ledTuerPin = 23;
 +const int ledTorPin  = 19;
 +
 +const int ledFreq = 5000;
 +const int ledResolution = 8;
 +
 +enum LedMode {
 +  LED_MODE_STANDBY,
 +  LED_MODE_SHOW_TUER,
 +  LED_MODE_SHOW_TOR
 +};
 +
 +LedMode ledMode = LED_MODE_STANDBY;
 +unsigned long ledModeUntilMs = 0;
 +unsigned long lastLedUpdateMs = 0;
 +unsigned long lastBlinkToggleMs = 0;
 +bool ledBlinkState = false;
 +
 +// =====================================================
 +// Objekte
 +// =====================================================
 +WiFiClient wifiClient;
 +PubSubClient mqttClient(wifiClient);
 +Preferences preferences;
 +WebServer httpServer(80);
 +HTTPUpdateServer httpUpdater;
 +
 +// =====================================================
 +// Vorwärtsdeklarationen
 +// =====================================================
 +bool connectWiFi();
 +bool ensureMqttConnected();
 +bool syncTime();
 +bool ensureTimeValid();
 +bool publishUnixTimeToTopic(const char* topic);
 +bool publishStatus();
 +void markMqttPendingTuer();
 +void markMqttPendingTor();
 +bool isMqttPendingTuer();
 +bool isMqttPendingTor();
 +void clearMqttPendingTuer();
 +void clearMqttPendingTor();
 +void processPendingMqtt();
 +void setupHttpRoutes();
 +void setupArduinoOta();
 +void triggerBell(bool fromButton);
 +void triggerTorEvent(const char* source);
 +void mqttCallback(char* topic, byte* payload, unsigned int length);
 +String buildDebugJson();
 +
 +int pulseType(uint16_t us);
 +size_t collectLows(const rmt_data_t *symbols, size_t symbolCount, uint16_t *lows);
 +int decodeByte(const uint16_t *pulses);
 +bool decodeMessage(const rmt_data_t *symbols, size_t symbolCount, uint8_t *msg, size_t &len);
 +bool messageContains(const uint8_t *msg, size_t len, const uint8_t *what, size_t whatLen);
 +
 +// =====================================================
 +// WLAN / Zeit / MQTT
 +// =====================================================
 +bool connectWiFi() {
 +  if (WiFi.status() == WL_CONNECTED) return true;
 +
 +  unsigned long now = millis();
 +  if (now - lastWifiReconnectMs < WifiRetryMs) return false;
 +  lastWifiReconnectMs = now;
 +
 +  Serial.println("Verbinde mit WLAN...");
 +  WiFi.mode(WIFI_STA);
 +  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
 +
 +  unsigned long startAttemptTime = millis();
 +  while (WiFi.status() != WL_CONNECTED && millis() - startAttemptTime < 10000) {
 +    delay(250);
 +    Serial.print(".");
 +  }
 +  Serial.println();
 +
 +  if (WiFi.status() == WL_CONNECTED) {
 +    Serial.println("WLAN verbunden");
 +    Serial.print("IP: ");
 +    Serial.println(WiFi.localIP());
 +    return true;
 +  }
 +
 +  Serial.println("WLAN Verbindung fehlgeschlagen");
 +  return false;
 +}
 +
 +bool syncTime() {
 +  if (WiFi.status() != WL_CONNECTED) return false;
 +
 +  Serial.println("Synchronisiere Zeit per NTP...");
 +  configTime(GMT_OFFSET_SEC, DAYLIGHT_OFFSET_SEC, NTP_SERVER);
 +
 +  time_t now = time(nullptr);
 +  int retry = 0;
 +
 +  while (now < 100000 && retry < 20) {
 +    delay(500);
 +    Serial.print(".");
 +    now = time(nullptr);
 +    retry++;
 +  }
 +  Serial.println();
 +
 +  if (now < 100000) {
 +    Serial.println("NTP Zeit konnte nicht geholt werden");
 +    timeValid = false;
 +    return false;
 +  }
 +
 +  Serial.print("Aktuelle Unix-Zeit: ");
 +  Serial.println((unsigned long)now);
 +  lastNtpSyncMs = millis();
 +  timeValid = true;
 +  return true;
 +}
 +
 +bool ensureTimeValid() {
 +  time_t now = time(nullptr);
 +  if (now > 100000 && timeValid) {
 +    if (millis() - lastNtpSyncMs < NtpResyncMs) {
 +      return true;
 +    }
 +  }
 +  return syncTime();
 +}
 +
 +bool ensureMqttConnected() {
 +  mqttClient.setServer(MQTT_HOST, MQTT_PORT);
 +
 +  if (mqttClient.connected()) return true;
 +  if (WiFi.status() != WL_CONNECTED) return false;
 +
 +  unsigned long now = millis();
 +  if (now - lastMqttReconnectMs < MqttRetryMs) return false;
 +  lastMqttReconnectMs = now;
 +
 +  String clientId = "esp32-klingel-";
 +  clientId += String((uint32_t)ESP.getEfuseMac(), HEX);
 +
 +  Serial.println("Verbinde MQTT...");
 +  if (mqttClient.connect(clientId.c_str(), MQTT_USER, MQTT_PASS)) {
 +    Serial.println("MQTT verbunden");
 +
 +    mqttClient.subscribe(MQTT_TOPIC_TUER_TEST);
 +    mqttClient.subscribe(MQTT_TOPIC_TOR_TEST);
 +    Serial.println("MQTT Test-Topics subscribed");
 +
 +    return true;
 +  }
 +
 +  Serial.print("MQTT Verbindung fehlgeschlagen, rc=");
 +  Serial.println(mqttClient.state());
 +  return false;
 +}
 +
 +bool publishUnixTimeToTopic(const char* topic) {
 +  if (!connectWiFi()) return false;
 +  if (!ensureTimeValid()) return false;
 +  if (!ensureMqttConnected()) return false;
 +
 +  time_t now = time(nullptr);
 +  uint64_t nowMs = ((uint64_t)now) * 1000ULL + (millis() % 1000ULL);
 +
 +  char payload[32];
 +  snprintf(payload, sizeof(payload), "%llu", (unsigned long long)nowMs);
 +
 +  bool ok = mqttClient.publish(topic, payload, true);
 +
 +  if (ok) {
 +    Serial.print("MQTT gesendet an ");
 +    Serial.print(topic);
 +    Serial.print(" mit Payload: ");
 +    Serial.println(payload);
 +  } else {
 +    Serial.print("MQTT publish fehlgeschlagen für ");
 +    Serial.println(topic);
 +  }
 +
 +  mqttClient.loop();
 +  return ok;
 +}
 +
 +void updateLeds() {
 +  unsigned long now = millis();
 +
 +  if (ledMode != LED_MODE_STANDBY && (long)(now - ledModeUntilMs) >= 0) {
 +    ledMode = LED_MODE_STANDBY;
 +  }
 +
 +  // ===== Langsames sinusförmiges Fade im Standby =====
 +  if (ledMode == LED_MODE_STANDBY) {
 +    if (now - lastLedUpdateMs >= 25) {
 +      lastLedUpdateMs = now;
 +
 +      static float phase = 0.0f;
 +
 +      // kleiner = langsamer
 +      phase += 0.02f;
 +
 +      if (phase > 2.0f * PI) {
 +        phase -= 2.0f * PI;
 +      }
 +
 +      int brightness = (int)((sin(phase) * 0.5f + 0.5f) * 255.0f);
 +
 +      ledcWrite(ledTuerPin, brightness);
 +      ledcWrite(ledTorPin, brightness);
 +    }
 +    return;
 +  }
 +
 +  // ===== Event-Blinken =====
 +  if (now - lastBlinkToggleMs >= 100) {
 +    lastBlinkToggleMs = now;
 +    ledBlinkState = !ledBlinkState;
 +  }
 +
 +  if (ledMode == LED_MODE_SHOW_TUER) {
 +    ledcWrite(ledTuerPin, ledBlinkState ? 255 : 0);
 +    ledcWrite(ledTorPin, 255);
 +    return;
 +  }
 +
 +  if (ledMode == LED_MODE_SHOW_TOR) {
 +    ledcWrite(ledTuerPin, 255);
 +    ledcWrite(ledTorPin, ledBlinkState ? 255 : 0);
 +    return;
 +  }
 +}
 +
 +bool publishStatus() {
 +  if (!connectWiFi()) return false;
 +  if (!ensureMqttConnected()) return false;
 +
 +  String json = buildDebugJson();
 +  bool ok = mqttClient.publish(MQTT_TOPIC_STATUS, json.c_str(), true);
 +
 +  if (ok) {
 +    Serial.println("MQTT Status (JSON) gesendet");
 +  } else {
 +    Serial.println("MQTT Status publish fehlgeschlagen");
 +  }
 +
 +  mqttClient.loop();
 +  return ok;
 +}
 +
 +// =====================================================
 +// Pending Flags
 +// =====================================================
 +bool isMqttPendingTuer() {
 +  preferences.begin("klingel", true);
 +  bool pending = preferences.getBool("mqttTuer", false);
 +  preferences.end();
 +  return pending;
 +}
 +
 +bool isMqttPendingTor() {
 +  preferences.begin("klingel", true);
 +  bool pending = preferences.getBool("mqttTor", false);
 +  preferences.end();
 +  return pending;
 +}
 +
 +void clearMqttPendingTuer() {
 +  preferences.begin("klingel", false);
 +  preferences.putBool("mqttTuer", false);
 +  preferences.end();
 +}
 +
 +void clearMqttPendingTor() {
 +  preferences.begin("klingel", false);
 +  preferences.putBool("mqttTor", false);
 +  preferences.end();
 +}
 +
 +void markMqttPendingTuer() {
 +  preferences.begin("klingel", false);
 +  preferences.putBool("mqttTuer", true);
 +  preferences.end();
 +}
 +
 +void markMqttPendingTor() {
 +  preferences.begin("klingel", false);
 +  preferences.putBool("mqttTor", true);
 +  preferences.end();
 +}
 +
 +void processPendingMqtt() {
 +  if (isMqttPendingTuer()) {
 +    if (publishUnixTimeToTopic(MQTT_TOPIC_TUER)) {
 +      clearMqttPendingTuer();
 +    }
 +  }
 +
 +  if (isMqttPendingTor()) {
 +    if (publishUnixTimeToTopic(MQTT_TOPIC_TOR)) {
 +      clearMqttPendingTor();
 +    }
 +  }
 +}
 +
 +// =====================================================
 +// Web / Debug / OTA
 +// =====================================================
 +String buildDebugJson() {
 +  String json;
 +  json.reserve(1700);
 +
 +  json += "{";
 +  json += "\"wifi\":\"";
 +  json += (WiFi.status() == WL_CONNECTED ? "connected" : "disconnected");
 +  json += "\",";
 +  json += "\"ip\":\"";
 +  json += WiFi.localIP().toString();
 +  json += "\",";
 +  json += "\"rssi\":";
 +  json += WiFi.RSSI();
 +  json += ",";
 +  json += "\"uptime\":";
 +  json += millis() / 1000UL;
 +  json += ",";
 +  json += "\"free_heap\":";
 +  json += ESP.getFreeHeap();
 +  json += ",";
 +  json += "\"state\":\"";
 +  json += lastState;
 +  json += "\",";
 +  json += "\"source\":\"";
 +  json += lastSource;
 +  json += "\",";
 +  json += "\"adcTorValue\":";
 +  json += adcTorValue;
 +  json += ",";
 +  json += "\"adcTorThreshold\":";
 +  json += adcTorThreshold;
 +  json += ",";
 +  json += "\"lastTorState\":";
 +  json += (lastTorState ? "true" : "false");
 +  json += ",";
 +  json += "\"lastDecodedLen\":";
 +  json += lastDecodedLen;
 +  json += ",";
 +  json += "\"lastDecodedHex\":\"";
 +  json += lastDecodedHex;
 +  json += "\",";
 +  json += "\"mqttPendingTuer\":";
 +  json += (isMqttPendingTuer() ? "true" : "false");
 +  json += ",";
 +  json += "\"mqttPendingTor\":";
 +  json += (isMqttPendingTor() ? "true" : "false");
 +  json += ",";
 +  json += "\"bellPulseActive\":";
 +  json += (bellPulseActive ? "true" : "false");
 +  json += ",";
 +  json += "\"hostname\":\"";
 +  json += OTA_HOSTNAME;
 +  json += "\",";
 +  json += "\"mac\":\"";
 +  json += WiFi.macAddress();
 +  json += "\"";
 +  json += "}";
 +
 +  return json;
 +}
 +
 +void setupHttpRoutes() {
 +  httpServer.on("/", HTTP_GET, []() {
 +    String html;
 +    html.reserve(3200);
 +
 +    html += "<!doctype html><html><head><meta charset='utf-8'>";
 +    html += "<meta name='viewport' content='width=device-width,initial-scale=1'>";
 +    html += "<title>HSHB Klingel</title></head><body>";
 +    html += "<h1>HSHB Klingel</h1>";
 +    html += "<p><b>IP:</b> " + WiFi.localIP().toString() + "</p>";
 +    html += "<p><b>RSSI:</b> " + String(WiFi.RSSI()) + "</p>";
 +    html += "<p><b>Uptime:</b> " + String(millis() / 1000UL) + " s</p>";
 +    html += "<p><b>State:</b> " + lastState + "</p>";
 +    html += "<p><b>Source:</b> " + lastSource + "</p>";
 +    html += "<p><b>ADC Tor:</b> " + String(adcTorValue) + "</p>";
 +    html += "<p><b>ADC Threshold:</b> " + String(adcTorThreshold) + "</p>";
 +    html += "<p><b>Last Tor State:</b> " + String(lastTorState ? "true" : "false") + "</p>";
 +    html += "<p><b>Decoded Len:</b> " + String(lastDecodedLen) + "</p>";
 +    html += "<p><b>Decoded Hex:</b> " + lastDecodedHex + "</p>";
 +    html += "<p><b>MQTT pending Tür:</b> " + String(isMqttPendingTuer() ? "true" : "false") + "</p>";
 +    html += "<p><b>MQTT pending Tor:</b> " + String(isMqttPendingTor() ? "true" : "false") + "</p>";
 +    html += "<p><b>Bell Pulse:</b> " + String(bellPulseActive ? "true" : "false") + "</p>";
 +    html += "<p><b>MAC:</b> " + WiFi.macAddress() + "</p>";
 +    html += "<p><a href='/debug'>Debug JSON</a></p>";
 +    html += "<p><a href='/update'>Firmware Update</a></p>";
 +    html += "</body></html>";
 +
 +    httpServer.send(200, "text/html", html);
 +  });
 +
 +  httpServer.on("/debug", HTTP_GET, []() {
 +    httpServer.send(200, "application/json", buildDebugJson());
 +  });
 +
 +  httpUpdater.setup(&httpServer, "/update", OTA_USER, OTA_PASS);
 +  httpServer.begin();
 +}
 +
 +void setupArduinoOta() {
 +  ArduinoOTA.setHostname(OTA_HOSTNAME);
 +  ArduinoOTA.setPassword(OTA_PASS);
 +  ArduinoOTA.begin();
 +}
 +
 +// =====================================================
 +// MQTT Callback
 +// =====================================================
 +void mqttCallback(char* topic, byte* payload, unsigned int length) {
 +  String topicStr = String(topic);
 +  String payloadStr;
 +
 +  for (unsigned int i = 0; i < length; i++) {
 +    payloadStr += (char)payload[i];
 +  }
 +
 +  payloadStr.trim();
 +
 +  Serial.print("MQTT empfangen | topic=");
 +  Serial.print(topicStr);
 +  Serial.print(" | payload=");
 +  Serial.println(payloadStr);
 +
 +  if (payloadStr != "test") return;
 +
 +  if (topicStr == MQTT_TOPIC_TUER_TEST) {
 +    Serial.println("MQTT TEST -> TUER");
 +    triggerBell(true);
 +  } else if (topicStr == MQTT_TOPIC_TOR_TEST) {
 +    Serial.println("MQTT TEST -> TOR");
 +    triggerTorEvent("mqtt-test");
 +  }
 +}
 +
 +// =====================================================
 +// Klingeltrigger
 +// =====================================================
 +void triggerBell(bool fromButton) {
 +  unsigned long now = millis();
 +  if (now - lastBellTriggered < BellDebounceMs) return;
 +
 +  Serial.println("Bell Triggered!");
 +
 +  ledMode = LED_MODE_SHOW_TUER;
 +  ledModeUntilMs = now + LedEventDurationMs;
 +
 +  lastBellTriggered = now;
 +
 +  digitalWrite(bellPin, HIGH);
 +  bellPulseStartMillis = now;
 +  bellPulseActive = true;
 +
 +  lastSource = fromButton ? "button" : "bus";
 +  lastState  = "BELL";
 +
 +  markMqttPendingTuer();
 +  processPendingMqtt();
 +  publishStatus();
 +}
 +
 +void triggerTorEvent(const char* source) {
 +  unsigned long now = millis();
 +
 +  if (now - lastTorTriggerMillis < TorDebounceMs) {
 +    Serial.println("TOR blockiert: 11s Sperre aktiv");
 +    return;
 +  }
 +
 +  lastTorTriggerMillis = now;
 +
 +  ledMode = LED_MODE_SHOW_TOR;
 +  ledModeUntilMs = now + LedEventDurationMs;
 +  lastState = "TOR";
 +  lastSource = source;
 +
 +  Serial.print("TOR TRIGGER! source=");
 +  Serial.println(source);
 +
 +  markMqttPendingTor();
 +  processPendingMqtt();
 +  publishStatus();
 +}
 +
 +// =====================================================
 +// RMT Hilfsfunktionen
 +// =====================================================
 +int pulseType(uint16_t us) {
 +  if (us >= 32  && us <= 52)  return P_ONE;
 +  if (us >= 103 && us <= 123) return P_ZERO;
 +  if (us >= 223 && us <= 243) return P_START;
 +  if (us >= 76  && us <= 96)  return P_STOP;
 +  return P_BAD;
 +}
 +
 +size_t collectLows(const rmt_data_t *symbols, size_t symbolCount, uint16_t *lows) {
 +  size_t n = 0;
 +
 +  for (size_t i = 0; i < symbolCount && n < maxSyms; ++i) {
 +    if (symbols[i].level0 && !symbols[i].level1 &&
 +        symbols[i].duration0 >= 17 && symbols[i].duration0 <= 33 &&
 +        symbols[i].duration1) {
 +      lows[n++] = symbols[i].duration1;
 +    } else if (!symbols[i].level0 && symbols[i].level1 &&
 +               symbols[i].duration1 >= 17 && symbols[i].duration1 <= 33 &&
 +               symbols[i].duration0) {
 +      lows[n++] = symbols[i].duration0;
 +    } else if (!symbols[i].level0 && pulseType(symbols[i].duration0) != P_BAD) {
 +      lows[n++] = symbols[i].duration0;
 +    } else if (!symbols[i].level1 && pulseType(symbols[i].duration1) != P_BAD) {
 +      lows[n++] = symbols[i].duration1;
 +    }
 +  }
 +
 +  return n;
 +}
 +
 +int decodeByte(const uint16_t *pulses) {
 +  if (pulseType(pulses[0]) != P_START) return -1;
 +
 +  uint8_t value = 0;
 +  for (int i = 0; i < 8; ++i) {
 +    int bit = pulseType(pulses[i + 1]);
 +    if (bit < 0 || bit > 1) return -1;
 +    value |= bit << i;
 +  }
 +  return value;
 +}
 +
 +bool decodeMessage(const rmt_data_t *symbols, size_t symbolCount, uint8_t *msg, size_t &len) {
 +  uint16_t lows[maxSyms];
 +  size_t lowCount = collectLows(symbols, symbolCount, lows);
 +
 +  len = 0;
 +  for (size_t i = 0; i + 9 < lowCount && len < bufferSize;) {
 +    if (pulseType(lows[i]) != P_START) {
 +      ++i;
 +      continue;
 +    }
 +
 +    int value = decodeByte(lows + i);
 +    if (value < 0) {
 +      ++i;
 +      continue;
 +    }
 +
 +    msg[len++] = value;
 +    i += 10;
 +
 +    if (i < lowCount && lows[i] >= 76) {
 +      ++i;
 +    }
 +  }
 +
 +  return len > 0;
 +}
 +
 +bool messageContains(const uint8_t *msg, size_t len, const uint8_t *what, size_t whatLen) {
 +  if (whatLen > len) return false;
 +
 +  for (size_t i = 0; i + whatLen <= len; ++i) {
 +    if (!memcmp(msg + i, what, whatLen)) return true;
 +  }
 +  return false;
 +}
 +
 +// =====================================================
 +// Setup
 +// =====================================================
 +void setup() {
 +  Serial.begin(115200);
 +  delay(500);
 +
 +  ledcAttach(ledTuerPin, ledFreq, ledResolution);
 +  ledcAttach(ledTorPin, ledFreq, ledResolution);
 +
 +  ledcWrite(ledTuerPin, 0);
 +  ledcWrite(ledTorPin, 0);
 +
 +  pinMode(bellPin, OUTPUT);
 +  digitalWrite(bellPin, LOW);
 +
 +  pinMode(testBellPin, INPUT_PULLUP);
 +  pinMode(adcTorPin, INPUT);
 +
 +  analogReadResolution(12);
 +  analogSetPinAttenuation(adcTorPin, ADC_11db);
 +
 +  WiFi.mode(WIFI_STA);
 +  esp_wifi_set_mac(WIFI_IF_STA, &newMACAddress[0]);
 +
 +  mqttClient.setServer(MQTT_HOST, MQTT_PORT);
 +  mqttClient.setCallback(mqttCallback);
 +  mqttClient.setBufferSize(2048);
 +  mqttClient.setKeepAlive(30);
 +  mqttClient.setSocketTimeout(2);
 +
 +  connectWiFi();
 +  ensureTimeValid();
 +  ensureMqttConnected();
 +
 +  if (MDNS.begin(OTA_HOSTNAME)) {
 +    MDNS.addService("http", "tcp", 80);
 +  }
 +
 +  setupHttpRoutes();
 +  setupArduinoOta();
 +
 +  rmtInit(busPin, RMT_RX_MODE, RMT_MEM_NUM_BLOCKS_2, 1000000);
 +  rmtSetRxMinThreshold(busPin, 3);
 +  rmtSetRxMaxThreshold(busPin, 1000);
 +  rmtReadAsync(busPin, symbols, &symbolCount);
 +
 +  publishStatus();
 +}
 +
 +// =====================================================
 +// Loop
 +// =====================================================
 +void loop() {
 +  unsigned long now = millis();
 +
 +  updateLeds();
 +  connectWiFi();
 +  ensureMqttConnected();
 +  mqttClient.loop();
 +
 +  httpServer.handleClient();
 +  ArduinoOTA.handle();
 +
 +  adcTorValue = analogRead(adcTorPin);
 +
 +  bool currentTorState = (adcTorValue > adcTorThreshold);
 +
 +  // nur steigende Flanke
 +  if (!lastTorState && currentTorState) {
 +    Serial.print("TOR Flanke erkannt, ADC=");
 +    Serial.println(adcTorValue);
 +    triggerTorEvent("adc");
 +  }
 +
 +  lastTorState = currentTorState;
 +
 +  bool currentButtonState = digitalRead(testBellPin);
 +  if (lastButtonState == HIGH && currentButtonState == LOW &&
 +      (now - lastButtonTriggerMillis > ButtonDebounceMs)) {
 +    lastButtonTriggerMillis = now;
 +    triggerBell(true);
 +  }
 +  lastButtonState = currentButtonState;
 +
 +  if (rmtReceiveCompleted(busPin)) {
 +    uint8_t msg[bufferSize];
 +    size_t len = 0;
 +
 +    decodeMessage(symbols, symbolCount, msg, len);
 +
 +    lastDecodedLen = len;
 +    lastDecodedHex = "";
 +    for (size_t i = 0; i < len; ++i) {
 +      char tmp[4];
 +      snprintf(tmp, sizeof(tmp), "%02X", msg[i]);
 +      if (i) lastDecodedHex += " ";
 +      lastDecodedHex += tmp;
 +    }
 +    if (lastDecodedHex.length() == 0) lastDecodedHex = "-";
 +
 +    symbolCount = maxSyms;
 +    rmtReadAsync(busPin, symbols, &symbolCount);
 +
 +    if (messageContains(msg, len, hackerspaceBell, sizeof(hackerspaceBell)) ||
 +        messageContains(msg, len, hackerspaceBell2, sizeof(hackerspaceBell2))) {
 +      triggerBell(false);
 +    }
 +  }
 +
 +  if (bellPulseActive && (now - bellPulseStartMillis > BellPulseMs)) {
 +    digitalWrite(bellPin, LOW);
 +    bellPulseActive = false;
 +    if (lastState == "BELL") {
 +      lastState = "IDLE";
 +    }
 +  }
 +
 +  if (now - lastStatusMillis > StatusMs) {
 +    lastStatusMillis = now;
 +    publishStatus();
 +  }
 +
 +  if (now - lastDebugMillis > Debuginterval) {
 +    lastDebugMillis = now;
 +    Serial.print("state=");
 +    Serial.print(lastState);
 +    Serial.print(" source=");
 +    Serial.print(lastSource);
 +    Serial.print(" adc=");
 +    Serial.print(adcTorValue);
 +    Serial.print(" torState=");
 +    Serial.println(currentTorState ? "1" : "0");
 +  }
 +
 +  delay(1);
 +}
 +</code>
  
-===== Empfänger mit Netzteil versorgen ===== 
  
-Die Empfänger werden mit einem standard USB -> Micro Netzweil versorgt am ESP32 versorgt. 
  
  
sonstiges/tuerklingel.1696015187.txt.gz · Zuletzt geändert: von schuballaa