diff --git a/usermods/PIR_sensor_switch/readme.md b/usermods/PIR_sensor_switch/readme.md
index 15a8db08..b023cea5 100644
--- a/usermods/PIR_sensor_switch/readme.md
+++ b/usermods/PIR_sensor_switch/readme.md
@@ -23,44 +23,7 @@ You can also use usermod's off timer instead of sensor's. In such case rotate th
## Usermod installation
-1. Copy the file `usermod_PIR_sensor_switch.h` to the `wled00` directory.
-2. Register the usermod by adding `#include "usermod_PIR_sensor_switch.h"` in the top and `registerUsermod(new PIRsensorSwitch());` in the bottom of `usermods_list.cpp`.
-
-Example **usermods_list.cpp**:
-
-```cpp
-#include "wled.h"
-/*
- * Register your v2 usermods here!
- * (for v1 usermods using just usermod.cpp, you can ignore this file)
- */
-
-/*
- * Add/uncomment your usermod filename here (and once more below)
- * || || ||
- * \/ \/ \/
- */
-//#include "usermod_v2_example.h"
-//#include "usermod_temperature.h"
-//#include "usermod_v2_empty.h"
-#include "usermod_PIR_sensor_switch.h"
-
-void registerUsermods()
-{
- /*
- * Add your usermod class name here
- * || || ||
- * \/ \/ \/
- */
- //usermods.add(new MyExampleUsermod());
- //usermods.add(new UsermodTemperature());
- //usermods.add(new UsermodRenameMe());
- usermods.add(new PIRsensorSwitch());
-
-}
-```
-
-**NOTE:** Usermod has been included in master branch of WLED so it can be compiled in directly just by defining `-D USERMOD_PIRSWITCH` and optionaly `-D PIR_SENSOR_PIN=16` to override default pin.
+**NOTE:** Usermod has been included in master branch of WLED so it can be compiled in directly just by defining `-D USERMOD_PIRSWITCH` and optionaly `-D PIR_SENSOR_PIN=16` to override default pin. You can also change the default off tim by adding `-D PIR_SENSOR_OFF_SEC=30`.
## API to enable/disable the PIR sensor from outside. For example from another usermod.
@@ -121,4 +84,9 @@ Have fun - @gegu & @blazoncek
2021-11
* Added information about dynamic configuration options
-* Added option to temporary enable/disble usermod from WLED UI (Info dialog)
\ No newline at end of file
+* Added option to temporary enable/disble usermod from WLED UI (Info dialog)
+
+2022-11
+* Added compile time option for off timer.
+* Added Home Assistant autodiscovery MQTT broadcast.
+* Updated info on compiling.
\ No newline at end of file
diff --git a/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h b/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h
index 602c514f..b03a36ce 100644
--- a/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h
+++ b/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h
@@ -1,500 +1,505 @@
-#pragma once
-
-#include "wled.h"
-
-#ifndef PIR_SENSOR_PIN
- // compatible with QuinLED-Dig-Uno
- #ifdef ARDUINO_ARCH_ESP32
- #define PIR_SENSOR_PIN 23 // Q4
- #else //ESP8266 boards
- #define PIR_SENSOR_PIN 13 // Q4 (D7 on D1 mini)
- #endif
-#endif
-
-/*
- * This usermod handles PIR sensor states.
- * 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.
- *
- *
- * Usermods allow you to add own functionality to WLED more easily
- * 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.
- * Multiple v2 usermods can be added to one compilation easily.
- */
-
-class PIRsensorSwitch : public Usermod
-{
-public:
- // constructor
- PIRsensorSwitch() {}
- // destructor
- ~PIRsensorSwitch() {}
-
- //Enable/Disable the PIR sensor
- void EnablePIRsensor(bool en) { enabled = en; }
-
- // Get PIR sensor enabled/disabled state
- bool PIRsensorEnabled() { return enabled; }
-
-private:
-
- byte prevPreset = 0;
- byte prevPlaylist = 0;
-
- uint32_t offTimerStart = 0; // off timer start time
- byte NotifyUpdateMode = CALL_MODE_NO_NOTIFY; // notification mode for stateUpdated(): CALL_MODE_NO_NOTIFY or CALL_MODE_DIRECT_CHANGE
- byte sensorPinState = LOW; // current PIR sensor pin state
- bool initDone = false; // status of initialization
- bool PIRtriggered = false;
- unsigned long lastLoop = 0;
-
- // configurable parameters
- bool enabled = true; // PIR sensor enabled
- int8_t PIRsensorPin = PIR_SENSOR_PIN; // PIR sensor pin
- uint32_t m_switchOffDelay = 600000; // 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
- bool m_nightTimeOnly = false; // flag to indicate that PIR sensor should activate WLED during nighttime only
- bool m_mqttOnly = false; // flag to send MQTT message only (assuming it is enabled)
- // flag to enable triggering only if WLED is initially off (LEDs are not on, preventing running effect being overwritten by PIR)
- bool m_offOnly = false;
- bool m_offMode = offMode;
-
- // Home Assistant
- bool HomeAssistantDiscovery = false; // is HA discovery turned on
-
- // strings to reduce flash memory usage (used more than twice)
- static const char _name[];
- static const char _switchOffDelay[];
- static const char _enabled[];
- static const char _onPreset[];
- static const char _offPreset[];
- static const char _nightTime[];
- static const char _mqttOnly[];
- static const char _offOnly[];
- static const char _haDiscovery[];
- static const char _notify[];
-
- /**
- * check if it is daytime
- * if sunrise/sunset is not defined (no NTP or lat/lon) default to nighttime
- */
- bool isDayTime() {
- updateLocalTime();
- uint8_t hr = hour(localTime);
- uint8_t mi = minute(localTime);
-
- if (sunrise && sunset) {
- if (hour(sunrise)
hr) {
- return true;
- } else {
- if (hour(sunrise)==hr && minute(sunrise)mi) {
- return true;
- }
- }
- }
- return false;
- }
-
- /**
- * switch strip on/off
- */
- void switchStrip(bool switchOn)
- {
- if (m_offOnly && bri && (switchOn || (!PIRtriggered && !switchOn))) return; //if lights on and off only, do nothing
- if (PIRtriggered && switchOn) return; //if already on and triggered before, do nothing
- PIRtriggered = switchOn;
- if (switchOn) {
- if (m_onPreset) {
- if (currentPlaylist>0 && !offMode) {
- prevPlaylist = currentPlaylist;
- unloadPlaylist();
- } else if (currentPreset>0 && !offMode) {
- prevPreset = currentPreset;
- } else {
- saveTemporaryPreset();
- prevPlaylist = 0;
- prevPreset = 255;
- }
- applyPreset(m_onPreset, NotifyUpdateMode);
- return;
- }
- // preset not assigned
- if (bri == 0) {
- bri = briLast;
- stateUpdated(NotifyUpdateMode);
- }
- } else {
- if (m_offPreset) {
- if (currentPreset==m_onPreset || currentPlaylist==m_onPreset) applyPreset(m_offPreset, NotifyUpdateMode);
- return;
- } else if (prevPlaylist) {
- if (currentPreset==m_onPreset || currentPlaylist==m_onPreset) applyPreset(prevPlaylist, NotifyUpdateMode);
- prevPlaylist = 0;
- return;
- } else if (prevPreset) {
- if (prevPreset<255) { if (currentPreset==m_onPreset || currentPlaylist==m_onPreset) applyPreset(prevPreset, NotifyUpdateMode); }
- else { if (currentPreset==m_onPreset || currentPlaylist==m_onPreset) applyTemporaryPreset(); }
- prevPreset = 0;
- return;
- }
- // preset not assigned
- if (bri != 0) {
- briLast = bri;
- bri = 0;
- stateUpdated(NotifyUpdateMode);
- }
- }
- }
-
- void publishMqtt(const char* 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"));
- mqtt->publish(subuf, 0, false, state);
- }
- }
-
- // Create an MQTT Binary Sensor for Home Assistant Discovery purposes, this includes a pointer to the topic that is published to in the Loop.
- void publishHomeAssistantAutodiscovery()
- {
- if (WLED_MQTT_CONNECTED) {
- StaticJsonDocument<600> doc;
- char uid[24], json_str[1024], buf[128];
-
- sprintf_P(buf, PSTR("%s Motion"), serverDescription); //max length: 33 + 7 = 40
- doc[F("name")] = buf;
- sprintf_P(buf, PSTR("%s/motion"), mqttDeviceTopic); //max length: 33 + 7 = 40
- doc[F("stat_t")] = buf;
- doc[F("pl_on")] = "on";
- doc[F("pl_off")] = "off";
- sprintf_P(uid, PSTR("%s_motion"), escapedMac.c_str());
- doc[F("uniq_id")] = uid;
- doc[F("dev_cla")] = F("motion");
- doc[F("exp_aft")] = 1800;
-
- JsonObject device = doc.createNestedObject(F("device")); // attach the sensor to the same device
- device[F("name")] = serverDescription;
- device[F("ids")] = String(F("wled-sensor-")) + mqttClientID;
- device[F("mf")] = "WLED";
- device[F("mdl")] = F("FOSS");
- device[F("sw")] = versionString;
-
- sprintf_P(buf, PSTR("homeassistant/binary_sensor/%s/config"), uid);
- DEBUG_PRINTLN(buf);
- size_t payload_size = serializeJson(doc, json_str);
- DEBUG_PRINTLN(json_str);
-
- mqtt->publish(buf, 0, true, json_str, payload_size); // do we really need to retain?
- }
- }
-
- /**
- * Read and update PIR sensor state.
- * Initilize/reset switch off timer
- */
- bool updatePIRsensorState()
- {
- bool pinState = digitalRead(PIRsensorPin);
- if (pinState != sensorPinState) {
- sensorPinState = pinState; // change previous state
-
- if (sensorPinState == HIGH) {
- offTimerStart = 0;
- if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()))) switchStrip(true);
- else if (NotifyUpdateMode != CALL_MODE_NO_NOTIFY) updateInterfaces(CALL_MODE_WS_SEND);
- publishMqtt("on");
- } else {
- // start switch off timer
- offTimerStart = millis();
- if (NotifyUpdateMode != CALL_MODE_NO_NOTIFY) updateInterfaces(CALL_MODE_WS_SEND);
- }
- return true;
- }
- return false;
- }
-
- /**
- * switch off the strip if the delay has elapsed
- */
- bool handleOffTimer()
- {
- if (offTimerStart > 0 && millis() - offTimerStart > m_switchOffDelay) {
- offTimerStart = 0;
- if (enabled == true) {
- if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()))) switchStrip(false);
- else if (NotifyUpdateMode != CALL_MODE_NO_NOTIFY) updateInterfaces(CALL_MODE_WS_SEND);
- publishMqtt("off");
- }
- return true;
- }
- return false;
- }
-
-public:
- //Functions called by WLED
-
- /**
- * setup() is called once at boot. WiFi is not yet connected at this point.
- * You can use it to initialize variables, sensors or similar.
- */
- void setup()
- {
- if (enabled) {
- // 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);
- } else {
- if (PIRsensorPin >= 0) {
- DEBUG_PRINTLN(F("PIRSensorSwitch pin allocation failed."));
- }
- PIRsensorPin = -1; // allocation failed
- enabled = false;
- }
- }
- initDone = true;
- }
-
- /**
- * connected() is called every time the WiFi is (re)connected
- * Use it to initialize network interfaces
- */
- void connected()
- {
- }
-
- /**
- * onMqttConnect() is called when MQTT connection is established
- */
- void onMqttConnect(bool sessionPresent) {
- if (HomeAssistantDiscovery) {
- publishHomeAssistantAutodiscovery();
- }
- }
-
- /**
- * loop() is called continuously. Here you can check for events, read sensors, etc.
- */
- void loop()
- {
- // only check sensors 4x/s
- if (!enabled || millis() - lastLoop < 250 || strip.isUpdating()) return;
- lastLoop = millis();
-
- if (!updatePIRsensorState()) {
- handleOffTimer();
- }
- }
-
- /**
- * addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
- *
- * Add PIR sensor state and switch off timer duration to jsoninfo
- */
- void addToJsonInfo(JsonObject &root)
- {
- JsonObject user = root["u"];
- if (user.isNull()) user = root.createNestedObject("u");
-
- JsonArray infoArr = user.createNestedArray(FPSTR(_name));
-
- String uiDomString;
- if (enabled) {
- if (offTimerStart > 0)
- {
- uiDomString = "";
- unsigned int offSeconds = (m_switchOffDelay - (millis() - offTimerStart)) / 1000;
- if (offSeconds >= 3600)
- {
- uiDomString += (offSeconds / 3600);
- uiDomString += F("h ");
- offSeconds %= 3600;
- }
- if (offSeconds >= 60)
- {
- uiDomString += (offSeconds / 60);
- offSeconds %= 60;
- }
- else if (uiDomString.length() > 0)
- {
- uiDomString += 0;
- }
- if (uiDomString.length() > 0)
- {
- uiDomString += F("min ");
- }
- uiDomString += (offSeconds);
- infoArr.add(uiDomString + F("s"));
- } else {
- infoArr.add(sensorPinState ? F("sensor on") : F("inactive"));
- }
- } else {
- infoArr.add(F("disabled"));
- }
-
- 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;
- }
-
- /**
- * 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)
- {
- if (!initDone) return; // prevent crash on boot applyPreset()
- JsonObject usermod = root[FPSTR(_name)];
- if (!usermod.isNull()) {
- if (usermod[FPSTR(_enabled)].is()) {
- enabled = usermod[FPSTR(_enabled)].as();
- }
- }
- }
-
-
- /**
- * provide the changeable values
- */
- void addToConfig(JsonObject &root)
- {
- JsonObject top = root.createNestedObject(FPSTR(_name));
- top[FPSTR(_enabled)] = enabled;
- top[FPSTR(_switchOffDelay)] = m_switchOffDelay / 1000;
- top["pin"] = PIRsensorPin;
- top[FPSTR(_onPreset)] = m_onPreset;
- top[FPSTR(_offPreset)] = m_offPreset;
- top[FPSTR(_nightTime)] = m_nightTimeOnly;
- top[FPSTR(_mqttOnly)] = m_mqttOnly;
- top[FPSTR(_offOnly)] = m_offOnly;
- top[FPSTR(_haDiscovery)] = HomeAssistantDiscovery;
- top[FPSTR(_notify)] = (NotifyUpdateMode != CALL_MODE_NO_NOTIFY);
- DEBUG_PRINTLN(F("PIR config saved."));
- }
-
- void 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:notifications',1,'Periodic WS updates');")); // 0 is field type, 1 is actual field
- }
-
- /**
- * restore the changeable values
- * readFromConfig() is called before setup() to populate properties from values stored in cfg.json
- *
- * The function should return true if configuration was successfully loaded or false if there was no configuration.
- */
- bool readFromConfig(JsonObject &root)
- {
- bool oldEnabled = enabled;
- int8_t oldPin = PIRsensorPin;
-
- DEBUG_PRINT(FPSTR(_name));
- JsonObject top = root[FPSTR(_name)];
- if (top.isNull()) {
- DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
- return false;
- }
-
- PIRsensorPin = top["pin"] | PIRsensorPin;
-
- enabled = top[FPSTR(_enabled)] | enabled;
-
- m_switchOffDelay = (top[FPSTR(_switchOffDelay)] | m_switchOffDelay/1000) * 1000;
-
- m_onPreset = top[FPSTR(_onPreset)] | m_onPreset;
- m_onPreset = max(0,min(250,(int)m_onPreset));
- m_offPreset = top[FPSTR(_offPreset)] | m_offPreset;
- m_offPreset = max(0,min(250,(int)m_offPreset));
-
- m_nightTimeOnly = top[FPSTR(_nightTime)] | m_nightTimeOnly;
- m_mqttOnly = top[FPSTR(_mqttOnly)] | m_mqttOnly;
- m_offOnly = top[FPSTR(_offOnly)] | m_offOnly;
- HomeAssistantDiscovery = top[FPSTR(_haDiscovery)] | HomeAssistantDiscovery;
-
- NotifyUpdateMode = top[FPSTR(_notify)] ? CALL_MODE_DIRECT_CHANGE : CALL_MODE_NO_NOTIFY;
-
- if (!initDone) {
- // 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);
- }
- }
- DEBUG_PRINTLN(F(" config (re)loaded."));
- }
- // use "return !top["newestParameter"].isNull();" when updating Usermod with new features
- return !top[FPSTR(_haDiscovery)].isNull();
- }
-
- /**
- * 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.
- */
- uint16_t getId()
- {
- return USERMOD_ID_PIRSWITCH;
- }
-};
-
-// strings to reduce flash memory usage (used more than twice)
-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";
-const char PIRsensorSwitch::_haDiscovery[] PROGMEM = "HA-discovery";
-const char PIRsensorSwitch::_notify[] PROGMEM = "notifications";
+#pragma once
+
+#include "wled.h"
+
+#ifndef PIR_SENSOR_PIN
+ // compatible with QuinLED-Dig-Uno
+ #ifdef ARDUINO_ARCH_ESP32
+ #define PIR_SENSOR_PIN 23 // Q4
+ #else //ESP8266 boards
+ #define PIR_SENSOR_PIN 13 // Q4 (D7 on D1 mini)
+ #endif
+#endif
+
+#ifndef PIR_SENSOR_OFF_SEC
+ #define PIR_SENSOR_OFF_SEC 600
+#endif
+
+
+/*
+ * This usermod handles PIR sensor states.
+ * 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.
+ *
+ *
+ * Usermods allow you to add own functionality to WLED more easily
+ * 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.
+ * Multiple v2 usermods can be added to one compilation easily.
+ */
+
+class PIRsensorSwitch : public Usermod
+{
+public:
+ // constructor
+ PIRsensorSwitch() {}
+ // destructor
+ ~PIRsensorSwitch() {}
+
+ //Enable/Disable the PIR sensor
+ void EnablePIRsensor(bool en) { enabled = en; }
+
+ // Get PIR sensor enabled/disabled state
+ bool PIRsensorEnabled() { return enabled; }
+
+private:
+
+ byte prevPreset = 0;
+ byte prevPlaylist = 0;
+
+ uint32_t offTimerStart = 0; // off timer start time
+ byte NotifyUpdateMode = CALL_MODE_NO_NOTIFY; // notification mode for stateUpdated(): CALL_MODE_NO_NOTIFY or CALL_MODE_DIRECT_CHANGE
+ byte sensorPinState = LOW; // current PIR sensor pin state
+ bool initDone = false; // status of initialization
+ bool PIRtriggered = false;
+ unsigned long lastLoop = 0;
+
+ // configurable parameters
+ bool enabled = true; // PIR sensor enabled
+ int8_t PIRsensorPin = 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
+ bool m_nightTimeOnly = false; // flag to indicate that PIR sensor should activate WLED during nighttime only
+ bool m_mqttOnly = false; // flag to send MQTT message only (assuming it is enabled)
+ // flag to enable triggering only if WLED is initially off (LEDs are not on, preventing running effect being overwritten by PIR)
+ bool m_offOnly = false;
+ bool m_offMode = offMode;
+
+ // Home Assistant
+ bool HomeAssistantDiscovery = false; // is HA discovery turned on
+
+ // strings to reduce flash memory usage (used more than twice)
+ static const char _name[];
+ static const char _switchOffDelay[];
+ static const char _enabled[];
+ static const char _onPreset[];
+ static const char _offPreset[];
+ static const char _nightTime[];
+ static const char _mqttOnly[];
+ static const char _offOnly[];
+ static const char _haDiscovery[];
+ static const char _notify[];
+
+ /**
+ * check if it is daytime
+ * if sunrise/sunset is not defined (no NTP or lat/lon) default to nighttime
+ */
+ bool isDayTime() {
+ updateLocalTime();
+ uint8_t hr = hour(localTime);
+ uint8_t mi = minute(localTime);
+
+ if (sunrise && sunset) {
+ if (hour(sunrise)hr) {
+ return true;
+ } else {
+ if (hour(sunrise)==hr && minute(sunrise)mi) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * switch strip on/off
+ */
+ void switchStrip(bool switchOn)
+ {
+ if (m_offOnly && bri && (switchOn || (!PIRtriggered && !switchOn))) return; //if lights on and off only, do nothing
+ if (PIRtriggered && switchOn) return; //if already on and triggered before, do nothing
+ PIRtriggered = switchOn;
+ if (switchOn) {
+ if (m_onPreset) {
+ if (currentPlaylist>0 && !offMode) {
+ prevPlaylist = currentPlaylist;
+ unloadPlaylist();
+ } else if (currentPreset>0 && !offMode) {
+ prevPreset = currentPreset;
+ } else {
+ saveTemporaryPreset();
+ prevPlaylist = 0;
+ prevPreset = 255;
+ }
+ applyPreset(m_onPreset, NotifyUpdateMode);
+ return;
+ }
+ // preset not assigned
+ if (bri == 0) {
+ bri = briLast;
+ stateUpdated(NotifyUpdateMode);
+ }
+ } else {
+ if (m_offPreset) {
+ if (currentPreset==m_onPreset || currentPlaylist==m_onPreset) applyPreset(m_offPreset, NotifyUpdateMode);
+ return;
+ } else if (prevPlaylist) {
+ if (currentPreset==m_onPreset || currentPlaylist==m_onPreset) applyPreset(prevPlaylist, NotifyUpdateMode);
+ prevPlaylist = 0;
+ return;
+ } else if (prevPreset) {
+ if (prevPreset<255) { if (currentPreset==m_onPreset || currentPlaylist==m_onPreset) applyPreset(prevPreset, NotifyUpdateMode); }
+ else { if (currentPreset==m_onPreset || currentPlaylist==m_onPreset) applyTemporaryPreset(); }
+ prevPreset = 0;
+ return;
+ }
+ // preset not assigned
+ if (bri != 0) {
+ briLast = bri;
+ bri = 0;
+ stateUpdated(NotifyUpdateMode);
+ }
+ }
+ }
+
+ void publishMqtt(const char* 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"));
+ mqtt->publish(subuf, 0, false, state);
+ }
+ }
+
+ // Create an MQTT Binary Sensor for Home Assistant Discovery purposes, this includes a pointer to the topic that is published to in the Loop.
+ void publishHomeAssistantAutodiscovery()
+ {
+ if (WLED_MQTT_CONNECTED) {
+ StaticJsonDocument<600> doc;
+ char uid[24], json_str[1024], buf[128];
+
+ sprintf_P(buf, PSTR("%s Motion"), serverDescription); //max length: 33 + 7 = 40
+ doc[F("name")] = buf;
+ sprintf_P(buf, PSTR("%s/motion"), mqttDeviceTopic); //max length: 33 + 7 = 40
+ doc[F("stat_t")] = buf;
+ doc[F("pl_on")] = "on";
+ doc[F("pl_off")] = "off";
+ sprintf_P(uid, PSTR("%s_motion"), escapedMac.c_str());
+ doc[F("uniq_id")] = uid;
+ doc[F("dev_cla")] = F("motion");
+ doc[F("exp_aft")] = 1800;
+
+ JsonObject device = doc.createNestedObject(F("device")); // attach the sensor to the same device
+ device[F("name")] = serverDescription;
+ device[F("ids")] = String(F("wled-sensor-")) + mqttClientID;
+ device[F("mf")] = "WLED";
+ device[F("mdl")] = F("FOSS");
+ device[F("sw")] = versionString;
+
+ sprintf_P(buf, PSTR("homeassistant/binary_sensor/%s/config"), uid);
+ DEBUG_PRINTLN(buf);
+ size_t payload_size = serializeJson(doc, json_str);
+ DEBUG_PRINTLN(json_str);
+
+ mqtt->publish(buf, 0, true, json_str, payload_size); // do we really need to retain?
+ }
+ }
+
+ /**
+ * Read and update PIR sensor state.
+ * Initilize/reset switch off timer
+ */
+ bool updatePIRsensorState()
+ {
+ bool pinState = digitalRead(PIRsensorPin);
+ if (pinState != sensorPinState) {
+ sensorPinState = pinState; // change previous state
+
+ if (sensorPinState == HIGH) {
+ offTimerStart = 0;
+ if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()))) switchStrip(true);
+ else if (NotifyUpdateMode != CALL_MODE_NO_NOTIFY) updateInterfaces(CALL_MODE_WS_SEND);
+ publishMqtt("on");
+ } else {
+ // start switch off timer
+ offTimerStart = millis();
+ if (NotifyUpdateMode != CALL_MODE_NO_NOTIFY) updateInterfaces(CALL_MODE_WS_SEND);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * switch off the strip if the delay has elapsed
+ */
+ bool handleOffTimer()
+ {
+ if (offTimerStart > 0 && millis() - offTimerStart > m_switchOffDelay) {
+ offTimerStart = 0;
+ if (enabled == true) {
+ if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()))) switchStrip(false);
+ else if (NotifyUpdateMode != CALL_MODE_NO_NOTIFY) updateInterfaces(CALL_MODE_WS_SEND);
+ publishMqtt("off");
+ }
+ return true;
+ }
+ return false;
+ }
+
+public:
+ //Functions called by WLED
+
+ /**
+ * setup() is called once at boot. WiFi is not yet connected at this point.
+ * You can use it to initialize variables, sensors or similar.
+ */
+ void setup()
+ {
+ if (enabled) {
+ // 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);
+ } else {
+ if (PIRsensorPin >= 0) {
+ DEBUG_PRINTLN(F("PIRSensorSwitch pin allocation failed."));
+ }
+ PIRsensorPin = -1; // allocation failed
+ enabled = false;
+ }
+ }
+ initDone = true;
+ }
+
+ /**
+ * connected() is called every time the WiFi is (re)connected
+ * Use it to initialize network interfaces
+ */
+ void connected()
+ {
+ }
+
+ /**
+ * onMqttConnect() is called when MQTT connection is established
+ */
+ void onMqttConnect(bool sessionPresent) {
+ if (HomeAssistantDiscovery) {
+ publishHomeAssistantAutodiscovery();
+ }
+ }
+
+ /**
+ * loop() is called continuously. Here you can check for events, read sensors, etc.
+ */
+ void loop()
+ {
+ // only check sensors 4x/s
+ if (!enabled || millis() - lastLoop < 250 || strip.isUpdating()) return;
+ lastLoop = millis();
+
+ if (!updatePIRsensorState()) {
+ handleOffTimer();
+ }
+ }
+
+ /**
+ * addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
+ *
+ * Add PIR sensor state and switch off timer duration to jsoninfo
+ */
+ void addToJsonInfo(JsonObject &root)
+ {
+ JsonObject user = root["u"];
+ if (user.isNull()) user = root.createNestedObject("u");
+
+ JsonArray infoArr = user.createNestedArray(FPSTR(_name));
+
+ String uiDomString;
+ if (enabled) {
+ if (offTimerStart > 0)
+ {
+ uiDomString = "";
+ unsigned int offSeconds = (m_switchOffDelay - (millis() - offTimerStart)) / 1000;
+ if (offSeconds >= 3600)
+ {
+ uiDomString += (offSeconds / 3600);
+ uiDomString += F("h ");
+ offSeconds %= 3600;
+ }
+ if (offSeconds >= 60)
+ {
+ uiDomString += (offSeconds / 60);
+ offSeconds %= 60;
+ }
+ else if (uiDomString.length() > 0)
+ {
+ uiDomString += 0;
+ }
+ if (uiDomString.length() > 0)
+ {
+ uiDomString += F("min ");
+ }
+ uiDomString += (offSeconds);
+ infoArr.add(uiDomString + F("s"));
+ } else {
+ infoArr.add(sensorPinState ? F("sensor on") : F("inactive"));
+ }
+ } else {
+ infoArr.add(F("disabled"));
+ }
+
+ 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;
+ }
+
+ /**
+ * 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)
+ {
+ if (!initDone) return; // prevent crash on boot applyPreset()
+ JsonObject usermod = root[FPSTR(_name)];
+ if (!usermod.isNull()) {
+ if (usermod[FPSTR(_enabled)].is()) {
+ enabled = usermod[FPSTR(_enabled)].as();
+ }
+ }
+ }
+
+
+ /**
+ * provide the changeable values
+ */
+ void addToConfig(JsonObject &root)
+ {
+ JsonObject top = root.createNestedObject(FPSTR(_name));
+ top[FPSTR(_enabled)] = enabled;
+ top[FPSTR(_switchOffDelay)] = m_switchOffDelay / 1000;
+ top["pin"] = PIRsensorPin;
+ top[FPSTR(_onPreset)] = m_onPreset;
+ top[FPSTR(_offPreset)] = m_offPreset;
+ top[FPSTR(_nightTime)] = m_nightTimeOnly;
+ top[FPSTR(_mqttOnly)] = m_mqttOnly;
+ top[FPSTR(_offOnly)] = m_offOnly;
+ top[FPSTR(_haDiscovery)] = HomeAssistantDiscovery;
+ top[FPSTR(_notify)] = (NotifyUpdateMode != CALL_MODE_NO_NOTIFY);
+ DEBUG_PRINTLN(F("PIR config saved."));
+ }
+
+ void 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:notifications',1,'Periodic WS updates');")); // 0 is field type, 1 is actual field
+ }
+
+ /**
+ * restore the changeable values
+ * readFromConfig() is called before setup() to populate properties from values stored in cfg.json
+ *
+ * The function should return true if configuration was successfully loaded or false if there was no configuration.
+ */
+ bool readFromConfig(JsonObject &root)
+ {
+ bool oldEnabled = enabled;
+ int8_t oldPin = PIRsensorPin;
+
+ DEBUG_PRINT(FPSTR(_name));
+ JsonObject top = root[FPSTR(_name)];
+ if (top.isNull()) {
+ DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
+ return false;
+ }
+
+ PIRsensorPin = top["pin"] | PIRsensorPin;
+
+ enabled = top[FPSTR(_enabled)] | enabled;
+
+ m_switchOffDelay = (top[FPSTR(_switchOffDelay)] | m_switchOffDelay/1000) * 1000;
+
+ m_onPreset = top[FPSTR(_onPreset)] | m_onPreset;
+ m_onPreset = max(0,min(250,(int)m_onPreset));
+ m_offPreset = top[FPSTR(_offPreset)] | m_offPreset;
+ m_offPreset = max(0,min(250,(int)m_offPreset));
+
+ m_nightTimeOnly = top[FPSTR(_nightTime)] | m_nightTimeOnly;
+ m_mqttOnly = top[FPSTR(_mqttOnly)] | m_mqttOnly;
+ m_offOnly = top[FPSTR(_offOnly)] | m_offOnly;
+ HomeAssistantDiscovery = top[FPSTR(_haDiscovery)] | HomeAssistantDiscovery;
+
+ NotifyUpdateMode = top[FPSTR(_notify)] ? CALL_MODE_DIRECT_CHANGE : CALL_MODE_NO_NOTIFY;
+
+ if (!initDone) {
+ // 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);
+ }
+ }
+ DEBUG_PRINTLN(F(" config (re)loaded."));
+ }
+ // use "return !top["newestParameter"].isNull();" when updating Usermod with new features
+ return !top[FPSTR(_haDiscovery)].isNull();
+ }
+
+ /**
+ * 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.
+ */
+ uint16_t getId()
+ {
+ return USERMOD_ID_PIRSWITCH;
+ }
+};
+
+// strings to reduce flash memory usage (used more than twice)
+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";
+const char PIRsensorSwitch::_haDiscovery[] PROGMEM = "HA-discovery";
+const char PIRsensorSwitch::_notify[] PROGMEM = "notifications";