sonstiges:tuerklingel
Unterschiede
Hier werden die Unterschiede zwischen zwei Versionen angezeigt.
| Beide Seiten der vorigen RevisionVorhergehende ÜberarbeitungNächste Überarbeitung | Vorhergehende Überarbeitung | ||
| sonstiges:tuerklingel [2023-09-29 21:21] – [Übersicht] schuballaa | sonstiges:tuerklingel [2026-05-07 02:21] (aktuell) – schuballaa | ||
|---|---|---|---|
| Zeile 1: | Zeile 1: | ||
| - | ====== | + | ====== |
| - | ===== Übersicht ===== | + | < |
| - | 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). | + | {{: |
| - | **Optional:** | + | [[https:// |
| - | 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. | + | {{: |
| - | **Achtung bei Powerbanks!** | + | Der Klingeldetektor erkennt, ob an der Tür oder am Tor geklingelt wurde. |
| + | ===== Erkennungswege ===== | ||
| - | Da sich die meisten Powerbanks bei geringer Last ausschalten, | + | * **Tür:** Auswertung des Datenbusses des hauseigenen Klingelsystems |
| + | * **Tor:** Erkennung über **LDR (Lichtsensor)** am Empfänger der Torklingel | ||
| + | ===== Bei einem Klingelereignis ===== | ||
| - | ===== Sprechanlage ===== | + | * 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:// | ||
| + | * Meldung per **MQTT** im Hackerspace-System (für zukünftige Erweiterungen) | ||
| - | Die Sprechanlage nutzt ein 2-Draht-Bussystem vom Typ [[http:// | + | ===== Verhalten im Standby ===== |
| - | Das Haustelefon ist vom Typ STR HT 3033. | + | * Warten auf ein Klingelereignis |
| + | * LEDs werden ein- und ausgeblendet, | ||
| - | Das Steuergerät ist ein SP 333 ({{: | ||
| - | ===== Pinout ===== | ||
| - | Öffnet man das Haustelefon über die einzig vorhandene Gehäuseschraube (die obere Gehäusehälfte lässt sich dann herunterklappen), | + | ===== Umgesetzte Extras ===== |
| - | Die Belegung lässt sich durch ein Foto einer Relaisbox {{: | + | * MQTT-Integration |
| + | * Akustische und visuelle Anzeige im Hauptraum | ||
| + | * Funkverteilung in die anderen Räume | ||
| + | * LED-Visualisierung | ||
| - | {{:sonstiges: | + | ===== Danksagungen: ===== |
| - | {{: | + | |
| - | 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. | + | * **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 ===== | ||
| - | ===== Funk-Türklingel ===== | + | {{: |
| + | ==== Tür – QwikBus-Auswertung | ||
| - | Da uns keine Leitung zwischen den Räumen Bornstr. 14-15 sowie 16-17 zur Verfügung steht, mussten wir auf eine [[http:// | + | Das hauseigene Klingelsystem (QwikBus) überträgt Klingelereignisse als zeitkodierte elektrische Signale. |
| - | Wenn geklingelt wird, zieht ein n channel mosfet den Pin 4 des [[https://www.amazon.de/ | + | **Signalcharakteristik:** |
| + | * Ruhespannung: | ||
| + | * Aktivsignal: | ||
| + | * Information in **zeitlichen Pulsfolgen** | ||
| + | **Hardware: | ||
| + | * 1 nF um nur die Busänderungen zu dedektieren | ||
| + | * Spannungsteiler | ||
| + | * Schmitt-Trigger | ||
| + | * Mikrocontroller (ESP32) | ||
| - | ===== Sender in Haustelefon einbauen ===== | + | **Verarbeitung: |
| + | * Flankenerkennung (Interrupt) | ||
| + | * Zeitmessung im µs-Bereich | ||
| + | * Bildung von Sequenzen: | ||
| + | * Quantisierung zur Reduktion von Rauschen | ||
| + | * Mustervergleich (Pattern Matching) zur Klassifikation | ||
| - | 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) | + | **Ergebnis:** |
| + | * Zuordnung des Signals zu einem **Türklingel-Ereignis** | ||
| - | {{https:// | + | ==== 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/ | ||
| + | * 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: | ||
| + | * 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** | ||
| + | {{: | ||
| + | {{: | ||
| + | {{: | ||
| + | {{: | ||
| + | |||
| + | ==== Code ==== | ||
| + | |||
| + | < | ||
| + | |||
| + | #include < | ||
| + | #include < | ||
| + | #include < | ||
| + | #include < | ||
| + | #include < | ||
| + | #include < | ||
| + | #include < | ||
| + | #include < | ||
| + | #include < | ||
| + | #include < | ||
| + | #include < | ||
| + | #include " | ||
| + | #include < | ||
| + | |||
| + | // ===================================================== | ||
| + | // WLAN / MQTT | ||
| + | // ===================================================== | ||
| + | const char* WIFI_SSID | ||
| + | const char* WIFI_PASSWORD = " | ||
| + | |||
| + | const char* MQTT_HOST = " | ||
| + | const int | ||
| + | const char* MQTT_USER = " | ||
| + | const char* MQTT_PASS = " | ||
| + | |||
| + | const char* MQTT_TOPIC_TUER | ||
| + | const char* MQTT_TOPIC_TOR | ||
| + | const char* MQTT_TOPIC_STATUS | ||
| + | const char* MQTT_TOPIC_TUER_TEST = "/ | ||
| + | const char* MQTT_TOPIC_TOR_TEST | ||
| + | |||
| + | // ===================================================== | ||
| + | // OTA / Web | ||
| + | // ===================================================== | ||
| + | const char* OTA_HOSTNAME = " | ||
| + | const char* OTA_USER | ||
| + | const char* OTA_PASS | ||
| + | |||
| + | // ===================================================== | ||
| + | // NTP | ||
| + | // ===================================================== | ||
| + | const char* NTP_SERVER = " | ||
| + | const long GMT_OFFSET_SEC = 3600; | ||
| + | const int | ||
| + | |||
| + | // ===================================================== | ||
| + | // MAC | ||
| + | // ===================================================== | ||
| + | uint8_t newMACAddress[] = {0x30, 0xAF, 0xA0, 0x05, 0x1D, 0x63}; | ||
| + | |||
| + | // ===================================================== | ||
| + | // Pins | ||
| + | // ===================================================== | ||
| + | constexpr int bellPin | ||
| + | constexpr int busPin | ||
| + | const int adcTorPin | ||
| + | const int testBellPin | ||
| + | |||
| + | // ===================================================== | ||
| + | // RMT / Nachrichten | ||
| + | // ===================================================== | ||
| + | constexpr uint8_t hackerspaceBell[] | ||
| + | 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 | ||
| + | unsigned long lastTorTriggerMillis | ||
| + | unsigned long lastButtonTriggerMillis = 0; | ||
| + | unsigned long lastDebugMillis | ||
| + | unsigned long lastStatusMillis | ||
| + | unsigned long bellPulseStartMillis | ||
| + | unsigned long lastWifiReconnectMs | ||
| + | unsigned long lastMqttReconnectMs | ||
| + | unsigned long lastNtpSyncMs | ||
| + | |||
| + | const unsigned long BellDebounceMs | ||
| + | const unsigned long TorDebounceMs | ||
| + | const unsigned long ButtonDebounceMs | ||
| + | const unsigned long Debuginterval | ||
| + | const unsigned long StatusMs | ||
| + | const unsigned long BellPulseMs | ||
| + | const unsigned long WifiRetryMs | ||
| + | const unsigned long MqttRetryMs | ||
| + | const unsigned long NtpResyncMs | ||
| + | const unsigned long LedEventDurationMs = 60000; | ||
| + | |||
| + | const int adcTorThreshold = 1500; | ||
| + | |||
| + | bool bellPulseActive = false; | ||
| + | bool timeValid = false; | ||
| + | bool lastButtonState = HIGH; | ||
| + | bool lastTorState = false; | ||
| + | |||
| + | String lastState = " | ||
| + | String lastSource = " | ||
| + | String lastDecodedHex = " | ||
| + | uint32_t lastDecodedLen = 0; | ||
| + | int adcTorValue = 0; | ||
| + | |||
| + | // ========================= | ||
| + | // LEDs | ||
| + | // ========================= | ||
| + | const int ledTuerPin = 23; | ||
| + | const int ledTorPin | ||
| + | |||
| + | 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, | ||
| + | int decodeByte(const uint16_t *pulses); | ||
| + | bool decodeMessage(const rmt_data_t *symbols, size_t symbolCount, | ||
| + | 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(" | ||
| + | WiFi.mode(WIFI_STA); | ||
| + | WiFi.begin(WIFI_SSID, | ||
| + | |||
| + | 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(" | ||
| + | Serial.print(" | ||
| + | Serial.println(WiFi.localIP()); | ||
| + | return true; | ||
| + | } | ||
| + | |||
| + | Serial.println(" | ||
| + | return false; | ||
| + | } | ||
| + | |||
| + | bool syncTime() { | ||
| + | if (WiFi.status() != WL_CONNECTED) return false; | ||
| + | |||
| + | Serial.println(" | ||
| + | configTime(GMT_OFFSET_SEC, | ||
| + | |||
| + | 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(" | ||
| + | timeValid = false; | ||
| + | return false; | ||
| + | } | ||
| + | |||
| + | Serial.print(" | ||
| + | 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, | ||
| + | |||
| + | 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 = " | ||
| + | clientId += String((uint32_t)ESP.getEfuseMac(), | ||
| + | |||
| + | Serial.println(" | ||
| + | if (mqttClient.connect(clientId.c_str(), | ||
| + | Serial.println(" | ||
| + | |||
| + | mqttClient.subscribe(MQTT_TOPIC_TUER_TEST); | ||
| + | mqttClient.subscribe(MQTT_TOPIC_TOR_TEST); | ||
| + | Serial.println(" | ||
| + | |||
| + | return true; | ||
| + | } | ||
| + | |||
| + | Serial.print(" | ||
| + | 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, | ||
| + | |||
| + | bool ok = mqttClient.publish(topic, | ||
| + | |||
| + | if (ok) { | ||
| + | Serial.print(" | ||
| + | Serial.print(topic); | ||
| + | Serial.print(" | ||
| + | Serial.println(payload); | ||
| + | } else { | ||
| + | Serial.print(" | ||
| + | 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, | ||
| + | ledcWrite(ledTorPin, | ||
| + | } | ||
| + | return; | ||
| + | } | ||
| + | |||
| + | // ===== Event-Blinken ===== | ||
| + | if (now - lastBlinkToggleMs >= 100) { | ||
| + | lastBlinkToggleMs = now; | ||
| + | ledBlinkState = !ledBlinkState; | ||
| + | } | ||
| + | |||
| + | if (ledMode == LED_MODE_SHOW_TUER) { | ||
| + | ledcWrite(ledTuerPin, | ||
| + | ledcWrite(ledTorPin, | ||
| + | return; | ||
| + | } | ||
| + | |||
| + | if (ledMode == LED_MODE_SHOW_TOR) { | ||
| + | ledcWrite(ledTuerPin, | ||
| + | ledcWrite(ledTorPin, | ||
| + | return; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | bool publishStatus() { | ||
| + | if (!connectWiFi()) return false; | ||
| + | if (!ensureMqttConnected()) return false; | ||
| + | |||
| + | String json = buildDebugJson(); | ||
| + | bool ok = mqttClient.publish(MQTT_TOPIC_STATUS, | ||
| + | |||
| + | if (ok) { | ||
| + | Serial.println(" | ||
| + | } else { | ||
| + | Serial.println(" | ||
| + | } | ||
| + | |||
| + | mqttClient.loop(); | ||
| + | return ok; | ||
| + | } | ||
| + | |||
| + | // ===================================================== | ||
| + | // Pending Flags | ||
| + | // ===================================================== | ||
| + | bool isMqttPendingTuer() { | ||
| + | preferences.begin(" | ||
| + | bool pending = preferences.getBool(" | ||
| + | preferences.end(); | ||
| + | return pending; | ||
| + | } | ||
| + | |||
| + | bool isMqttPendingTor() { | ||
| + | preferences.begin(" | ||
| + | bool pending = preferences.getBool(" | ||
| + | preferences.end(); | ||
| + | return pending; | ||
| + | } | ||
| + | |||
| + | void clearMqttPendingTuer() { | ||
| + | preferences.begin(" | ||
| + | preferences.putBool(" | ||
| + | preferences.end(); | ||
| + | } | ||
| + | |||
| + | void clearMqttPendingTor() { | ||
| + | preferences.begin(" | ||
| + | preferences.putBool(" | ||
| + | preferences.end(); | ||
| + | } | ||
| + | |||
| + | void markMqttPendingTuer() { | ||
| + | preferences.begin(" | ||
| + | preferences.putBool(" | ||
| + | preferences.end(); | ||
| + | } | ||
| + | |||
| + | void markMqttPendingTor() { | ||
| + | preferences.begin(" | ||
| + | preferences.putBool(" | ||
| + | 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 += " | ||
| + | json += (WiFi.status() == WL_CONNECTED ? " | ||
| + | json += " | ||
| + | json += " | ||
| + | json += WiFi.localIP().toString(); | ||
| + | json += " | ||
| + | json += " | ||
| + | json += WiFi.RSSI(); | ||
| + | json += ","; | ||
| + | json += " | ||
| + | json += millis() / 1000UL; | ||
| + | json += ","; | ||
| + | json += " | ||
| + | json += ESP.getFreeHeap(); | ||
| + | json += ","; | ||
| + | json += " | ||
| + | json += lastState; | ||
| + | json += " | ||
| + | json += " | ||
| + | json += lastSource; | ||
| + | json += " | ||
| + | json += " | ||
| + | json += adcTorValue; | ||
| + | json += ","; | ||
| + | json += " | ||
| + | json += adcTorThreshold; | ||
| + | json += ","; | ||
| + | json += " | ||
| + | json += (lastTorState ? " | ||
| + | json += ","; | ||
| + | json += " | ||
| + | json += lastDecodedLen; | ||
| + | json += ","; | ||
| + | json += " | ||
| + | json += lastDecodedHex; | ||
| + | json += " | ||
| + | json += " | ||
| + | json += (isMqttPendingTuer() ? " | ||
| + | json += ","; | ||
| + | json += " | ||
| + | json += (isMqttPendingTor() ? " | ||
| + | json += ","; | ||
| + | json += " | ||
| + | json += (bellPulseActive ? " | ||
| + | json += ","; | ||
| + | json += " | ||
| + | json += OTA_HOSTNAME; | ||
| + | json += " | ||
| + | json += " | ||
| + | json += WiFi.macAddress(); | ||
| + | json += " | ||
| + | json += " | ||
| + | |||
| + | return json; | ||
| + | } | ||
| + | |||
| + | void setupHttpRoutes() { | ||
| + | httpServer.on("/", | ||
| + | String html; | ||
| + | html.reserve(3200); | ||
| + | |||
| + | html += "< | ||
| + | html += "< | ||
| + | html += "< | ||
| + | html += "< | ||
| + | html += "< | ||
| + | html += "< | ||
| + | html += "< | ||
| + | html += "< | ||
| + | html += "< | ||
| + | html += "< | ||
| + | html += "< | ||
| + | html += "< | ||
| + | html += "< | ||
| + | html += "< | ||
| + | html += "< | ||
| + | html += "< | ||
| + | html += "< | ||
| + | html += "< | ||
| + | html += "< | ||
| + | html += "< | ||
| + | html += "</ | ||
| + | |||
| + | httpServer.send(200, | ||
| + | }); | ||
| + | |||
| + | httpServer.on("/ | ||
| + | httpServer.send(200, | ||
| + | }); | ||
| + | |||
| + | httpUpdater.setup(& | ||
| + | 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(" | ||
| + | Serial.print(topicStr); | ||
| + | Serial.print(" | ||
| + | Serial.println(payloadStr); | ||
| + | |||
| + | if (payloadStr != " | ||
| + | |||
| + | if (topicStr == MQTT_TOPIC_TUER_TEST) { | ||
| + | Serial.println(" | ||
| + | triggerBell(true); | ||
| + | } else if (topicStr == MQTT_TOPIC_TOR_TEST) { | ||
| + | Serial.println(" | ||
| + | triggerTorEvent(" | ||
| + | } | ||
| + | } | ||
| + | |||
| + | // ===================================================== | ||
| + | // Klingeltrigger | ||
| + | // ===================================================== | ||
| + | void triggerBell(bool fromButton) { | ||
| + | unsigned long now = millis(); | ||
| + | if (now - lastBellTriggered < BellDebounceMs) return; | ||
| + | |||
| + | Serial.println(" | ||
| + | |||
| + | ledMode = LED_MODE_SHOW_TUER; | ||
| + | ledModeUntilMs = now + LedEventDurationMs; | ||
| + | |||
| + | lastBellTriggered = now; | ||
| + | |||
| + | digitalWrite(bellPin, | ||
| + | bellPulseStartMillis = now; | ||
| + | bellPulseActive = true; | ||
| + | |||
| + | lastSource = fromButton ? " | ||
| + | lastState | ||
| + | |||
| + | markMqttPendingTuer(); | ||
| + | processPendingMqtt(); | ||
| + | publishStatus(); | ||
| + | } | ||
| + | |||
| + | void triggerTorEvent(const char* source) { | ||
| + | unsigned long now = millis(); | ||
| + | |||
| + | if (now - lastTorTriggerMillis < TorDebounceMs) { | ||
| + | Serial.println(" | ||
| + | return; | ||
| + | } | ||
| + | |||
| + | lastTorTriggerMillis = now; | ||
| + | |||
| + | ledMode = LED_MODE_SHOW_TOR; | ||
| + | ledModeUntilMs = now + LedEventDurationMs; | ||
| + | lastState = " | ||
| + | lastSource = source; | ||
| + | |||
| + | Serial.print(" | ||
| + | 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, | ||
| + | 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 && | ||
| + | | ||
| + | | ||
| + | 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, | ||
| + | uint16_t lows[maxSyms]; | ||
| + | size_t lowCount = collectLows(symbols, | ||
| + | |||
| + | 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, | ||
| + | ledcAttach(ledTorPin, | ||
| + | |||
| + | ledcWrite(ledTuerPin, | ||
| + | ledcWrite(ledTorPin, | ||
| + | |||
| + | pinMode(bellPin, | ||
| + | digitalWrite(bellPin, | ||
| + | |||
| + | pinMode(testBellPin, | ||
| + | pinMode(adcTorPin, | ||
| + | |||
| + | analogReadResolution(12); | ||
| + | analogSetPinAttenuation(adcTorPin, | ||
| + | |||
| + | WiFi.mode(WIFI_STA); | ||
| + | esp_wifi_set_mac(WIFI_IF_STA, | ||
| + | |||
| + | mqttClient.setServer(MQTT_HOST, | ||
| + | mqttClient.setCallback(mqttCallback); | ||
| + | mqttClient.setBufferSize(2048); | ||
| + | mqttClient.setKeepAlive(30); | ||
| + | mqttClient.setSocketTimeout(2); | ||
| + | |||
| + | connectWiFi(); | ||
| + | ensureTimeValid(); | ||
| + | ensureMqttConnected(); | ||
| + | |||
| + | if (MDNS.begin(OTA_HOSTNAME)) { | ||
| + | MDNS.addService(" | ||
| + | } | ||
| + | |||
| + | setupHttpRoutes(); | ||
| + | setupArduinoOta(); | ||
| + | |||
| + | rmtInit(busPin, | ||
| + | rmtSetRxMinThreshold(busPin, | ||
| + | rmtSetRxMaxThreshold(busPin, | ||
| + | rmtReadAsync(busPin, | ||
| + | |||
| + | 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(" | ||
| + | Serial.println(adcTorValue); | ||
| + | triggerTorEvent(" | ||
| + | } | ||
| + | |||
| + | 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, | ||
| + | |||
| + | lastDecodedLen = len; | ||
| + | lastDecodedHex = ""; | ||
| + | for (size_t i = 0; i < len; ++i) { | ||
| + | char tmp[4]; | ||
| + | snprintf(tmp, | ||
| + | if (i) lastDecodedHex += " "; | ||
| + | lastDecodedHex += tmp; | ||
| + | } | ||
| + | if (lastDecodedHex.length() == 0) lastDecodedHex = " | ||
| + | |||
| + | symbolCount = maxSyms; | ||
| + | rmtReadAsync(busPin, | ||
| + | |||
| + | if (messageContains(msg, | ||
| + | messageContains(msg, | ||
| + | triggerBell(false); | ||
| + | } | ||
| + | } | ||
| + | |||
| + | if (bellPulseActive && (now - bellPulseStartMillis > BellPulseMs)) { | ||
| + | digitalWrite(bellPin, | ||
| + | bellPulseActive = false; | ||
| + | if (lastState == " | ||
| + | lastState = " | ||
| + | } | ||
| + | } | ||
| + | |||
| + | if (now - lastStatusMillis > StatusMs) { | ||
| + | lastStatusMillis = now; | ||
| + | publishStatus(); | ||
| + | } | ||
| + | |||
| + | if (now - lastDebugMillis > Debuginterval) { | ||
| + | lastDebugMillis = now; | ||
| + | Serial.print(" | ||
| + | Serial.print(lastState); | ||
| + | Serial.print(" | ||
| + | Serial.print(lastSource); | ||
| + | Serial.print(" | ||
| + | Serial.print(adcTorValue); | ||
| + | Serial.print(" | ||
| + | Serial.println(currentTorState ? " | ||
| + | } | ||
| + | |||
| + | delay(1); | ||
| + | } | ||
| + | </ | ||
| - | ===== Empfänger mit Netzteil versorgen ===== | ||
| - | Die Empfänger werden mit einem standard USB -> Micro Netzweil versorgt am ESP32 versorgt. | ||
sonstiges/tuerklingel.1696015272.txt.gz · Zuletzt geändert: von schuballaa
