From 246746a82e88a4c99ee61d8e2704f875f99e8ae8 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Mon, 5 Feb 2024 22:13:32 +0100 Subject: [PATCH] Multi PIR usermod --- .../usermod_PIR_sensor_switch.h | 146 ++++++++++-------- wled00/const.h | 2 +- wled00/data/settings_um.htm | 4 +- 3 files changed, 83 insertions(+), 69 deletions(-) diff --git a/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h b/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h index 2e909ae0e..99286748d 100644 --- a/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h +++ b/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h @@ -15,6 +15,9 @@ #define PIR_SENSOR_OFF_SEC 600 #endif +#ifndef PIR_SENSOR_MAX_SENSORS + #define PIR_SENSOR_MAX_SENSORS 1 +#endif /* * This usermod handles PIR sensor states. @@ -50,13 +53,13 @@ private: volatile unsigned long offTimerStart = 0; // off timer start time volatile bool PIRtriggered = false; // did PIR trigger? - bool sensorPinState = LOW; // current PIR sensor pin state - bool initDone = false; // status of initialization - unsigned long lastLoop = 0; + bool initDone = false; // status of initialization + unsigned long lastLoop = 0; + bool sensorPinState[PIR_SENSOR_MAX_SENSORS] = {LOW}; // current PIR sensor pin state // configurable parameters bool enabled = true; // PIR sensor enabled - int8_t PIRsensorPin = PIR_SENSOR_PIN; // PIR sensor pin + int8_t PIRsensorPin[PIR_SENSOR_MAX_SENSORS] = {PIR_SENSOR_PIN}; // PIR sensor pin uint32_t m_switchOffDelay = PIR_SENSOR_OFF_SEC*1000; // delay before switch off after the sensor state goes LOW (10min) uint8_t m_onPreset = 0; // on preset uint8_t m_offPreset = 0; // off preset @@ -325,21 +328,29 @@ void PIRsensorSwitch::publishHomeAssistantAutodiscovery() bool PIRsensorSwitch::updatePIRsensorState() { - bool pinState = digitalRead(PIRsensorPin); - if (pinState != sensorPinState) { - sensorPinState = pinState; // change previous state + bool stateChanged = false; + bool allOff = true; + for (int i = 0; i < PIR_SENSOR_MAX_SENSORS; i++) { + if (PIRsensorPin[i] < 0) continue; - if (sensorPinState == HIGH) { - offTimerStart = 0; - if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()))) switchStrip(true); - } else { - // start switch off timer - offTimerStart = millis(); + bool pinState = digitalRead(PIRsensorPin[i]); + if (pinState != sensorPinState[i]) { + sensorPinState[i] = pinState; // change previous state + stateChanged = true; + + if (sensorPinState[i] == HIGH) { + offTimerStart = 0; + allOff = false; + if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()))) switchStrip(true); + } } - publishMqtt(sensorPinState == HIGH); - return true; } - return false; + if (stateChanged) { + publishMqtt(!allOff); + // start switch off timer + if (allOff) offTimerStart = millis(); + } + return stateChanged; } bool PIRsensorSwitch::handleOffTimer() @@ -356,18 +367,21 @@ bool PIRsensorSwitch::handleOffTimer() void PIRsensorSwitch::setup() { - if (enabled) { + for (int i = 0; i < PIR_SENSOR_MAX_SENSORS; i++) { + sensorPinState[i] = LOW; + if (PIRsensorPin[i] < 0) continue; // pin retrieved from cfg.json (readFromConfig()) prior to running setup() - if (PIRsensorPin >= 0 && pinManager.allocatePin(PIRsensorPin, false, PinOwner::UM_PIR)) { - // PIR Sensor mode INPUT_PULLUP - pinMode(PIRsensorPin, INPUT_PULLUP); - sensorPinState = digitalRead(PIRsensorPin); + if (pinManager.allocatePin(PIRsensorPin[i], false, PinOwner::UM_PIR)) { + // PIR Sensor mode INPUT_PULLDOWN + #ifdef ESP8266 + pinMode(PIRsensorPin[i], PIRsensorPin[i]==16 ? INPUT_PULLDOWN : INPUT_PULLUP); // ESP8266 has INPUT_PULLDOWN on GPIO16 only + #else + pinMode(PIRsensorPin[i], INPUT_PULLDOWN); + #endif + sensorPinState[i] = digitalRead(PIRsensorPin[i]); } else { - if (PIRsensorPin >= 0) { - DEBUG_PRINTLN(F("PIRSensorSwitch pin allocation failed.")); - } - PIRsensorPin = -1; // allocation failed - enabled = false; + DEBUG_PRINT(F("PIRSensorSwitch pin ")); DEBUG_PRINTLN(i); DEBUG_PRINTLN(F(" allocation failed.")); + PIRsensorPin[i] = -1; // allocation failed } } initDone = true; @@ -382,8 +396,8 @@ void PIRsensorSwitch::onMqttConnect(bool sessionPresent) void PIRsensorSwitch::loop() { - // only check sensors 4x/s - if (!enabled || millis() - lastLoop < 250 || strip.isUpdating()) return; + // only check sensors 5x/s + if (!enabled || millis() - lastLoop < 200) return; lastLoop = millis(); if (!updatePIRsensorState()) { @@ -396,37 +410,35 @@ void PIRsensorSwitch::addToJsonInfo(JsonObject &root) JsonObject user = root["u"]; if (user.isNull()) user = root.createNestedObject("u"); + bool state = LOW; + for (int i = 0; i < PIR_SENSOR_MAX_SENSORS; i++) + if (PIRsensorPin[i] >= 0) state |= sensorPinState[i]; + JsonArray infoArr = user.createNestedArray(FPSTR(_name)); String uiDomString; if (enabled) { - if (offTimerStart > 0) - { + if (offTimerStart > 0) { uiDomString = ""; unsigned int offSeconds = (m_switchOffDelay - (millis() - offTimerStart)) / 1000; - if (offSeconds >= 3600) - { + if (offSeconds >= 3600) { uiDomString += (offSeconds / 3600); uiDomString += F("h "); offSeconds %= 3600; } - if (offSeconds >= 60) - { + if (offSeconds >= 60) { uiDomString += (offSeconds / 60); offSeconds %= 60; - } - else if (uiDomString.length() > 0) - { + } else if (uiDomString.length() > 0) { uiDomString += 0; } - if (uiDomString.length() > 0) - { + if (uiDomString.length() > 0) { uiDomString += F("min "); } uiDomString += (offSeconds); infoArr.add(uiDomString + F("s")); } else { - infoArr.add(sensorPinState ? F("sensor on") : F("inactive")); + infoArr.add(state ? F("sensor on") : F("inactive")); } } else { infoArr.add(F("disabled")); @@ -446,9 +458,11 @@ void PIRsensorSwitch::addToJsonInfo(JsonObject &root) uiDomString += F(""); infoArr.add(uiDomString); - JsonObject sensor = root[F("sensor")]; - if (sensor.isNull()) sensor = root.createNestedObject(F("sensor")); - sensor[F("motion")] = sensorPinState || offTimerStart>0 ? true : false; + if (enabled) { + JsonObject sensor = root[F("sensor")]; + if (sensor.isNull()) sensor = root.createNestedObject(F("sensor")); + sensor[F("motion")] = state || offTimerStart>0 ? true : false; + } } void PIRsensorSwitch::onStateChange(uint8_t mode) { @@ -478,7 +492,8 @@ void PIRsensorSwitch::addToConfig(JsonObject &root) JsonObject top = root.createNestedObject(FPSTR(_name)); top[FPSTR(_enabled)] = enabled; top[FPSTR(_switchOffDelay)] = m_switchOffDelay / 1000; - top["pin"] = PIRsensorPin; + JsonArray pinArray = top.createNestedArray("pin"); + for (int i = 0; i < PIR_SENSOR_MAX_SENSORS; i++) pinArray.add(PIRsensorPin[i]); top[FPSTR(_onPreset)] = m_onPreset; top[FPSTR(_offPreset)] = m_offPreset; top[FPSTR(_nightTime)] = m_nightTimeOnly; @@ -494,12 +509,20 @@ void PIRsensorSwitch::appendConfigData() { oappend(SET_F("addInfo('PIRsensorSwitch:HA-discovery',1,'HA=Home Assistant');")); // 0 is field type, 1 is actual field oappend(SET_F("addInfo('PIRsensorSwitch:override',1,'Cancel timer on change');")); // 0 is field type, 1 is actual field + for (int i = 0; i < PIR_SENSOR_MAX_SENSORS; i++) { + char str[128]; + sprintf_P(str, PSTR("addInfo('PIRsensorSwitch:pin[]',%d,'','#%d');"), i, i); + oappend(str); + } } bool PIRsensorSwitch::readFromConfig(JsonObject &root) { - bool oldEnabled = enabled; - int8_t oldPin = PIRsensorPin; + int8_t oldPin[PIR_SENSOR_MAX_SENSORS]; + for (int i = 0; i < PIR_SENSOR_MAX_SENSORS; i++) { + oldPin[i] = PIRsensorPin[i]; + PIRsensorPin[i] = -1; + } DEBUG_PRINT(FPSTR(_name)); JsonObject top = root[FPSTR(_name)]; @@ -508,7 +531,13 @@ bool PIRsensorSwitch::readFromConfig(JsonObject &root) return false; } - PIRsensorPin = top["pin"] | PIRsensorPin; + JsonArray pins = top["pin"]; + if (!pins.isNull()) { + for (int i = 0; i < PIR_SENSOR_MAX_SENSORS; i++) + if (i < pins.size()) PIRsensorPin[i] = pins[i] | PIRsensorPin[i]; + } else { + PIRsensorPin[0] = top["pin"] | oldPin[0]; + } enabled = top[FPSTR(_enabled)] | enabled; @@ -530,26 +559,11 @@ bool PIRsensorSwitch::readFromConfig(JsonObject &root) // reading config prior to setup() DEBUG_PRINTLN(F(" config loaded.")); } else { - if (oldPin != PIRsensorPin || oldEnabled != enabled) { - // check if pin is OK - if (oldPin != PIRsensorPin && oldPin >= 0) { - // if we are changing pin in settings page - // deallocate old pin - pinManager.deallocatePin(oldPin, PinOwner::UM_PIR); - if (pinManager.allocatePin(PIRsensorPin, false, PinOwner::UM_PIR)) { - pinMode(PIRsensorPin, INPUT_PULLUP); - } else { - // allocation failed - PIRsensorPin = -1; - enabled = false; - } - } - if (enabled) { - sensorPinState = digitalRead(PIRsensorPin); - } - } + for (int i = 0; i < PIR_SENSOR_MAX_SENSORS; i++) + if (oldPin[i] >= 0) pinManager.deallocatePin(oldPin[i], PinOwner::UM_PIR); + setup(); DEBUG_PRINTLN(F(" config (re)loaded.")); } // use "return !top["newestParameter"].isNull();" when updating Usermod with new features - return !top[FPSTR(_domoticzIDX)].isNull(); + return !(pins.isNull() || pins.size() != PIR_SENSOR_MAX_SENSORS); } diff --git a/wled00/const.h b/wled00/const.h index 2ddfb858d..b02b6696e 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -426,7 +426,7 @@ #ifdef ESP8266 #define SETTINGS_STACK_BUF_SIZE 2048 #else -#define SETTINGS_STACK_BUF_SIZE 3608 // warning: quite a large value for stack +#define SETTINGS_STACK_BUF_SIZE 3840 // warning: quite a large value for stack (640 * WLED_MAX_USERMODS) #endif #ifdef WLED_USE_ETHERNET diff --git a/wled00/data/settings_um.htm b/wled00/data/settings_um.htm index f9b7b28ee..a50fc8269 100644 --- a/wled00/data/settings_um.htm +++ b/wled00/data/settings_um.htm @@ -227,10 +227,10 @@ } else if (typeof(fld) === "number") sel.classList.add("pin"); // a hack to add a class let arr = d.getElementsByName(um); let idx = arr[0].type==="hidden"?1:0; // ignore hidden field - if (arr.length > 2) { + if (arr.length > 1+idx) { // we have array of values (usually pins) for (let i of arr) { - if (i.type === "number") break; + if (i.nodeName === "INPUT" && i.type === "number") break; idx++; } }