Added PIR sensor night time presets.

Added PIR sensor Disable/Enable button.
UI refinements.
Added colorUpdated() on HTTP API in IR JSON.
This commit is contained in:
Blaz Kristan 2021-09-28 23:27:40 +02:00
parent 9092549f07
commit dc9d48850f
8 changed files with 2298 additions and 2233 deletions

View File

@ -72,6 +72,9 @@ private:
// 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;
// nighttime presets (optional)
uint8_t m_onNighttime = 0;
uint8_t m_offNighttime = 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)
@ -88,6 +91,8 @@ private:
static const char _enabled[]; static const char _enabled[];
static const char _onPreset[]; static const char _onPreset[];
static const char _offPreset[]; static const char _offPreset[];
static const char _onNighttime[];
static const char _offNighttime[];
static const char _nightTime[]; static const char _nightTime[];
static const char _mqttOnly[]; static const char _mqttOnly[];
static const char _offOnly[]; static const char _offOnly[];
@ -97,24 +102,23 @@ private:
* if sunrise/sunset is not defined (no NTP or lat/lon) default to nighttime * if sunrise/sunset is not defined (no NTP or lat/lon) default to nighttime
*/ */
bool isDayTime() { bool isDayTime() {
bool isDayTime = false;
updateLocalTime(); updateLocalTime();
uint8_t hr = hour(localTime); uint8_t hr = hour(localTime);
uint8_t mi = minute(localTime); uint8_t mi = minute(localTime);
if (sunrise && sunset) { if (sunrise && sunset) {
if (hour(sunrise)<hr && hour(sunset)>hr) { if (hour(sunrise)<hr && hour(sunset)>hr) {
isDayTime = true; return true;
} else { } else {
if (hour(sunrise)==hr && minute(sunrise)<mi) { if (hour(sunrise)==hr && minute(sunrise)<mi) {
isDayTime = true; return true;
} }
if (hour(sunset)==hr && minute(sunset)>mi) { if (hour(sunset)==hr && minute(sunset)>mi) {
isDayTime = true; return true;
} }
} }
} }
return isDayTime; return false;
} }
/** /**
@ -124,17 +128,33 @@ private:
{ {
if (m_offOnly && bri && (switchOn || (!PIRtriggered && !switchOn))) return; if (m_offOnly && bri && (switchOn || (!PIRtriggered && !switchOn))) return;
PIRtriggered = switchOn; PIRtriggered = switchOn;
if (switchOn && m_onPreset) { if (switchOn) {
applyPreset(m_onPreset); if (m_onNighttime && !isDayTime()) {
} else if (!switchOn && m_offPreset) { applyPreset(m_onNighttime);
applyPreset(m_offPreset); return;
} else if (switchOn && bri == 0) { } else if (m_onPreset) {
bri = briLast; applyPreset(m_onPreset);
colorUpdated(NotifyUpdateMode); return;
} else if (!switchOn && bri != 0) { }
briLast = bri; // preset not assigned
bri = 0; if (bri == 0) {
colorUpdated(NotifyUpdateMode); bri = briLast;
colorUpdated(NotifyUpdateMode);
}
} else {
if (m_offNighttime && !isDayTime()) {
applyPreset(m_offNighttime);
return;
} else if (m_offPreset) {
applyPreset(m_offPreset);
return;
}
// preset not assigned
if (bri != 0) {
briLast = bri;
bri = 0;
colorUpdated(NotifyUpdateMode);
}
} }
} }
@ -248,11 +268,21 @@ public:
JsonObject user = root["u"]; JsonObject user = root["u"];
if (user.isNull()) user = root.createNestedObject("u"); if (user.isNull()) user = root.createNestedObject("u");
if (enabled) String uiDomString = F("<button class=\"btn\" onclick=\"requestJson({");
{ uiDomString += FPSTR(_name);
// off timer uiDomString += F(":{");
String uiDomString = F("PIR <i class=\"icons\">&#xe325;</i>"); uiDomString += FPSTR(_enabled);
JsonArray infoArr = user.createNestedArray(uiDomString); // timer value if (enabled) {
uiDomString += F(":false}});loadInfo();\">");
uiDomString += F("PIR <i class=\"icons\">&#xe325;</i>");
} else {
uiDomString += F(":true}});loadInfo();\">");
uiDomString += F("PIR <i class=\"icons\">&#xe08f;</i>");
}
uiDomString += F("</button>");
JsonArray infoArr = user.createNestedArray(uiDomString); // timer value
if (enabled) {
if (m_offTimerStart > 0) if (m_offTimerStart > 0)
{ {
uiDomString = ""; uiDomString = "";
@ -282,8 +312,6 @@ public:
infoArr.add(sensorPinState ? F("sensor on") : F("inactive")); infoArr.add(sensorPinState ? F("sensor on") : F("inactive"));
} }
} else { } else {
String uiDomString = F("PIR sensor");
JsonArray infoArr = user.createNestedArray(uiDomString);
infoArr.add(F("disabled")); infoArr.add(F("disabled"));
} }
} }
@ -302,11 +330,18 @@ public:
* readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). * 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 * Values in the state object may be modified by connected clients
*/ */
/*
void readFromJsonState(JsonObject &root) 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<bool>()) {
enabled = usermod[FPSTR(_enabled)].as<bool>();
}
}
} }
*/
/** /**
* provide the changeable values * provide the changeable values
@ -314,14 +349,16 @@ public:
void addToConfig(JsonObject &root) void 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; top["pin"] = PIRsensorPin;
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(_onNighttime)] = m_onNighttime;
top[FPSTR(_mqttOnly)] = m_mqttOnly; top[FPSTR(_offNighttime)] = m_offNighttime;
top[FPSTR(_offOnly)] = m_offOnly; top[FPSTR(_nightTime)] = m_nightTimeOnly;
top[FPSTR(_mqttOnly)] = m_mqttOnly;
top[FPSTR(_offOnly)] = m_offOnly;
DEBUG_PRINTLN(F("PIR config saved.")); DEBUG_PRINTLN(F("PIR config saved."));
} }
@ -336,9 +373,9 @@ public:
bool oldEnabled = enabled; bool oldEnabled = enabled;
int8_t oldPin = PIRsensorPin; int8_t oldPin = PIRsensorPin;
DEBUG_PRINT(FPSTR(_name));
JsonObject top = root[FPSTR(_name)]; JsonObject top = root[FPSTR(_name)];
if (top.isNull()) { if (top.isNull()) {
DEBUG_PRINT(FPSTR(_name));
DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
return false; return false;
} }
@ -351,15 +388,18 @@ public:
m_onPreset = top[FPSTR(_onPreset)] | m_onPreset; m_onPreset = top[FPSTR(_onPreset)] | m_onPreset;
m_onPreset = max(0,min(250,(int)m_onPreset)); m_onPreset = max(0,min(250,(int)m_onPreset));
m_offPreset = top[FPSTR(_offPreset)] | m_offPreset; m_offPreset = top[FPSTR(_offPreset)] | m_offPreset;
m_offPreset = max(0,min(250,(int)m_offPreset)); m_offPreset = max(0,min(250,(int)m_offPreset));
m_onNighttime = top[FPSTR(_onNighttime)] | m_onNighttime;
m_onNighttime = max(0,min(250,(int)m_onNighttime));
m_offNighttime = top[FPSTR(_offNighttime)] | m_offNighttime;
m_offNighttime = max(0,min(250,(int)m_offNighttime));
m_nightTimeOnly = top[FPSTR(_nightTime)] | m_nightTimeOnly; m_nightTimeOnly = top[FPSTR(_nightTime)] | m_nightTimeOnly;
m_mqttOnly = top[FPSTR(_mqttOnly)] | m_mqttOnly; m_mqttOnly = top[FPSTR(_mqttOnly)] | m_mqttOnly;
m_offOnly = top[FPSTR(_offOnly)] | m_offOnly; m_offOnly = top[FPSTR(_offOnly)] | m_offOnly;
DEBUG_PRINT(FPSTR(_name));
if (!initDone) { if (!initDone) {
// reading config prior to setup() // reading config prior to setup()
DEBUG_PRINTLN(F(" config loaded.")); DEBUG_PRINTLN(F(" config loaded."));
@ -385,7 +425,7 @@ public:
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(_offOnly)].isNull(); return !top[FPSTR(_onNighttime)].isNull();
} }
/** /**
@ -404,6 +444,8 @@ const char PIRsensorSwitch::_enabled[] PROGMEM = "PIRenabled";
const char PIRsensorSwitch::_switchOffDelay[] PROGMEM = "PIRoffSec"; const char PIRsensorSwitch::_switchOffDelay[] PROGMEM = "PIRoffSec";
const char PIRsensorSwitch::_onPreset[] PROGMEM = "on-preset"; const char PIRsensorSwitch::_onPreset[] PROGMEM = "on-preset";
const char PIRsensorSwitch::_offPreset[] PROGMEM = "off-preset"; const char PIRsensorSwitch::_offPreset[] PROGMEM = "off-preset";
const char PIRsensorSwitch::_onNighttime[] PROGMEM = "on-nighttime";
const char PIRsensorSwitch::_offNighttime[] PROGMEM = "off-nighttime";
const char PIRsensorSwitch::_nightTime[] PROGMEM = "nighttime-only"; const char PIRsensorSwitch::_nightTime[] PROGMEM = "nighttime-only";
const char PIRsensorSwitch::_mqttOnly[] PROGMEM = "mqtt-only"; const char PIRsensorSwitch::_mqttOnly[] PROGMEM = "mqtt-only";
const char PIRsensorSwitch::_offOnly[] PROGMEM = "off-only"; const char PIRsensorSwitch::_offOnly[] PROGMEM = "off-only";

View File

@ -128,11 +128,6 @@ button {
font-size: 42px; font-size: 42px;
} }
.infot {
table-layout: fixed;
width: 100%;
}
.segt, .plentry TABLE { .segt, .plentry TABLE {
table-layout: fixed; table-layout: fixed;
width: 100%; width: 100%;
@ -455,14 +450,24 @@ button {
} }
#kv, #kn { #kv, #kn {
max-width: 490px; /*max-width: 490px;*/
display: inline-block; display: inline-block;
} }
#kv td { #info table, #nodes table {
table-layout: fixed;
width: 490px;
margin: auto;
}
#info td, #nodes td {
padding-bottom: 8px; padding-bottom: 8px;
} }
#info .btn, #nodes .btn {
margin: 0;
}
#lv { #lv {
max-width: 600px; max-width: 600px;
display: inline-block; display: inline-block;
@ -1130,14 +1135,17 @@ input[type="text"].fnd:hover {
@media all and (max-width: 335px) { @media all and (max-width: 335px) {
.sliderbubble { .sliderbubble {
display: none; display: none;
} }
} }
@media all and (max-width: 550px) and (min-width: 374px) { @media all and (max-width: 550px) and (min-width: 374px) {
.infobtn { #info .btn, #nodes .btn {
width: 155px; width: 155px;
} }
#info table, #nodes table {
width: 320px;
}
} }
@media all and (max-width: 540px) { @media all and (max-width: 540px) {

View File

@ -201,18 +201,28 @@
<img class="wi" alt="" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB0AAAAFCAYAAAC5Fuf5AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAABbSURBVChTlY9bDoAwDMNW7n9nwCipytQN4Z8tbrTHmDmF4oPzyldwRqp1SSdnV/NuZuzqerAByxXznBw3igkeFEfXyUuhK/yFM0CxJfyqXZEOc6/Sr9/bf7uIC5Nwd7orMvAPAAAAAElFTkSuQmCC" /> <img class="wi" alt="" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB0AAAAFCAYAAAC5Fuf5AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAABbSURBVChTlY9bDoAwDMNW7n9nwCipytQN4Z8tbrTHmDmF4oPzyldwRqp1SSdnV/NuZuzqerAByxXznBw3igkeFEfXyUuhK/yFM0CxJfyqXZEOc6/Sr9/bf7uIC5Nwd7orMvAPAAAAAElFTkSuQmCC" />
</div><br> </div><br>
<div id="kv">Loading...</div><br> <div id="kv">Loading...</div><br>
<button class="btn infobtn" onclick="loadInfo()">Refresh</button> <table>
<button class="btn infobtn" onclick="toggleInfo()">Close Info</button><br> <tr>
<button class="btn infobtn" onclick="toggleNodes()">Instance List</button> <td class="keytd"><button class="btn" onclick="loadInfo()">Refresh</button></td>
<button class="btn infobtn" id="resetbtn" onclick="cnfReset()">Reboot WLED</button><br> <td class="valtd"><button class="btn" onclick="toggleInfo()">Close Info</button></td>
</tr>
<tr>
<td class="keytd"><button class="btn" onclick="toggleNodes()">Instance List</button></td>
<td class="valtd"><button class="btn" id="resetbtn" onclick="cnfReset()">Reboot WLED</button></td>
</tr>
</table><br>
<span class="h">Made with <span id="heart">&#10084;&#xFE0E;</span> by Aircoookie and the WLED community</span> <span class="h">Made with <span id="heart">&#10084;&#xFE0E;</span> by Aircoookie and the WLED community</span>
</div> </div>
<div id="nodes" class="modal"> <div id="nodes" class="modal">
<div id="ndlt">WLED instances</div> <div id="ndlt">WLED instances</div>
<div id="kn">Loading...</div><br> <div id="kn">Loading...</div><br>
<button class="btn infobtn" onclick="loadNodes()">Refresh</button> <table>
<button class="btn infobtn" onclick="toggleNodes()">Close list</button><br> <tr>
<td class="keytd"><button class="btn infobtn" onclick="loadNodes()">Refresh</button></td>
<td class="valtd"><button class="btn infobtn" onclick="toggleNodes()">Close list</button></td>
</tr>
</table><br>
</div> </div>
<div id="rover" class="modal"> <div id="rover" class="modal">

View File

@ -629,7 +629,7 @@ function populateInfo(i)
if (i.ver.includes("-bl")) vcn = "Ryujin"; if (i.ver.includes("-bl")) vcn = "Ryujin";
if (i.cn) vcn = i.cn; if (i.cn) vcn = i.cn;
cn += `v${i.ver} "${vcn}"<br><br><table class="infot"> cn += `v${i.ver} "${vcn}"<br><br><table>
${urows} ${urows}
${inforow("Build",i.vid)} ${inforow("Build",i.vid)}
${inforow("Signal strength",i.wifi.signal +"% ("+ i.wifi.rssi, " dBm)")} ${inforow("Signal strength",i.wifi.signal +"% ("+ i.wifi.rssi, " dBm)")}
@ -760,7 +760,7 @@ function populateNodes(i,n)
for (var x=0;x<n.nodes.length;x++) { for (var x=0;x<n.nodes.length;x++) {
var o = n.nodes[x]; var o = n.nodes[x];
if (o.name) { if (o.name) {
var url = `<button class="btn infobtn" title="${o.ip}" onclick="location.assign('http://${o.ip}');">${bname(o)}</button>`; var url = `<button class="btn" title="${o.ip}" onclick="location.assign('http://${o.ip}');">${bname(o)}</button>`;
urows += inforow(url,`${btype(o.type)}<br><i>${o.vid==0?"N/A":o.vid}</i>`); urows += inforow(url,`${btype(o.type)}<br><i>${o.vid==0?"N/A":o.vid}</i>`);
nnodes++; nnodes++;
} }
@ -768,9 +768,9 @@ function populateNodes(i,n)
} }
if (i.ndc < 0) cn += `Instance List is disabled.`; if (i.ndc < 0) cn += `Instance List is disabled.`;
else if (nnodes == 0) cn += `No other instances found.`; else if (nnodes == 0) cn += `No other instances found.`;
cn += `<table class="infot"> cn += `<table>
${urows}
${inforow("Current instance:",i.name)} ${inforow("Current instance:",i.name)}
${urows}
</table>`; </table>`;
gId('kn').innerHTML = cn; gId('kn').innerHTML = cn;
} }

