From 3e65b914fde9000eff244976c183c1ead332a97d Mon Sep 17 00:00:00 2001 From: dulfer <30404904+dulfer@users.noreply.github.com> Date: Sun, 12 Jun 2022 17:16:08 +0200 Subject: [PATCH] Radar/pir no-motion state debounce period (#486) Adding pir/radar timeout logic Timeouts configurable via MQTT Co-authored-by: Darrell Turner --- lib/BleFingerprint/GUI.cpp | 42 ++++++++++------ lib/BleFingerprint/GUI.h | 4 ++ src/MotionSensors.cpp | 99 ++++++++++++++++++++++++-------------- src/MotionSensors.h | 32 ++++++------ src/defaults.h | 6 +++ src/main.cpp | 20 +++----- src/main.h | 10 +--- 7 files changed, 125 insertions(+), 88 deletions(-) diff --git a/lib/BleFingerprint/GUI.cpp b/lib/BleFingerprint/GUI.cpp index af0d36f..d9d6370 100644 --- a/lib/BleFingerprint/GUI.cpp +++ b/lib/BleFingerprint/GUI.cpp @@ -1,4 +1,5 @@ #include "GUI.h" + #ifdef M5STICK #include "tb_display.h" #endif @@ -79,7 +80,7 @@ void GUI::connected(bool wifi = false, bool mqtt = false) #ifdef LED_BUILTIN digitalWrite(LED_BUILTIN, !LED_BUILTIN_ON); #endif - status("Wifi: %s Mqtt: %s", (wifi ? "yes" : "no"), (mqtt ? "yes" : "no")); + status("Wifi:%s Mqtt:%s", (wifi ? "yes" : "no"), (mqtt ? "yes" : "no")); #endif } @@ -89,7 +90,7 @@ void GUI::added(BleFingerprint *f) Serial.printf("%u New %s | MAC: %s, ID: %-60s %s\n", xPortGetCoreID(), f->getRmAsst() ? "R" : (f->getAllowQuery() ? "Q" : " "), f->getMac().c_str(), f->getId().c_str(), f->getDiscriminator().c_str()); } - void GUI::removed(BleFingerprint *f) +void GUI::removed(BleFingerprint *f) { if (f->getIgnore() || !f->getAdded()) return; Serial.printf("\u001b[38;5;236m%u Del | MAC: %s, ID: %-60s %s\u001b[0m\n", xPortGetCoreID(), f->getMac().c_str(), f->getId().c_str(), f->getDiscriminator().c_str()); @@ -105,21 +106,33 @@ void GUI::minusOne(BleFingerprint *f) Serial.printf("\u001b[35m%u C# -1 | MAC: %s, ID: %-60s (%.2fm) %lums\u001b[0m\n", xPortGetCoreID(), f->getMac().c_str(), f->getId().c_str(), f->getDistance(), f->getMsSinceLastSeen()); } - void GUI::close(BleFingerprint *f) +void GUI::close(BleFingerprint *f) { if (f->getIgnore()) return; Serial.printf("\u001b[32m%u Close | MAC: %s, ID: %-60s (%.2fm) %ddBm\u001b[0m\n", xPortGetCoreID(), f->getMac().c_str(), f->getId().c_str(), f->getDistance(), f->getNewestRssi()); - status("C: %s", f->getId().c_str()); + status("C:%s", f->getId().c_str()); } - void GUI::left(BleFingerprint *f) +void GUI::left(BleFingerprint *f) { if (f->getIgnore()) return; Serial.printf("\u001b[33m%u Left | MAC: %s, ID: %-60s (%.2fm) %ddBm\u001b[0m\n", xPortGetCoreID(), f->getMac().c_str(), f->getId().c_str(), f->getDistance(), f->getNewestRssi()); - status("L: %s", f->getId().c_str()); + status("L:%s", f->getId().c_str()); } - void GUI::status(const char *format, ...) +void GUI::radar(bool value) +{ + Serial.printf("%u Radar | %s\n", xPortGetCoreID(), value ? "detected" : "cleared"); + status("Radar:%s", value ? "detected" : "cleared"); +} + +void GUI::pir(bool value) +{ + Serial.printf("%u Pir | %s\n", xPortGetCoreID(), value ? "detected" : "cleared"); + status("Pir:%s", value ? "detected" : "cleared"); +} + +void GUI::status(const char *format, ...) { begin(); #ifdef M5STICK @@ -141,14 +154,14 @@ void GUI::minusOne(BleFingerprint *f) #endif } - void GUI::setup() +void GUI::setup() { #ifdef LED_BUILTIN pinMode(LED_BUILTIN, OUTPUT); #endif } - void GUI::begin() +void GUI::begin() { if (!GUI::init) { @@ -163,30 +176,29 @@ void GUI::minusOne(BleFingerprint *f) } } - - void GUI::updateStart() +void GUI::updateStart() { #ifdef LED_BUILTIN if (GUI::statusLed) digitalWrite(LED_BUILTIN, LED_BUILTIN_ON); #endif } - void GUI::updateProgress(unsigned int percent) +void GUI::updateProgress(unsigned int percent) { #ifdef LED_BUILTIN if (GUI::statusLed) digitalWrite(LED_BUILTIN, percent % 2); #endif } - void GUI::updateEnd() +void GUI::updateEnd() { #ifdef LED_BUILTIN digitalWrite(LED_BUILTIN, !LED_BUILTIN_ON); #endif } -bool GUI::init=false; -bool GUI::statusLed=false; +bool GUI::init = false; +bool GUI::statusLed = false; #ifdef M5STICK bool GUI::dirty = false; diff --git a/lib/BleFingerprint/GUI.h b/lib/BleFingerprint/GUI.h index 82f5d0b..39701dc 100644 --- a/lib/BleFingerprint/GUI.h +++ b/lib/BleFingerprint/GUI.h @@ -26,6 +26,9 @@ public: static void close(BleFingerprint *f); static void left(BleFingerprint *f); + static void pir(bool value); + static void radar(bool value); + static void erasing(); static void erased(); @@ -55,4 +58,5 @@ private: static TFT_eSprite sprite; #endif }; + #endif diff --git a/src/MotionSensors.cpp b/src/MotionSensors.cpp index f899a54..cb3b29a 100644 --- a/src/MotionSensors.cpp +++ b/src/MotionSensors.cpp @@ -4,31 +4,46 @@ // for #define ESPMAC #include "string_utils.h" +#include "defaults.h" +#include "GUI.h" // TODO: Not a fan of externs, but this helps refactoring for now extern char buffer[2048]; extern String room; extern String roomsTopic; extern void commonDiscovery(); +extern bool sendNumberDiscovery(const String& name, const String& entityCategory); extern bool pub(const char *topic, uint8_t qos, bool retain, const char *payload, size_t length = 0, bool dup = false, uint16_t message_id = 0); +extern bool spurt(const String& fn, const String& content); namespace Motion { + int lastMotionValue = -1; + int pirPin = 0; + float pirTimeout = 0; int lastPirValue = -1; + unsigned long lastPirMilli = 0; + int radarPin = 0; + float radarTimeout = 0; int lastRadarValue = -1; + unsigned long lastRadarMilli = 0; void Setup() { - if (pirPin) pinMode(pirPin, INPUT_PULLUP); - if (radarPin) pinMode(radarPin, INPUT_PULLUP); + if (pirPin) + pinMode(pirPin, INPUT_PULLUP); + if (radarPin) + pinMode(radarPin, INPUT_PULLUP); } void ConnectToWifi() { pirPin = WiFiSettings.integer("pir_pin", 0, "PIR motion pin (0 for disable)"); + pirTimeout = WiFiSettings.floating("pir_timeout", 0, 300, DEFAULT_DEBOUNCE_TIMEOUT, "PIR motion timeout (in seconds)"); radarPin = WiFiSettings.integer("radar_pin", 0, "Radar motion pin (0 for disable)"); + radarTimeout = WiFiSettings.floating("radar_timeout", 0, 300, DEFAULT_DEBOUNCE_TIMEOUT, "Radar motion timeout (in seconds)"); } void SerialReport() @@ -42,56 +57,45 @@ namespace Motion static void PirLoop(AsyncMqttClient& mqttClient) { if (!pirPin) return; - int pirValue = digitalRead(pirPin); + bool detected = digitalRead(pirPin) == HIGH; + if (detected) lastPirMilli = millis(); + unsigned long since = millis() - lastPirMilli; + int pirValue = (detected || since < (pirTimeout * 1000)) ? HIGH : LOW; - if (pirValue != lastPirValue) - { - if (pirValue == HIGH) - { - mqttClient.publish((roomsTopic + "/motion").c_str(), 0, true, "ON"); - Serial.println("PIR MOTION DETECTED!!!"); - } - else - { - mqttClient.publish((roomsTopic + "/motion").c_str(), 0, true, "OFF"); - Serial.println("NO PIR MOTION DETECTED!!!"); - } - - lastPirValue = pirValue; - } + if (lastPirValue == pirValue) return; + mqttClient.publish((roomsTopic + "/pir").c_str(), 0, true, pirValue == HIGH ? "ON" : "OFF"); + GUI::pir(pirValue == HIGH); + lastPirValue = pirValue; } static void RadarLoop(AsyncMqttClient& mqttClient) { if (!radarPin) return; - int radarValue = digitalRead(radarPin); + bool detected = digitalRead(radarPin) == HIGH; + if (detected) lastRadarMilli = millis(); + unsigned long since = millis() - lastRadarMilli; + int radarValue = (detected || since < (radarTimeout * 1000)) ? HIGH : LOW; - if (radarValue != lastRadarValue) - { - if (radarValue == HIGH) - { - mqttClient.publish((roomsTopic + "/motion").c_str(), 0, true, "ON"); - Serial.println("Radar MOTION DETECTED!!!"); - } - else - { - mqttClient.publish((roomsTopic + "/motion").c_str(), 0, true, "OFF"); - Serial.println("NO Radar MOTION DETECTED!!!"); - } - - lastRadarValue = radarValue; - } + if (lastRadarValue == radarValue) return; + mqttClient.publish((roomsTopic + "/radar").c_str(), 0, true, radarValue == HIGH ? "ON" : "OFF"); + GUI::radar(radarValue == HIGH); + lastRadarValue = radarValue; } void Loop(AsyncMqttClient& mqttClient) { PirLoop(mqttClient); RadarLoop(mqttClient); + int motionValue = (lastRadarValue == HIGH || lastPirValue == HIGH) ? HIGH : LOW; + if (lastMotionValue == motionValue) return; + mqttClient.publish((roomsTopic + "/motion").c_str(), 0, true, motionValue == HIGH ? "ON" : "OFF"); + lastMotionValue = motionValue; } bool SendDiscovery(DynamicJsonDocument& doc) { - if (!pirPin && !radarPin) return true; + if (!pirPin && !radarPin) + return true; commonDiscovery(); doc["~"] = roomsTopic; @@ -104,6 +108,29 @@ namespace Motion serializeJson(doc, buffer); String discoveryTopic = "homeassistant/binary_sensor/espresense_" + ESPMAC + "/motion/config"; - return pub(discoveryTopic.c_str(), 0, true, buffer); + if (!pub(discoveryTopic.c_str(), 0, true, buffer)) return false; + return (!pirPin || sendNumberDiscovery("Pir Timeout", EC_CONFIG)) && (!radarPin || sendNumberDiscovery("Radar Timeout", EC_CONFIG)); + } + + bool Command(String& command, String& pay) + { + if (command == "pir_timeout") + { + pirTimeout = pay.toInt(); + spurt("/pir_timeout", pay); + } + else if (command == "radar_timeout") + { + radarTimeout = pay.toInt(); + spurt("/radar_timeout", pay); + } + else return false; + return true; + } + + bool SendOnline(DynamicJsonDocument& doc) + { + return pub((roomsTopic + "/pir_timeout").c_str(), 0, true, String(pirTimeout).c_str()) + && pub((roomsTopic + "/radar_timeout").c_str(), 0, true, String(radarTimeout).c_str()); } } diff --git a/src/MotionSensors.h b/src/MotionSensors.h index d1bc5e9..45ee77f 100644 --- a/src/MotionSensors.h +++ b/src/MotionSensors.h @@ -1,15 +1,17 @@ -#pragma once - -#include - -// Forward declares -class AsyncMqttClient; - -namespace Motion -{ - void Setup(); - void ConnectToWifi(); - void SerialReport(); - void Loop(AsyncMqttClient& mqttClient); - bool SendDiscovery(DynamicJsonDocument& doc); -} \ No newline at end of file +#pragma once + +#include + +// Forward declares +class AsyncMqttClient; + +namespace Motion +{ + void Setup(); + void ConnectToWifi(); + void SerialReport(); + void Loop(AsyncMqttClient& mqttClient); + bool SendDiscovery(DynamicJsonDocument& doc); + bool SendOnline(DynamicJsonDocument& doc); + bool Command(String& command, String& pay); +} diff --git a/src/defaults.h b/src/defaults.h index 161d134..e2fb1ab 100644 --- a/src/defaults.h +++ b/src/defaults.h @@ -15,6 +15,9 @@ // Maximum distance (in meters) to report. Devices that are calculated to be further than this distance in meters will not be reported #define DEFAULT_MAX_DISTANCE 16 +// Seconds before reporting radar/motion cleared +#define DEFAULT_DEBOUNCE_TIMEOUT 0.5 + // Define the base topic for room detection. Usually "espresense" #define CHANNEL String("espresense") @@ -51,3 +54,6 @@ #define DEFAULT_AUTO_UPDATE false #define DEFAULT_ARDUINO_OTA true #endif + +static const char *const EC_DIAGNOSTIC = "diagnostic"; +static const char *const EC_CONFIG = "config"; diff --git a/src/main.cpp b/src/main.cpp index 4495fc8..9e999de 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,7 +7,8 @@ bool sendTelemetry(int totalSeen, int totalFpSeen, int totalFpQueried, int total { if (!online) { - if (sendOnline()) + if (pub(statusTopic.c_str(), 0, true, "online") && pub((roomsTopic + "/max_distance").c_str(), 0, true, String(BleFingerprintCollection::maxDistance).c_str()) && pub((roomsTopic + "/absorption").c_str(), 0, true, String(BleFingerprintCollection::absorption).c_str()) && pub((roomsTopic + "/query").c_str(), 0, true, BleFingerprintCollection::query.c_str()) && pub((roomsTopic + "/include").c_str(), 0, true, BleFingerprintCollection::include.c_str()) && pub((roomsTopic + "/exclude").c_str(), 0, true, BleFingerprintCollection::exclude.c_str()) && pub((roomsTopic + "/known_macs").c_str(), 0, true, BleFingerprintCollection::knownMacs.c_str()) && pub((roomsTopic + "/count_ids").c_str(), 0, true, BleFingerprintCollection::countIds.c_str()) && pub((roomsTopic + "/status_led").c_str(), 0, true, String(GUI::statusLed ? "ON" : "OFF").c_str()) && pub((roomsTopic + "/arduino_ota").c_str(), 0, true, String(arduinoOta ? "ON" : "OFF").c_str()) && + pub((roomsTopic + "/auto_update").c_str(), 0, true, String(autoUpdate ? "ON" : "OFF").c_str()) && pub((roomsTopic + "/prerelease").c_str(), 0, true, String(prerelease ? "ON" : "OFF").c_str()) && pub((roomsTopic + "/active_scan").c_str(), 0, true, String(activeScan ? "ON" : "OFF").c_str()) && Motion::SendOnline(doc)) { online = true; reconnectTries = 0; @@ -196,6 +197,7 @@ void setupNetwork() WiFiSettings.heading("Misc ℹ️", false); GUI::statusLed = WiFiSettings.checkbox("status_led", true, "Status LED"); + Motion::ConnectToWifi(); #ifdef SENSORS dht11Pin = WiFiSettings.integer("dht11_pin", 0, "DHT11 sensor pin (0 for disable)"); @@ -318,77 +320,66 @@ void onMqttMessage(char *topic, char *payload, AsyncMqttClientMessageProperties if (commandPos < 0) return; auto command = top.substring(commandPos + 1, setPos); + bool changed = true; if (command == "max_distance") { BleFingerprintCollection::maxDistance = pay.toFloat(); spurt("/max_dist", pay); - online = false; } else if (command == "absorption") { BleFingerprintCollection::absorption = pay.toFloat(); spurt("/absorption", pay); - online = false; } else if (command == "active_scan") { activeScan = pay == "ON"; spurt("/active_scan", String(activeScan)); - online = false; } else if (command == "query") { BleFingerprintCollection::query = pay; spurt("/query", pay); - online = false; } else if (command == "include") { BleFingerprintCollection::include = pay; spurt("/include", pay); - online = false; } else if (command == "exclude") { BleFingerprintCollection::exclude = pay; spurt("/exclude", pay); - online = false; } else if (command == "known_macs") { BleFingerprintCollection::knownMacs = pay; spurt("/known_macs", pay); - online = false; } else if (command == "count_ids") { BleFingerprintCollection::countIds = pay; spurt("/count_ids", pay); - online = false; } else if (command == "status_led") { GUI::statusLed = pay == "ON"; spurt("/status_led", String(GUI::statusLed)); - online = false; } else if (command == "arduino_ota") { arduinoOta = pay == "ON"; spurt("/arduino_ota", String(arduinoOta)); - online = false; } else if (command == "auto_update") { autoUpdate = pay == "ON"; spurt("/auto_update", String(autoUpdate)); - online = false; } else if (command == "prerelease") { prerelease = pay == "ON"; spurt("/prerelease", String(prerelease)); - online = false; } else if (command == "restart") { @@ -398,6 +389,9 @@ void onMqttMessage(char *topic, char *payload, AsyncMqttClientMessageProperties { heap_caps_dump_all(); } + else if (Motion::Command(command, pay)){} + else changed = false; + if (changed) online = false; } void reconnect(TimerHandle_t xTimer) diff --git a/src/main.h b/src/main.h index 2e5b864..18a9891 100644 --- a/src/main.h +++ b/src/main.h @@ -56,9 +56,6 @@ int BH1750_I2c_Bus; #include "HX711.h" #endif -static const char *const EC_DIAGNOSTIC = "diagnostic"; -static const char *const EC_CONFIG = "config"; - AsyncMqttClient mqttClient; TimerHandle_t reconnectTimer; TaskHandle_t scanTaskHandle, reportTaskHandle; @@ -328,11 +325,6 @@ bool pub(const char *topic, uint8_t qos, bool retain, const char *payload, size_ return false; } -bool sendOnline() -{ - return pub(statusTopic.c_str(), 0, true, "online") && pub((roomsTopic + "/max_distance").c_str(), 0, true, String(BleFingerprintCollection::maxDistance).c_str()) && pub((roomsTopic + "/absorption").c_str(), 0, true, String(BleFingerprintCollection::absorption).c_str()) && pub((roomsTopic + "/query").c_str(), 0, true, BleFingerprintCollection::query.c_str()) && pub((roomsTopic + "/include").c_str(), 0, true, BleFingerprintCollection::include.c_str()) && pub((roomsTopic + "/exclude").c_str(), 0, true, BleFingerprintCollection::exclude.c_str()) && pub((roomsTopic + "/known_macs").c_str(), 0, true, BleFingerprintCollection::knownMacs.c_str()) && pub((roomsTopic + "/count_ids").c_str(), 0, true, BleFingerprintCollection::countIds.c_str()) && pub((roomsTopic + "/status_led").c_str(), 0, true, String(GUI::statusLed ? "ON" : "OFF").c_str()) && pub((roomsTopic + "/arduino_ota").c_str(), 0, true, String(arduinoOta ? "ON" : "OFF").c_str()) && pub((roomsTopic + "/auto_update").c_str(), 0, true, String(autoUpdate ? "ON" : "OFF").c_str()) && pub((roomsTopic + "/prerelease").c_str(), 0, true, String(prerelease ? "ON" : "OFF").c_str()) && pub((roomsTopic + "/active_scan").c_str(), 0, true, String(activeScan ? "ON" : "OFF").c_str()); -} - void commonDiscovery() { doc.clear(); @@ -525,7 +517,7 @@ bool sendNumberDiscovery(const String &name, const String &entityCategory) doc["avty_t"] = "~/status"; doc["stat_t"] = "~/" + slug; doc["cmd_t"] = "~/" + slug + "/set"; - doc["step"] = "0.01"; + doc["step"] = "0.1"; doc["entity_category"] = entityCategory; serializeJson(doc, buffer);