Multi PIR usermod

This commit is contained in:
Blaz Kristan 2024-02-05 22:13:32 +01:00
parent 00038453e1
commit 246746a82e
3 changed files with 83 additions and 69 deletions

View File

@ -15,6 +15,9 @@
#define PIR_SENSOR_OFF_SEC 600 #define PIR_SENSOR_OFF_SEC 600
#endif #endif
#ifndef PIR_SENSOR_MAX_SENSORS
#define PIR_SENSOR_MAX_SENSORS 1
#endif
/* /*
* This usermod handles PIR sensor states. * This usermod handles PIR sensor states.
@ -50,13 +53,13 @@ private:
volatile unsigned long offTimerStart = 0; // off timer start time volatile unsigned long offTimerStart = 0; // off timer start time
volatile bool PIRtriggered = false; // did PIR trigger? volatile bool PIRtriggered = false; // did PIR trigger?
bool sensorPinState = LOW; // current PIR sensor pin state bool initDone = false; // status of initialization
bool initDone = false; // status of initialization unsigned long lastLoop = 0;
unsigned long lastLoop = 0; bool sensorPinState[PIR_SENSOR_MAX_SENSORS] = {LOW}; // current PIR sensor pin state
// configurable parameters // configurable parameters
bool enabled = true; // PIR sensor enabled 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) 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_onPreset = 0; // on preset
uint8_t m_offPreset = 0; // off preset uint8_t m_offPreset = 0; // off preset
@ -325,21 +328,29 @@ void PIRsensorSwitch::publishHomeAssistantAutodiscovery()
bool PIRsensorSwitch::updatePIRsensorState() bool PIRsensorSwitch::updatePIRsensorState()
{ {
bool pinState = digitalRead(PIRsensorPin); bool stateChanged = false;
if (pinState != sensorPinState) { bool allOff = true;
sensorPinState = pinState; // change previous state for (int i = 0; i < PIR_SENSOR_MAX_SENSORS; i++) {
if (PIRsensorPin[i] < 0) continue;
if (sensorPinState == HIGH) { bool pinState = digitalRead(PIRsensorPin[i]);
offTimerStart = 0; if (pinState != sensorPinState[i]) {
if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()))) switchStrip(true); sensorPinState[i] = pinState; // change previous state
} else { stateChanged = true;
// start switch off timer
offTimerStart = millis(); 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() bool PIRsensorSwitch::handleOffTimer()
@ -356,18 +367,21 @@ bool PIRsensorSwitch::handleOffTimer()
void PIRsensorSwitch::setup() 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() // pin retrieved from cfg.json (readFromConfig()) prior to running setup()
if (PIRsensorPin >= 0 && pinManager.allocatePin(PIRsensorPin, false, PinOwner::UM_PIR)) { if (pinManager.allocatePin(PIRsensorPin[i], false, PinOwner::UM_PIR)) {
// PIR Sensor mode INPUT_PULLUP // PIR Sensor mode INPUT_PULLDOWN
pinMode(PIRsensorPin, INPUT_PULLUP); #ifdef ESP8266
sensorPinState = digitalRead(PIRsensorPin); 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 { } else {
if (PIRsensorPin >= 0) { DEBUG_PRINT(F("PIRSensorSwitch pin ")); DEBUG_PRINTLN(i); DEBUG_PRINTLN(F(" allocation failed."));
DEBUG_PRINTLN(F("PIRSensorSwitch pin allocation failed.")); PIRsensorPin[i] = -1; // allocation failed
}
PIRsensorPin = -1; // allocation failed
enabled = false;
} }
} }
initDone = true; initDone = true;
@ -382,8 +396,8 @@ void PIRsensorSwitch::onMqttConnect(bool sessionPresent)
void PIRsensorSwitch::loop() void PIRsensorSwitch::loop()
{ {
// only check sensors 4x/s // only check sensors 5x/s
if (!enabled || millis() - lastLoop < 250 || strip.isUpdating()) return; if (!enabled || millis() - lastLoop < 200) return;
lastLoop = millis(); lastLoop = millis();
if (!updatePIRsensorState()) { if (!updatePIRsensorState()) {
@ -396,37 +410,35 @@ void PIRsensorSwitch::addToJsonInfo(JsonObject &root)
JsonObject user = root["u"]; JsonObject user = root["u"];
if (user.isNull()) user = root.createNestedObject("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)); JsonArray infoArr = user.createNestedArray(FPSTR(_name));
String uiDomString; String uiDomString;
if (enabled) { if (enabled) {
if (offTimerStart > 0) if (offTimerStart > 0) {
{
uiDomString = ""; uiDomString = "";
unsigned int offSeconds = (m_switchOffDelay - (millis() - offTimerStart)) / 1000; unsigned int offSeconds = (m_switchOffDelay - (millis() - offTimerStart)) / 1000;
if (offSeconds >= 3600) if (offSeconds >= 3600) {
{
uiDomString += (offSeconds / 3600); uiDomString += (offSeconds / 3600);
uiDomString += F("h "); uiDomString += F("h ");
offSeconds %= 3600; offSeconds %= 3600;
} }
if (offSeconds >= 60) if (offSeconds >= 60) {
{
uiDomString += (offSeconds / 60); uiDomString += (offSeconds / 60);
offSeconds %= 60; offSeconds %= 60;
} } else if (uiDomString.length() > 0) {
else if (uiDomString.length() > 0)
{
uiDomString += 0; uiDomString += 0;
} }
if (uiDomString.length() > 0) if (uiDomString.length() > 0) {
{
uiDomString += F("min "); uiDomString += F("min ");
} }
uiDomString += (offSeconds); uiDomString += (offSeconds);
infoArr.add(uiDomString + F("s")); infoArr.add(uiDomString + F("s"));
} else { } else {
infoArr.add(sensorPinState ? F("sensor on") : F("inactive")); infoArr.add(state ? F("sensor on") : F("inactive"));
} }
} else { } else {
infoArr.add(F("disabled")); infoArr.add(F("disabled"));
@ -446,9 +458,11 @@ void PIRsensorSwitch::addToJsonInfo(JsonObject &root)
uiDomString += F("</button>"); uiDomString += F("</button>");
infoArr.add(uiDomString); infoArr.add(uiDomString);
JsonObject sensor = root[F("sensor")]; if (enabled) {
if (sensor.isNull()) sensor = root.createNestedObject(F("sensor")); JsonObject sensor = root[F("sensor")];
sensor[F("motion")] = sensorPinState || offTimerStart>0 ? true : false; if (sensor.isNull()) sensor = root.createNestedObject(F("sensor"));
sensor[F("motion")] = state || offTimerStart>0 ? true : false;
}
} }
void PIRsensorSwitch::onStateChange(uint8_t mode) { void PIRsensorSwitch::onStateChange(uint8_t mode) {
@ -478,7 +492,8 @@ void PIRsensorSwitch::addToConfig(JsonObject &root)
JsonObject top = root.createNestedObject(FPSTR(_name)); JsonObject top = root.createNestedObject(FPSTR(_name));
top[FPSTR(_enabled)] = enabled; top[FPSTR(_enabled)] = enabled;
top[FPSTR(_switchOffDelay)] = m_switchOffDelay / 1000; 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(_onPreset)] = m_onPreset;
top[FPSTR(_offPreset)] = m_offPreset; top[FPSTR(_offPreset)] = m_offPreset;
top[FPSTR(_nightTime)] = m_nightTimeOnly; 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: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 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 PIRsensorSwitch::readFromConfig(JsonObject &root)
{ {
bool oldEnabled = enabled; int8_t oldPin[PIR_SENSOR_MAX_SENSORS];
int8_t oldPin = PIRsensorPin; for (int i = 0; i < PIR_SENSOR_MAX_SENSORS; i++) {
oldPin[i] = PIRsensorPin[i];
PIRsensorPin[i] = -1;
}
DEBUG_PRINT(FPSTR(_name)); DEBUG_PRINT(FPSTR(_name));
JsonObject top = root[FPSTR(_name)]; JsonObject top = root[FPSTR(_name)];
@ -508,7 +531,13 @@ bool PIRsensorSwitch::readFromConfig(JsonObject &root)
return false; 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; enabled = top[FPSTR(_enabled)] | enabled;
@ -530,26 +559,11 @@ bool PIRsensorSwitch::readFromConfig(JsonObject &root)
// reading config prior to setup() // reading config prior to setup()
DEBUG_PRINTLN(F(" config loaded.")); DEBUG_PRINTLN(F(" config loaded."));
} else { } else {
if (oldPin != PIRsensorPin || oldEnabled != enabled) { for (int i = 0; i < PIR_SENSOR_MAX_SENSORS; i++)
// check if pin is OK if (oldPin[i] >= 0) pinManager.deallocatePin(oldPin[i], PinOwner::UM_PIR);
if (oldPin != PIRsensorPin && oldPin >= 0) { setup();
// 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);
}
}
DEBUG_PRINTLN(F(" config (re)loaded.")); DEBUG_PRINTLN(F(" config (re)loaded."));
} }
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features // 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);
} }

View File

@ -426,7 +426,7 @@
#ifdef ESP8266 #ifdef ESP8266
#define SETTINGS_STACK_BUF_SIZE 2048 #define SETTINGS_STACK_BUF_SIZE 2048
#else #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 #endif
#ifdef WLED_USE_ETHERNET #ifdef WLED_USE_ETHERNET

View File

@ -227,10 +227,10 @@
} else if (typeof(fld) === "number") sel.classList.add("pin"); // a hack to add a class } else if (typeof(fld) === "number") sel.classList.add("pin"); // a hack to add a class
let arr = d.getElementsByName(um); let arr = d.getElementsByName(um);
let idx = arr[0].type==="hidden"?1:0; // ignore hidden field 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) // we have array of values (usually pins)
for (let i of arr) { for (let i of arr) {
if (i.type === "number") break; if (i.nodeName === "INPUT" && i.type === "number") break;
idx++; idx++;
} }
} }