File diff suppressed because it is too large Load Diff

View File

@ -625,6 +625,7 @@ void decodeIRJson(uint32_t code)
cmdStr = "win&" + cmdStr; cmdStr = "win&" + cmdStr;
} }
handleSet(nullptr, cmdStr, false); handleSet(nullptr, cmdStr, false);
colorUpdated(CALL_MODE_BUTTON);
} }
} else if (!jsonCmdObj.isNull()) { } else if (!jsonCmdObj.isNull()) {
// command is JSON object // command is JSON object

View File

@ -713,12 +713,10 @@ void WLED::initInterfaces()
// Set up mDNS responder: // Set up mDNS responder:
if (strlen(cmDNS) > 0) { if (strlen(cmDNS) > 0) {
#ifndef WLED_DISABLE_OTA // "end" must be called before "begin" is called a 2nd time
if (!aOtaEnabled) //ArduinoOTA begins mDNS for us if enabled // see https://github.com/esp8266/Arduino/issues/7213
MDNS.begin(cmDNS); MDNS.end();
#else
MDNS.begin(cmDNS); MDNS.begin(cmDNS);
#endif
DEBUG_PRINTLN(F("mDNS started")); DEBUG_PRINTLN(F("mDNS started"));
MDNS.addService("http", "tcp", 80); MDNS.addService("http", "tcp", 80);

View File

@ -8,7 +8,7 @@
*/ */
// version code in format yymmddb (b = daily build) // version code in format yymmddb (b = daily build)
#define VERSION 2109261 #define VERSION 2109281
//uncomment this if you have a "my_config.h" file you'd like to use //uncomment this if you have a "my_config.h" file you'd like to use
//#define WLED_USE_MY_CONFIG //#define WLED_USE_MY_CONFIG
@ -87,6 +87,7 @@
#include <WiFiUdp.h> #include <WiFiUdp.h>
#include <DNSServer.h> #include <DNSServer.h>
#ifndef WLED_DISABLE_OTA #ifndef WLED_DISABLE_OTA
#define NO_OTA_PORT
#include <ArduinoOTA.h> #include <ArduinoOTA.h>
#endif #endif
#include <SPIFFSEditor.h> #include <SPIFFSEditor.h>