Added PIR option to trigger only if WLED is off.

This commit is contained in:
Blaz Kristan 2021-07-23 18:43:51 +02:00
parent f702e1a80d
commit 9f0f6181a1

View File

@ -1,399 +1,406 @@
#pragma once #pragma once
#include "wled.h" #include "wled.h"
#ifndef PIR_SENSOR_PIN #ifndef PIR_SENSOR_PIN
// compatible with QuinLED-Dig-Uno // compatible with QuinLED-Dig-Uno
#ifdef ARDUINO_ARCH_ESP32 #ifdef ARDUINO_ARCH_ESP32
#define PIR_SENSOR_PIN 23 // Q4 #define PIR_SENSOR_PIN 23 // Q4
#else //ESP8266 boards #else //ESP8266 boards
#define PIR_SENSOR_PIN 13 // Q4 (D7 on D1 mini) #define PIR_SENSOR_PIN 13 // Q4 (D7 on D1 mini)
#endif #endif
#endif #endif
/* /*
* This usermod handles PIR sensor states. * This usermod handles PIR sensor states.
* The strip will be switched on and the off timer will be resetted when the sensor goes HIGH. * The strip will be switched on and the off timer will be resetted when the sensor goes HIGH.
* When the sensor state goes LOW, the off timer is started and when it expires, the strip is switched off. * When the sensor state goes LOW, the off timer is started and when it expires, the strip is switched off.
* *
* *
* Usermods allow you to add own functionality to WLED more easily * Usermods allow you to add own functionality to WLED more easily
* See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality * See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality
* *
* v2 usermods are class inheritance based and can (but don't have to) implement more functions, each of them is shown in this example. * v2 usermods are class inheritance based and can (but don't have to) implement more functions, each of them is shown in this example.
* Multiple v2 usermods can be added to one compilation easily. * Multiple v2 usermods can be added to one compilation easily.
* *
* Creating a usermod: * Creating a usermod:
* This file serves as an example. If you want to create a usermod, it is recommended to use usermod_v2_empty.h from the usermods folder as a template. * This file serves as an example. If you want to create a usermod, it is recommended to use usermod_v2_empty.h from the usermods folder as a template.
* Please remember to rename the class and file to a descriptive name. * Please remember to rename the class and file to a descriptive name.
* You may also use multiple .h and .cpp files. * You may also use multiple .h and .cpp files.
* *
* Using a usermod: * Using a usermod:
* 1. Copy the usermod into the sketch folder (same folder as wled00.ino) * 1. Copy the usermod into the sketch folder (same folder as wled00.ino)
* 2. Register the usermod by adding #include "usermod_filename.h" in the top and registerUsermod(new MyUsermodClass()) in the bottom of usermods_list.cpp * 2. Register the usermod by adding #include "usermod_filename.h" in the top and registerUsermod(new MyUsermodClass()) in the bottom of usermods_list.cpp
*/ */
class PIRsensorSwitch : public Usermod class PIRsensorSwitch : public Usermod
{ {
public: public:
/** /**
* constructor * constructor
*/ */
PIRsensorSwitch() {} PIRsensorSwitch() {}
/** /**
* desctructor * desctructor
*/ */
~PIRsensorSwitch() {} ~PIRsensorSwitch() {}
/** /**
* Enable/Disable the PIR sensor * Enable/Disable the PIR sensor
*/ */
void EnablePIRsensor(bool en) { enabled = en; } void EnablePIRsensor(bool en) { enabled = en; }
/** /**
* Get PIR sensor enabled/disabled state * Get PIR sensor enabled/disabled state
*/ */
bool PIRsensorEnabled() { return enabled; } bool PIRsensorEnabled() { return enabled; }
private: private:
// PIR sensor pin // PIR sensor pin
int8_t PIRsensorPin = PIR_SENSOR_PIN; int8_t PIRsensorPin = PIR_SENSOR_PIN;
// notification mode for colorUpdated() // notification mode for colorUpdated()
const byte NotifyUpdateMode = CALL_MODE_NO_NOTIFY; // CALL_MODE_DIRECT_CHANGE const byte NotifyUpdateMode = CALL_MODE_NO_NOTIFY; // CALL_MODE_DIRECT_CHANGE
// delay before switch off after the sensor state goes LOW // delay before switch off after the sensor state goes LOW
uint32_t m_switchOffDelay = 600000; // 10min uint32_t m_switchOffDelay = 600000; // 10min
// off timer start time // off timer start time
uint32_t m_offTimerStart = 0; uint32_t m_offTimerStart = 0;
// current PIR sensor pin state // current PIR sensor pin state
byte sensorPinState = LOW; byte sensorPinState = LOW;
// PIR sensor enabled // PIR sensor enabled
bool enabled = true; bool enabled = true;
// status of initialisation // status of initialisation
bool initDone = false; bool initDone = false;
// on and off presets // on and off presets
uint8_t m_onPreset = 0; uint8_t m_onPreset = 0;
uint8_t m_offPreset = 0; uint8_t m_offPreset = 0;
// flag to indicate that PIR sensor should activate WLED during nighttime only // flag to indicate that PIR sensor should activate WLED during nighttime only
bool m_nightTimeOnly = false; bool m_nightTimeOnly = false;
// flag to send MQTT message only (assuming it is enabled) // flag to send MQTT message only (assuming it is enabled)
bool m_mqttOnly = false; bool m_mqttOnly = false;
// flag to enable triggering only if WLED is initially off (LEDs are not on, preventing running effect being overwritten by PIR)
unsigned long lastLoop = 0; bool m_offOnly = false;
bool PIRtriggered = false;
// strings to reduce flash memory usage (used more than twice)
static const char _name[]; unsigned long lastLoop = 0;
static const char _switchOffDelay[];
static const char _enabled[]; // strings to reduce flash memory usage (used more than twice)
static const char _onPreset[]; static const char _name[];
static const char _offPreset[]; static const char _switchOffDelay[];
static const char _nightTime[]; static const char _enabled[];
static const char _mqttOnly[]; static const char _onPreset[];
static const char _offPreset[];
/** static const char _nightTime[];
* check if it is daytime static const char _mqttOnly[];
* if sunrise/sunset is not defined (no NTP or lat/lon) default to nighttime static const char _offOnly[];
*/
bool isDayTime() { /**
bool isDayTime = false; * check if it is daytime
updateLocalTime(); * if sunrise/sunset is not defined (no NTP or lat/lon) default to nighttime
uint8_t hr = hour(localTime); */
uint8_t mi = minute(localTime); bool isDayTime() {
bool isDayTime = false;
if (sunrise && sunset) { updateLocalTime();
if (hour(sunrise)<hr && hour(sunset)>hr) { uint8_t hr = hour(localTime);
isDayTime = true; uint8_t mi = minute(localTime);
} else {
if (hour(sunrise)==hr && minute(sunrise)<mi) { if (sunrise && sunset) {
isDayTime = true; if (hour(sunrise)<hr && hour(sunset)>hr) {
} isDayTime = true;
if (hour(sunset)==hr && minute(sunset)>mi) { } else {
isDayTime = true; if (hour(sunrise)==hr && minute(sunrise)<mi) {
} isDayTime = true;
} }
} if (hour(sunset)==hr && minute(sunset)>mi) {
return isDayTime; isDayTime = true;
} }
}
/** }
* switch strip on/off return isDayTime;
*/ }
void switchStrip(bool switchOn)
{ /**
if (switchOn && m_onPreset) { * switch strip on/off
applyPreset(m_onPreset); */
} else if (!switchOn && m_offPreset) { void switchStrip(bool switchOn)
applyPreset(m_offPreset); {
} else if (switchOn && bri == 0) { PIRtriggered = switchOn;
bri = briLast; if (switchOn && m_onPreset) {
colorUpdated(NotifyUpdateMode); applyPreset(m_onPreset);
} else if (!switchOn && bri != 0) { } else if (!switchOn && m_offPreset) {
briLast = bri; applyPreset(m_offPreset);
bri = 0; } else if (switchOn && bri == 0) {
colorUpdated(NotifyUpdateMode); bri = briLast;
} colorUpdated(NotifyUpdateMode);
} } else if (!switchOn && bri != 0) {
briLast = bri;
void publishMqtt(const char* state) bri = 0;
{ colorUpdated(NotifyUpdateMode);
//Check if MQTT Connected, otherwise it will crash the 8266 }
if (WLED_MQTT_CONNECTED){ }
char subuf[64];
strcpy(subuf, mqttDeviceTopic); void publishMqtt(const char* state)
strcat_P(subuf, PSTR("/motion")); {
mqtt->publish(subuf, 0, false, state); //Check if MQTT Connected, otherwise it will crash the 8266
} if (WLED_MQTT_CONNECTED){
} char subuf[64];
strcpy(subuf, mqttDeviceTopic);
/** strcat_P(subuf, PSTR("/motion"));
* Read and update PIR sensor state. mqtt->publish(subuf, 0, false, state);
* Initilize/reset switch off timer }
*/ }
bool updatePIRsensorState()
{ /**
bool pinState = digitalRead(PIRsensorPin); * Read and update PIR sensor state.
if (pinState != sensorPinState) { * Initilize/reset switch off timer
sensorPinState = pinState; // change previous state */
bool updatePIRsensorState()
if (sensorPinState == HIGH) { {
m_offTimerStart = 0; bool pinState = digitalRead(PIRsensorPin);
if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()))) switchStrip(true); if (pinState != sensorPinState) {
publishMqtt("on"); sensorPinState = pinState; // change previous state
} else /*if (bri != 0)*/ {
// start switch off timer if (sensorPinState == HIGH) {
m_offTimerStart = millis(); m_offTimerStart = 0;
} if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()))) switchStrip(true);
return true; publishMqtt("on");
} } else /*if (bri != 0)*/ {
return false; // start switch off timer
} m_offTimerStart = millis();
}
/** return true;
* switch off the strip if the delay has elapsed }
*/ return false;
bool handleOffTimer() }
{
if (m_offTimerStart > 0 && millis() - m_offTimerStart > m_switchOffDelay) /**
{ * switch off the strip if the delay has elapsed
if (enabled == true) */
{ bool handleOffTimer()
if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()))) switchStrip(false); {
publishMqtt("off"); if (m_offTimerStart > 0 && millis() - m_offTimerStart > m_switchOffDelay)
} {
m_offTimerStart = 0; if (enabled == true)
return true; {
} if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()))) switchStrip(false);
return false; publishMqtt("off");
} }
m_offTimerStart = 0;
public: return true;
//Functions called by WLED }
return false;
/** }
* setup() is called once at boot. WiFi is not yet connected at this point.
* You can use it to initialize variables, sensors or similar. public:
*/ //Functions called by WLED
void setup()
{ /**
if (enabled) { * setup() is called once at boot. WiFi is not yet connected at this point.
// pin retrieved from cfg.json (readFromConfig()) prior to running setup() * You can use it to initialize variables, sensors or similar.
if (PIRsensorPin >= 0 && pinManager.allocatePin(PIRsensorPin,false)) { */
// PIR Sensor mode INPUT_PULLUP void setup()
pinMode(PIRsensorPin, INPUT_PULLUP); {
sensorPinState = digitalRead(PIRsensorPin); if (enabled) {
} else { // pin retrieved from cfg.json (readFromConfig()) prior to running setup()
if (PIRsensorPin >= 0) DEBUG_PRINTLN(F("PIRSensorSwitch pin allocation failed.")); if (PIRsensorPin >= 0 && pinManager.allocatePin(PIRsensorPin,false)) {
PIRsensorPin = -1; // allocation failed // PIR Sensor mode INPUT_PULLUP
enabled = false; pinMode(PIRsensorPin, INPUT_PULLUP);
} sensorPinState = digitalRead(PIRsensorPin);
} } else {
initDone = true; if (PIRsensorPin >= 0) DEBUG_PRINTLN(F("PIRSensorSwitch pin allocation failed."));
} PIRsensorPin = -1; // allocation failed
enabled = false;
/** }
* connected() is called every time the WiFi is (re)connected }
* Use it to initialize network interfaces initDone = true;
*/ }
void connected()
{ /**
} * connected() is called every time the WiFi is (re)connected
* Use it to initialize network interfaces
/** */
* loop() is called continuously. Here you can check for events, read sensors, etc. void connected()
*/ {
void loop() }
{
// only check sensors 4x/s /**
if (!enabled || millis() - lastLoop < 250 || strip.isUpdating()) return; * loop() is called continuously. Here you can check for events, read sensors, etc.
lastLoop = millis(); */
void loop()
if (!updatePIRsensorState()) { {
handleOffTimer(); // only check sensors 4x/s
} if (!enabled || millis() - lastLoop < 250 || strip.isUpdating() || (m_offOnly && bri && !PIRtriggered)) return;
} lastLoop = millis();
/** if (!updatePIRsensorState()) {
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API. handleOffTimer();
* }
* Add PIR sensor state and switch off timer duration to jsoninfo }
*/
void addToJsonInfo(JsonObject &root) /**
{ * addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
JsonObject user = root["u"]; *
if (user.isNull()) user = root.createNestedObject("u"); * Add PIR sensor state and switch off timer duration to jsoninfo
*/
if (enabled) void addToJsonInfo(JsonObject &root)
{ {
// off timer JsonObject user = root["u"];
String uiDomString = F("PIR <i class=\"icons\">&#xe325;</i>"); if (user.isNull()) user = root.createNestedObject("u");
JsonArray infoArr = user.createNestedArray(uiDomString); // timer value
if (m_offTimerStart > 0) if (enabled)
{ {
uiDomString = ""; // off timer
unsigned int offSeconds = (m_switchOffDelay - (millis() - m_offTimerStart)) / 1000; String uiDomString = F("PIR <i class=\"icons\">&#xe325;</i>");
if (offSeconds >= 3600) JsonArray infoArr = user.createNestedArray(uiDomString); // timer value
{ if (m_offTimerStart > 0)
uiDomString += (offSeconds / 3600); {
uiDomString += F("h "); uiDomString = "";
offSeconds %= 3600; unsigned int offSeconds = (m_switchOffDelay - (millis() - m_offTimerStart)) / 1000;
} if (offSeconds >= 3600)
if (offSeconds >= 60) {
{ uiDomString += (offSeconds / 3600);
uiDomString += (offSeconds / 60); uiDomString += F("h ");
offSeconds %= 60; offSeconds %= 3600;
} }
else if (uiDomString.length() > 0) if (offSeconds >= 60)
{ {
uiDomString += 0; uiDomString += (offSeconds / 60);
} offSeconds %= 60;
if (uiDomString.length() > 0) }
{ else if (uiDomString.length() > 0)
uiDomString += F("min "); {
} uiDomString += 0;
uiDomString += (offSeconds); }
infoArr.add(uiDomString + F("s")); if (uiDomString.length() > 0)
} else { {
infoArr.add(sensorPinState ? F("sensor on") : F("inactive")); uiDomString += F("min ");
} }
} else { uiDomString += (offSeconds);
String uiDomString = F("PIR sensor"); infoArr.add(uiDomString + F("s"));
JsonArray infoArr = user.createNestedArray(uiDomString); } else {
infoArr.add(F("disabled")); infoArr.add(sensorPinState ? F("sensor on") : F("inactive"));
} }
} } else {
String uiDomString = F("PIR sensor");
/** JsonArray infoArr = user.createNestedArray(uiDomString);
* addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object). infoArr.add(F("disabled"));
* Values in the state object may be modified by connected clients }
*/ }
/*
void addToJsonState(JsonObject &root) /**
{ * addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object).
} * Values in the state object may be modified by connected clients
*/ */
/*
/** void addToJsonState(JsonObject &root)
* readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). {
* Values in the state object may be modified by connected clients }
*/ */
/*
void readFromJsonState(JsonObject &root) /**
{ * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object).
} * Values in the state object may be modified by connected clients
*/ */
/*
/** void readFromJsonState(JsonObject &root)
* provide the changeable values {
*/ }
void addToConfig(JsonObject &root) */
{
JsonObject top = root.createNestedObject(FPSTR(_name)); /**
top[FPSTR(_enabled)] = enabled; * provide the changeable values
top[FPSTR(_switchOffDelay)] = m_switchOffDelay / 1000; */
top["pin"] = PIRsensorPin; void addToConfig(JsonObject &root)
top[FPSTR(_onPreset)] = m_onPreset; {
top[FPSTR(_offPreset)] = m_offPreset; JsonObject top = root.createNestedObject(FPSTR(_name));
top[FPSTR(_nightTime)] = m_nightTimeOnly; top[FPSTR(_enabled)] = enabled;
top[FPSTR(_mqttOnly)] = m_mqttOnly; top[FPSTR(_switchOffDelay)] = m_switchOffDelay / 1000;
DEBUG_PRINTLN(F("PIR config saved.")); top["pin"] = PIRsensorPin;
} top[FPSTR(_onPreset)] = m_onPreset;
top[FPSTR(_offPreset)] = m_offPreset;
/** top[FPSTR(_nightTime)] = m_nightTimeOnly;
* restore the changeable values top[FPSTR(_mqttOnly)] = m_mqttOnly;
* readFromConfig() is called before setup() to populate properties from values stored in cfg.json top[FPSTR(_offOnly)] = m_offOnly;
* DEBUG_PRINTLN(F("PIR config saved."));
* The function should return true if configuration was successfully loaded or false if there was no configuration. }
*/
bool readFromConfig(JsonObject &root) /**
{ * restore the changeable values
bool oldEnabled = enabled; * readFromConfig() is called before setup() to populate properties from values stored in cfg.json
int8_t oldPin = PIRsensorPin; *
* The function should return true if configuration was successfully loaded or false if there was no configuration.
JsonObject top = root[FPSTR(_name)]; */
if (top.isNull()) { bool readFromConfig(JsonObject &root)
DEBUG_PRINT(FPSTR(_name)); {
DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); bool oldEnabled = enabled;
return false; int8_t oldPin = PIRsensorPin;
}
JsonObject top = root[FPSTR(_name)];
PIRsensorPin = top["pin"] | PIRsensorPin; if (top.isNull()) {
// PIRsensorPin = min(39,max(-1,(int)PIRsensorPin)); // check bounds DEBUG_PRINT(FPSTR(_name));
DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
enabled = top[FPSTR(_enabled)] | enabled; return false;
}
m_switchOffDelay = (top[FPSTR(_switchOffDelay)] | m_switchOffDelay/1000) * 1000;
PIRsensorPin = top["pin"] | PIRsensorPin;
m_onPreset = top[FPSTR(_onPreset)] | m_onPreset;
m_onPreset = max(0,min(250,(int)m_onPreset)); enabled = top[FPSTR(_enabled)] | enabled;
m_offPreset = top[FPSTR(_offPreset)] | m_offPreset; m_switchOffDelay = (top[FPSTR(_switchOffDelay)] | m_switchOffDelay/1000) * 1000;
m_offPreset = max(0,min(250,(int)m_offPreset));
m_onPreset = top[FPSTR(_onPreset)] | m_onPreset;
m_nightTimeOnly = top[FPSTR(_nightTime)] | m_nightTimeOnly; m_onPreset = max(0,min(250,(int)m_onPreset));
m_mqttOnly = top[FPSTR(_mqttOnly)] | m_mqttOnly;
m_offPreset = top[FPSTR(_offPreset)] | m_offPreset;
DEBUG_PRINT(FPSTR(_name)); m_offPreset = max(0,min(250,(int)m_offPreset));
if (!initDone) {
// reading config prior to setup() m_nightTimeOnly = top[FPSTR(_nightTime)] | m_nightTimeOnly;
DEBUG_PRINTLN(F(" config loaded.")); m_mqttOnly = top[FPSTR(_mqttOnly)] | m_mqttOnly;
} else { m_offOnly = top[FPSTR(_offOnly)] | m_offOnly;
if (oldPin != PIRsensorPin || oldEnabled != enabled) {
// check if pin is OK DEBUG_PRINT(FPSTR(_name));
if (oldPin != PIRsensorPin && oldPin >= 0) { if (!initDone) {
// if we are changing pin in settings page // reading config prior to setup()
// deallocate old pin DEBUG_PRINTLN(F(" config loaded."));
pinManager.deallocatePin(oldPin); } else {
if (pinManager.allocatePin(PIRsensorPin,false)) { if (oldPin != PIRsensorPin || oldEnabled != enabled) {
pinMode(PIRsensorPin, INPUT_PULLUP); // check if pin is OK
} else { if (oldPin != PIRsensorPin && oldPin >= 0) {
// allocation failed // if we are changing pin in settings page
PIRsensorPin = -1; // deallocate old pin
enabled = false; pinManager.deallocatePin(oldPin);
} if (pinManager.allocatePin(PIRsensorPin,false)) {
} pinMode(PIRsensorPin, INPUT_PULLUP);
if (enabled) { } else {
sensorPinState = digitalRead(PIRsensorPin); // allocation failed
} PIRsensorPin = -1;
} enabled = false;
DEBUG_PRINTLN(F(" config (re)loaded.")); }
} }
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features if (enabled) {
return true; sensorPinState = digitalRead(PIRsensorPin);
} }
}
/** DEBUG_PRINTLN(F(" config (re)loaded."));
* getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!). }
* This could be used in the future for the system to determine whether your usermod is installed. // use "return !top["newestParameter"].isNull();" when updating Usermod with new features
*/ return !top[FPSTR(_offOnly)].isNull();
uint16_t getId() }
{
return USERMOD_ID_PIRSWITCH; /**
} * getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).
}; * This could be used in the future for the system to determine whether your usermod is installed.
*/
// strings to reduce flash memory usage (used more than twice) uint16_t getId()
const char PIRsensorSwitch::_name[] PROGMEM = "PIRsensorSwitch"; {
const char PIRsensorSwitch::_enabled[] PROGMEM = "PIRenabled"; return USERMOD_ID_PIRSWITCH;
const char PIRsensorSwitch::_switchOffDelay[] PROGMEM = "PIRoffSec"; }
const char PIRsensorSwitch::_onPreset[] PROGMEM = "on-preset"; };
const char PIRsensorSwitch::_offPreset[] PROGMEM = "off-preset";
const char PIRsensorSwitch::_nightTime[] PROGMEM = "nighttime-only"; // strings to reduce flash memory usage (used more than twice)
const char PIRsensorSwitch::_mqttOnly[] PROGMEM = "mqtt-only"; const char PIRsensorSwitch::_name[] PROGMEM = "PIRsensorSwitch";
const char PIRsensorSwitch::_enabled[] PROGMEM = "PIRenabled";
const char PIRsensorSwitch::_switchOffDelay[] PROGMEM = "PIRoffSec";
const char PIRsensorSwitch::_onPreset[] PROGMEM = "on-preset";
const char PIRsensorSwitch::_offPreset[] PROGMEM = "off-preset";
const char PIRsensorSwitch::_nightTime[] PROGMEM = "nighttime-only";
const char PIRsensorSwitch::_mqttOnly[] PROGMEM = "mqtt-only";
const char PIRsensorSwitch::_offOnly[] PROGMEM = "off-only";