User configurable usermods.
- Utilizing addToConfig() & readFromConfig() - UM configuration Settings page - MQTT support for usermods - A few sample usermods including rewritten Temparature and new Multi-Relay
This commit is contained in:
parent
fa075f6800
commit
136a00a301
@ -333,6 +333,18 @@ const char PAGE_settings_dmx[] PROGMEM = R"=====()=====";
|
|||||||
"function GetV() {var d=document;\n"
|
"function GetV() {var d=document;\n"
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
file: "settings_um.htm",
|
||||||
|
name: "PAGE_settings_um",
|
||||||
|
prepend: "=====(",
|
||||||
|
append: ")=====",
|
||||||
|
method: "plaintext",
|
||||||
|
filter: "html-minify",
|
||||||
|
mangle: (str) =>
|
||||||
|
str
|
||||||
|
.replace(/\<link rel="stylesheet".*\>/gms, "")
|
||||||
|
.replace(/\<style\>.*\<\/style\>/gms, "%CSS%%SCSS%")
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"wled00/html_settings.h"
|
"wled00/html_settings.h"
|
||||||
);
|
);
|
||||||
|
@ -9,25 +9,31 @@
|
|||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "wled.h"
|
#include "wled.h"
|
||||||
#include "Animated_Staircase_config.h"
|
|
||||||
#define USERMOD_ID_ANIMATED_STAIRCASE 1011
|
#define USERMOD_ID_ANIMATED_STAIRCASE 1011
|
||||||
|
|
||||||
/* Initial configuration (available in API and stored in flash) */
|
|
||||||
bool enabled = true; // Enable this usermod
|
|
||||||
unsigned long segment_delay_ms = 150; // Time between switching each segment
|
|
||||||
unsigned long on_time_ms = 5 * 1000; // The time for the light to stay on
|
|
||||||
#ifndef TOP_PIR_PIN
|
|
||||||
unsigned int topMaxTimeUs = 1749; // default echo timout, top
|
|
||||||
#endif
|
|
||||||
#ifndef BOTTOM_PIR_PIN
|
|
||||||
unsigned int bottomMaxTimeUs = 1749; // default echo timout, bottom
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Time between checking of the sensors
|
|
||||||
const int scanDelay = 50;
|
|
||||||
|
|
||||||
class Animated_Staircase : public Usermod {
|
class Animated_Staircase : public Usermod {
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
/* configuration (available in API and stored in flash) */
|
||||||
|
bool enabled = false; // Enable this usermod
|
||||||
|
unsigned long segment_delay_ms = 150; // Time between switching each segment
|
||||||
|
unsigned long on_time_ms = 5 * 1000; // The time for the light to stay on
|
||||||
|
int8_t topPIRorTriggerPin = -1; // disabled
|
||||||
|
int8_t bottomPIRorTriggerPin = -1; // disabled
|
||||||
|
int8_t topEchoPin = -1; // disabled
|
||||||
|
int8_t bottomEchoPin = -1; // disabled
|
||||||
|
bool useUSSensorTop = false; // using PIR or UltraSound sensor?
|
||||||
|
bool useUSSensorBottom = false; // using PIR or UltraSound sensor?
|
||||||
|
unsigned int topMaxTimeUs = 1749; // default echo timout, top
|
||||||
|
unsigned int bottomMaxTimeUs = 1749; // default echo timout, bottom
|
||||||
|
|
||||||
|
/* runtime variables */
|
||||||
|
bool initDone = false;
|
||||||
|
|
||||||
|
// Time between checking of the sensors
|
||||||
|
const unsigned int scanDelay = 50;
|
||||||
|
|
||||||
// Lights on or off.
|
// Lights on or off.
|
||||||
// Flipping this will start a transition.
|
// Flipping this will start a transition.
|
||||||
bool on = false;
|
bool on = false;
|
||||||
@ -63,8 +69,6 @@ class Animated_Staircase : public Usermod {
|
|||||||
byte maxSegmentId = 1;
|
byte maxSegmentId = 1;
|
||||||
byte mainSegmentId = 0;
|
byte mainSegmentId = 0;
|
||||||
|
|
||||||
bool saveState = false;
|
|
||||||
|
|
||||||
// These values are used by the API to read the
|
// These values are used by the API to read the
|
||||||
// last sensor state, or trigger a sensor
|
// last sensor state, or trigger a sensor
|
||||||
// through the API
|
// through the API
|
||||||
@ -73,9 +77,24 @@ class Animated_Staircase : public Usermod {
|
|||||||
bool bottomSensorRead = false;
|
bool bottomSensorRead = false;
|
||||||
bool bottomSensorWrite = false;
|
bool bottomSensorWrite = false;
|
||||||
|
|
||||||
|
// strings to reduce flash memory usage (used more than twice)
|
||||||
|
static const char _name[];
|
||||||
|
static const char _enabled[];
|
||||||
|
static const char _segmentDelay[];
|
||||||
|
static const char _onTime[];
|
||||||
|
static const char _useTopUltrasoundSensor[];
|
||||||
|
static const char _topPIRorTrigger_pin[];
|
||||||
|
static const char _topEcho_pin[];
|
||||||
|
static const char _useBottomUltrasoundSensor[];
|
||||||
|
static const char _bottomPIRorTrigger_pin[];
|
||||||
|
static const char _bottomEcho_pin[];
|
||||||
|
static const char _topEchoTime[];
|
||||||
|
static const char _bottomEchoTime[];
|
||||||
|
static const char _[];
|
||||||
|
|
||||||
void updateSegments() {
|
void updateSegments() {
|
||||||
mainSegmentId = strip.getMainSegmentId();
|
// mainSegmentId = strip.getMainSegmentId();
|
||||||
WS2812FX::Segment mainsegment = strip.getSegment(mainSegmentId);
|
// WS2812FX::Segment mainsegment = strip.getSegment(mainSegmentId);
|
||||||
WS2812FX::Segment* segments = strip.getSegments();
|
WS2812FX::Segment* segments = strip.getSegments();
|
||||||
for (int i = 0; i < MAX_NUM_SEGMENTS; i++, segments++) {
|
for (int i = 0; i < MAX_NUM_SEGMENTS; i++, segments++) {
|
||||||
if (!segments->isActive()) {
|
if (!segments->isActive()) {
|
||||||
@ -134,17 +153,15 @@ class Animated_Staircase : public Usermod {
|
|||||||
if ((millis() - lastScanTime) > scanDelay) {
|
if ((millis() - lastScanTime) > scanDelay) {
|
||||||
lastScanTime = millis();
|
lastScanTime = millis();
|
||||||
|
|
||||||
#ifdef BOTTOM_PIR_PIN
|
if (!useUSSensorBottom)
|
||||||
bottomSensorRead = bottomSensorWrite || (digitalRead(BOTTOM_PIR_PIN) == HIGH);
|
bottomSensorRead = bottomSensorWrite || (digitalRead(bottomPIRorTriggerPin) == HIGH);
|
||||||
#else
|
else
|
||||||
bottomSensorRead = bottomSensorWrite || ultrasoundRead(BOTTOM_TRIGGER_PIN, BOTTOM_ECHO_PIN, bottomMaxTimeUs);
|
bottomSensorRead = bottomSensorWrite || ultrasoundRead(bottomPIRorTriggerPin, bottomEchoPin, bottomMaxTimeUs);
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef TOP_PIR_PIN
|
if (!useUSSensorTop)
|
||||||
topSensorRead = topSensorWrite || (digitalRead(TOP_PIR_PIN) == HIGH);
|
topSensorRead = topSensorWrite || (digitalRead(topPIRorTriggerPin) == HIGH);
|
||||||
#else
|
else
|
||||||
topSensorRead = topSensorWrite || ultrasoundRead(TOP_TRIGGER_PIN, TOP_ECHO_PIN, topMaxTimeUs);
|
topSensorRead = topSensorWrite || ultrasoundRead(topPIRorTriggerPin, topEchoPin, topMaxTimeUs);
|
||||||
#endif
|
|
||||||
|
|
||||||
// Values read, reset the flags for next API call
|
// Values read, reset the flags for next API call
|
||||||
topSensorWrite = false;
|
topSensorWrite = false;
|
||||||
@ -160,9 +177,9 @@ class Animated_Staircase : public Usermod {
|
|||||||
swipe = bottomSensorRead;
|
swipe = bottomSensorRead;
|
||||||
|
|
||||||
if (swipe) {
|
if (swipe) {
|
||||||
Serial.println("ON -> Swipe up.");
|
DEBUG_PRINTLN(F("ON -> Swipe up."));
|
||||||
} else {
|
} else {
|
||||||
Serial.println("ON -> Swipe down.");
|
DEBUG_PRINTLN(F("ON -> Swipe down."));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (onIndex == offIndex) {
|
if (onIndex == offIndex) {
|
||||||
@ -181,15 +198,16 @@ class Animated_Staircase : public Usermod {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void autoPowerOff() {
|
void autoPowerOff() {
|
||||||
|
// TODO: add logic to wait until PIR sensor deactivates
|
||||||
if (on && ((millis() - lastSwitchTime) > on_time_ms)) {
|
if (on && ((millis() - lastSwitchTime) > on_time_ms)) {
|
||||||
// Swipe OFF in the direction of the last sensor detection
|
// Swipe OFF in the direction of the last sensor detection
|
||||||
swipe = lastSensor;
|
swipe = lastSensor;
|
||||||
on = false;
|
on = false;
|
||||||
|
|
||||||
if (swipe) {
|
if (swipe) {
|
||||||
Serial.println("OFF -> Swipe up.");
|
DEBUG_PRINTLN(F("OFF -> Swipe up."));
|
||||||
} else {
|
} else {
|
||||||
Serial.println("OFF -> Swipe down.");
|
DEBUG_PRINTLN(F("OFF -> Swipe down."));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -198,8 +216,8 @@ class Animated_Staircase : public Usermod {
|
|||||||
if ((millis() - lastTime) > segment_delay_ms) {
|
if ((millis() - lastTime) > segment_delay_ms) {
|
||||||
lastTime = millis();
|
lastTime = millis();
|
||||||
|
|
||||||
byte oldOnIndex = onIndex;
|
// byte oldOnIndex = onIndex;
|
||||||
byte oldOffIndex = offIndex;
|
// byte oldOffIndex = offIndex;
|
||||||
|
|
||||||
if (on) {
|
if (on) {
|
||||||
// Turn on all segments
|
// Turn on all segments
|
||||||
@ -217,103 +235,44 @@ class Animated_Staircase : public Usermod {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void writeSettingsToJson(JsonObject& root) {
|
// send sesnor values to JSON API
|
||||||
JsonObject staircase = root["staircase"];
|
void writeSensorsToJson(JsonObject& staircase) {
|
||||||
if (staircase.isNull()) {
|
staircase[F("top-sensor")] = topSensorRead;
|
||||||
staircase = root.createNestedObject("staircase");
|
staircase[F("bottom-sensor")] = bottomSensorRead;
|
||||||
}
|
|
||||||
staircase["enabled"] = enabled;
|
|
||||||
staircase["segment-delay-ms"] = segment_delay_ms;
|
|
||||||
staircase["on-time-s"] = on_time_ms / 1000;
|
|
||||||
|
|
||||||
#ifdef TOP_TRIGGER_PIN
|
|
||||||
staircase["top-echo-us"] = topMaxTimeUs;
|
|
||||||
#endif
|
|
||||||
#ifdef BOTTOM_TRIGGER_PIN
|
|
||||||
staircase["bottom-echo-us"] = bottomMaxTimeUs;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void writeSensorsToJson(JsonObject& root) {
|
// allow overrides from JSON API
|
||||||
JsonObject staircase = root["staircase"];
|
void readSensorsFromJson(JsonObject& staircase) {
|
||||||
if (staircase.isNull()) {
|
bottomSensorWrite = bottomSensorRead || (staircase[F("bottom-sensor")].as<bool>());
|
||||||
staircase = root.createNestedObject("staircase");
|
topSensorWrite = topSensorRead || (staircase[F("top-sensor")].as<bool>());
|
||||||
}
|
|
||||||
staircase["top-sensor"] = topSensorRead;
|
|
||||||
staircase["bottom-sensor"] = bottomSensorRead;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool readSettingsFromJson(JsonObject& root) {
|
|
||||||
JsonObject staircase = root["staircase"];
|
|
||||||
bool changed = false;
|
|
||||||
|
|
||||||
bool shouldEnable = staircase["enabled"] | enabled;
|
|
||||||
if (shouldEnable != enabled) {
|
|
||||||
enable(shouldEnable);
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned long c_segment_delay_ms = staircase["segment-delay-ms"] | segment_delay_ms;
|
|
||||||
if (c_segment_delay_ms != segment_delay_ms) {
|
|
||||||
segment_delay_ms = c_segment_delay_ms;
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned long c_on_time_ms = (staircase["on-time-s"] | (on_time_ms / 1000)) * 1000;
|
|
||||||
if (c_on_time_ms != on_time_ms) {
|
|
||||||
on_time_ms = c_on_time_ms;
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef TOP_TRIGGER_PIN
|
|
||||||
unsigned int c_topMaxTimeUs = staircase["top-echo-us"] | topMaxTimeUs;
|
|
||||||
if (c_topMaxTimeUs != topMaxTimeUs) {
|
|
||||||
topMaxTimeUs = c_topMaxTimeUs;
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#ifdef BOTTOM_TRIGGER_PIN
|
|
||||||
unsigned int c_bottomMaxTimeUs = staircase["bottom-echo-us"] | bottomMaxTimeUs;
|
|
||||||
if (c_bottomMaxTimeUs != bottomMaxTimeUs) {
|
|
||||||
bottomMaxTimeUs = c_bottomMaxTimeUs;
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return changed;
|
|
||||||
}
|
|
||||||
|
|
||||||
void readSensorsFromJson(JsonObject& root) {
|
|
||||||
JsonObject staircase = root["staircase"];
|
|
||||||
bottomSensorWrite = bottomSensorRead || (staircase["bottom-sensor"].as<bool>());
|
|
||||||
topSensorWrite = topSensorRead || (staircase["top-sensor"].as<bool>());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void enable(bool enable) {
|
void enable(bool enable) {
|
||||||
if (enable) {
|
if (enable) {
|
||||||
Serial.println("Animated Staircase enabled.");
|
DEBUG_PRINTLN(F("Animated Staircase enabled."));
|
||||||
Serial.print("Delay between steps: ");
|
DEBUG_PRINT(F("Delay between steps: "));
|
||||||
Serial.print(segment_delay_ms, DEC);
|
DEBUG_PRINT(segment_delay_ms);
|
||||||
Serial.print(" milliseconds.\nStairs switch off after: ");
|
DEBUG_PRINT(F(" milliseconds.\nStairs switch off after: "));
|
||||||
Serial.print(on_time_ms / 1000, DEC);
|
DEBUG_PRINT(on_time_ms / 1000);
|
||||||
Serial.println(" seconds.");
|
DEBUG_PRINTLN(F(" seconds."));
|
||||||
|
|
||||||
#ifdef BOTTOM_PIR_PIN
|
// TODO: attach interrupts
|
||||||
pinMode(BOTTOM_PIR_PIN, INPUT);
|
if (!useUSSensorBottom)
|
||||||
#else
|
pinMode(bottomPIRorTriggerPin, INPUT);
|
||||||
pinMode(BOTTOM_TRIGGER_PIN, OUTPUT);
|
else {
|
||||||
pinMode(BOTTOM_ECHO_PIN, INPUT);
|
pinMode(bottomPIRorTriggerPin, OUTPUT);
|
||||||
#endif
|
pinMode(bottomEchoPin, INPUT);
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef TOP_PIR_PIN
|
if (!useUSSensorTop)
|
||||||
pinMode(TOP_PIR_PIN, INPUT);
|
pinMode(topPIRorTriggerPin, INPUT);
|
||||||
#else
|
else {
|
||||||
pinMode(TOP_TRIGGER_PIN, OUTPUT);
|
pinMode(topPIRorTriggerPin, OUTPUT);
|
||||||
pinMode(TOP_ECHO_PIN, INPUT);
|
pinMode(topEchoPin, INPUT);
|
||||||
#endif
|
}
|
||||||
} else {
|
} else {
|
||||||
// Restore segment options
|
// Restore segment options
|
||||||
WS2812FX::Segment mainsegment = strip.getSegment(mainSegmentId);
|
// WS2812FX::Segment mainsegment = strip.getSegment(mainSegmentId);
|
||||||
WS2812FX::Segment* segments = strip.getSegments();
|
WS2812FX::Segment* segments = strip.getSegments();
|
||||||
for (int i = 0; i < MAX_NUM_SEGMENTS; i++, segments++) {
|
for (int i = 0; i < MAX_NUM_SEGMENTS; i++, segments++) {
|
||||||
if (!segments->isActive()) {
|
if (!segments->isActive()) {
|
||||||
@ -323,47 +282,57 @@ class Animated_Staircase : public Usermod {
|
|||||||
segments->setOption(SEG_OPTION_ON, 1, 1);
|
segments->setOption(SEG_OPTION_ON, 1, 1);
|
||||||
}
|
}
|
||||||
colorUpdated(NOTIFIER_CALL_MODE_DIRECT_CHANGE);
|
colorUpdated(NOTIFIER_CALL_MODE_DIRECT_CHANGE);
|
||||||
Serial.println("Animated Staircase disabled.");
|
DEBUG_PRINTLN(F("Animated Staircase disabled."));
|
||||||
}
|
}
|
||||||
enabled = enable;
|
enabled = enable;
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void setup() { enable(enabled); }
|
void setup() {
|
||||||
|
// allocate pins
|
||||||
|
if (topPIRorTriggerPin >= 0) {
|
||||||
|
if (!pinManager.allocatePin(topPIRorTriggerPin,useUSSensorTop))
|
||||||
|
topPIRorTriggerPin = -1;
|
||||||
|
}
|
||||||
|
if (topEchoPin >= 0) {
|
||||||
|
if (!pinManager.allocatePin(topEchoPin,false))
|
||||||
|
topEchoPin = -1;
|
||||||
|
}
|
||||||
|
if (bottomPIRorTriggerPin >= 0) {
|
||||||
|
if (!pinManager.allocatePin(bottomPIRorTriggerPin,useUSSensorBottom))
|
||||||
|
bottomPIRorTriggerPin = -1;
|
||||||
|
}
|
||||||
|
if (bottomEchoPin >= 0) {
|
||||||
|
if (!pinManager.allocatePin(bottomPIRorTriggerPin,false))
|
||||||
|
bottomEchoPin = -1;
|
||||||
|
}
|
||||||
|
// TODO: attach interrupts in enable()
|
||||||
|
|
||||||
|
// validate pins
|
||||||
|
if ( topPIRorTriggerPin < 0 || bottomPIRorTriggerPin < 0 ||
|
||||||
|
(useUSSensorTop && topEchoPin < 0) || (useUSSensorBottom && bottomEchoPin < 0) )
|
||||||
|
enabled = false;
|
||||||
|
|
||||||
|
enable(enabled);
|
||||||
|
initDone = true;
|
||||||
|
}
|
||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
// Write changed settings from to flash (see readFromJsonState())
|
if (!enabled) return;
|
||||||
if (saveState) {
|
|
||||||
serializeConfig();
|
|
||||||
saveState = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!enabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
checkSensors();
|
checkSensors();
|
||||||
autoPowerOff();
|
autoPowerOff();
|
||||||
updateSwipe();
|
updateSwipe();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t getId() { return USERMOD_ID_ANIMATED_STAIRCASE; }
|
uint16_t getId() { return USERMOD_ID_ANIMATED_STAIRCASE; }
|
||||||
|
|
||||||
/*
|
|
||||||
* Shows configuration settings to the json API. This object looks like:
|
|
||||||
*
|
|
||||||
* "staircase" : {
|
|
||||||
* "enabled" : true
|
|
||||||
* "segment-delay-ms" : 150,
|
|
||||||
* "on-time-s" : 5
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
void addToJsonState(JsonObject& root) {
|
void addToJsonState(JsonObject& root) {
|
||||||
writeSettingsToJson(root);
|
JsonObject staircase = root[FPSTR(_name)];
|
||||||
writeSensorsToJson(root);
|
if (staircase.isNull()) {
|
||||||
Serial.println("Staircase config exposed in API.");
|
staircase = root.createNestedObject(FPSTR(_name));
|
||||||
|
}
|
||||||
|
writeSensorsToJson(staircase);
|
||||||
|
DEBUG_PRINTLN(F("Staircase sensor state exposed in API."));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -371,27 +340,107 @@ class Animated_Staircase : public Usermod {
|
|||||||
* See void addToJsonState(JsonObject& root)
|
* See void addToJsonState(JsonObject& root)
|
||||||
*/
|
*/
|
||||||
void readFromJsonState(JsonObject& root) {
|
void readFromJsonState(JsonObject& root) {
|
||||||
// The call to serializeConfig() must be done in the main loop,
|
if (!initDone) return; // prevent crash on boot applyPreset()
|
||||||
// so we set a flag to signal the main loop to save state.
|
JsonObject staircase = root[FPSTR(_name)];
|
||||||
saveState = readSettingsFromJson(root);
|
if (!staircase.isNull()) {
|
||||||
|
if (staircase[FPSTR(_enabled)].is<bool>()) {
|
||||||
|
enabled = staircase[FPSTR(_enabled)].as<bool>();
|
||||||
|
} else {
|
||||||
|
String str = staircase[FPSTR(_enabled)]; // checkbox -> off or on
|
||||||
|
enabled = (bool)(str!="off"); // off is guaranteed to be present
|
||||||
|
}
|
||||||
readSensorsFromJson(root);
|
readSensorsFromJson(root);
|
||||||
Serial.println("Staircase config read from API.");
|
DEBUG_PRINTLN(F("Staircase sensor state read from API."));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Writes the configuration to internal flash memory.
|
* Writes the configuration to internal flash memory.
|
||||||
*/
|
*/
|
||||||
void addToConfig(JsonObject& root) {
|
void addToConfig(JsonObject& root) {
|
||||||
writeSettingsToJson(root);
|
JsonObject staircase = root[FPSTR(_name)];
|
||||||
Serial.println("Staircase config saved.");
|
if (staircase.isNull()) {
|
||||||
|
staircase = root.createNestedObject(FPSTR(_name));
|
||||||
|
}
|
||||||
|
staircase[FPSTR(_enabled)] = enabled;
|
||||||
|
staircase[FPSTR(_segmentDelay)] = segment_delay_ms;
|
||||||
|
staircase[FPSTR(_onTime)] = on_time_ms / 1000;
|
||||||
|
staircase[FPSTR(_useTopUltrasoundSensor)] = useUSSensorTop;
|
||||||
|
staircase[FPSTR(_topPIRorTrigger_pin)] = topPIRorTriggerPin;
|
||||||
|
staircase[FPSTR(_topEcho_pin)] = useUSSensorTop ? topEchoPin : -1;
|
||||||
|
staircase[FPSTR(_useBottomUltrasoundSensor)] = useUSSensorBottom;
|
||||||
|
staircase[FPSTR(_bottomPIRorTrigger_pin)] = bottomPIRorTriggerPin;
|
||||||
|
staircase[FPSTR(_bottomEcho_pin)] = useUSSensorBottom ? bottomEchoPin : -1;
|
||||||
|
staircase[FPSTR(_topEchoTime)] = topMaxTimeUs;
|
||||||
|
staircase[FPSTR(_bottomEchoTime)] = bottomMaxTimeUs;
|
||||||
|
DEBUG_PRINTLN(F("Staircase config saved."));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Reads the configuration to internal flash memory before setup() is called.
|
* Reads the configuration to internal flash memory before setup() is called.
|
||||||
*/
|
*/
|
||||||
void readFromConfig(JsonObject& root) {
|
void readFromConfig(JsonObject& root) {
|
||||||
readSettingsFromJson(root);
|
bool oldUseUSSensorTop = useUSSensorTop;
|
||||||
Serial.println("Staircase config loaded.");
|
bool oldUseUSSensorBottom = useUSSensorBottom;
|
||||||
|
int8_t oldTopAPin = topPIRorTriggerPin;
|
||||||
|
int8_t oldTopBPin = topEchoPin;
|
||||||
|
int8_t oldBottomAPin = bottomPIRorTriggerPin;
|
||||||
|
int8_t oldBottomBPin = bottomEchoPin;
|
||||||
|
|
||||||
|
JsonObject staircase = root[FPSTR(_name)];
|
||||||
|
if (!staircase.isNull()) {
|
||||||
|
if (staircase[FPSTR(_enabled)].is<bool>()) {
|
||||||
|
enabled = staircase[FPSTR(_enabled)].as<bool>();
|
||||||
|
} else {
|
||||||
|
String str = staircase[FPSTR(_enabled)]; // checkbox -> off or on
|
||||||
|
enabled = (bool)(str!="off"); // off is guaranteed to be present
|
||||||
|
}
|
||||||
|
segment_delay_ms = min(10000,max(10,staircase[FPSTR(_segmentDelay)].as<int>())); // max delay 10s
|
||||||
|
on_time_ms = min(900,max(10,staircase[FPSTR(_onTime)].as<int>())) * 1000; // min 10s, max 15min
|
||||||
|
|
||||||
|
if (staircase[FPSTR(_useTopUltrasoundSensor)].is<bool>()) {
|
||||||
|
useUSSensorTop = staircase[FPSTR(_useTopUltrasoundSensor)].as<bool>();
|
||||||
|
} else {
|
||||||
|
String str = staircase[FPSTR(_useTopUltrasoundSensor)]; // checkbox -> off or on
|
||||||
|
useUSSensorTop = (bool)(str!="off"); // off is guaranteed to be present
|
||||||
|
}
|
||||||
|
|
||||||
|
topPIRorTriggerPin = min(39,max(-1,staircase[FPSTR(_topPIRorTrigger_pin)].as<int>()));
|
||||||
|
topEchoPin = min(39,max(-1,staircase[FPSTR(_topEcho_pin)].as<int>()));
|
||||||
|
|
||||||
|
if (staircase[FPSTR(_useBottomUltrasoundSensor)].is<bool>()) {
|
||||||
|
useUSSensorBottom = staircase[FPSTR(_useBottomUltrasoundSensor)].as<bool>();
|
||||||
|
} else {
|
||||||
|
String str = staircase[FPSTR(_useBottomUltrasoundSensor)]; // checkbox -> off or on
|
||||||
|
useUSSensorBottom = (bool)(str!="off"); // off is guaranteed to be present
|
||||||
|
}
|
||||||
|
bottomPIRorTriggerPin = min(39,max(-1,staircase[FPSTR(_bottomPIRorTrigger_pin)].as<int>()));
|
||||||
|
bottomEchoPin = min(39,max(-1,staircase[FPSTR(_bottomEcho_pin)].as<int>()));
|
||||||
|
topMaxTimeUs = min(18000,max(300,staircase[FPSTR(_topEchoTime)].as<int>())); // max distnace ~3m (a noticable lag of 18ms may be expected)
|
||||||
|
bottomMaxTimeUs = min(18000,max(300,staircase[FPSTR(_bottomEchoTime)].as<int>())); // max distance ~3m (a noticable lag of 18ms may be expected)
|
||||||
|
DEBUG_PRINTLN(F("Staircase config (re)loaded."));
|
||||||
|
} else {
|
||||||
|
DEBUG_PRINTLN(F("No config found. (Using defaults.)"));
|
||||||
|
}
|
||||||
|
if (!initDone) {
|
||||||
|
// first run: reading from cfg.json
|
||||||
|
} else {
|
||||||
|
// changing paramters from settings page
|
||||||
|
bool changed = false;
|
||||||
|
if ((oldUseUSSensorTop != useUSSensorTop) ||
|
||||||
|
(oldUseUSSensorBottom != useUSSensorBottom) ||
|
||||||
|
(oldTopAPin != topPIRorTriggerPin) ||
|
||||||
|
(oldTopBPin != topEchoPin) ||
|
||||||
|
(oldBottomAPin != bottomPIRorTriggerPin) ||
|
||||||
|
(oldBottomBPin != bottomEchoPin)) {
|
||||||
|
changed = true;
|
||||||
|
pinManager.deallocatePin(oldTopAPin);
|
||||||
|
pinManager.deallocatePin(oldTopBPin);
|
||||||
|
pinManager.deallocatePin(oldBottomAPin);
|
||||||
|
pinManager.deallocatePin(oldBottomBPin);
|
||||||
|
}
|
||||||
|
if (changed) setup();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -405,23 +454,33 @@ class Animated_Staircase : public Usermod {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
JsonArray usermodEnabled =
|
JsonArray usermodEnabled = staircase.createNestedArray(F("Staircase enabled")); // name
|
||||||
staircase.createNestedArray("Staircase enabled"); // name
|
|
||||||
usermodEnabled.add("yes"); // value
|
usermodEnabled.add("yes"); // value
|
||||||
|
|
||||||
JsonArray segmentDelay =
|
JsonArray segmentDelay = staircase.createNestedArray(F("Delay between stairs")); // name
|
||||||
staircase.createNestedArray("Delay between stairs"); // name
|
|
||||||
segmentDelay.add(segment_delay_ms); // value
|
segmentDelay.add(segment_delay_ms); // value
|
||||||
segmentDelay.add(" milliseconds"); // unit
|
segmentDelay.add("ms"); // unit
|
||||||
|
|
||||||
JsonArray onTime =
|
JsonArray onTime = staircase.createNestedArray(F("Power-off stairs after")); // name
|
||||||
staircase.createNestedArray("Power-off stairs after"); // name
|
|
||||||
onTime.add(on_time_ms / 1000); // value
|
onTime.add(on_time_ms / 1000); // value
|
||||||
onTime.add(" seconds"); // unit
|
onTime.add("s"); // unit
|
||||||
} else {
|
} else {
|
||||||
JsonArray usermodEnabled =
|
JsonArray usermodEnabled = staircase.createNestedArray(F("Staircase enabled")); // name
|
||||||
staircase.createNestedArray("Staircase enabled"); // name
|
|
||||||
usermodEnabled.add("no"); // value
|
usermodEnabled.add("no"); // value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// strings to reduce flash memory usage (used more than twice)
|
||||||
|
const char Animated_Staircase::_name[] PROGMEM = "staircase";
|
||||||
|
const char Animated_Staircase::_enabled[] PROGMEM = "enabled";
|
||||||
|
const char Animated_Staircase::_segmentDelay[] PROGMEM = "segment-delay-ms";
|
||||||
|
const char Animated_Staircase::_onTime[] PROGMEM = "on-time-s";
|
||||||
|
const char Animated_Staircase::_useTopUltrasoundSensor[] PROGMEM = "useTopUltrasoundSensor";
|
||||||
|
const char Animated_Staircase::_topPIRorTrigger_pin[] PROGMEM = "topPIRorTrigger_pin";
|
||||||
|
const char Animated_Staircase::_topEcho_pin[] PROGMEM = "topEcho_pin";
|
||||||
|
const char Animated_Staircase::_useBottomUltrasoundSensor[] PROGMEM = "useBottomUltrasoundSensor";
|
||||||
|
const char Animated_Staircase::_bottomPIRorTrigger_pin[] PROGMEM = "bottomPIRorTrigger_pin";
|
||||||
|
const char Animated_Staircase::_bottomEcho_pin[] PROGMEM = "bottomEcho_pin";
|
||||||
|
const char Animated_Staircase::_topEchoTime[] PROGMEM = "top-echo-us";
|
||||||
|
const char Animated_Staircase::_bottomEchoTime[] PROGMEM = "bottom-echo-us";
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
/*
|
|
||||||
* Animated_Staircase compiletime confguration.
|
|
||||||
*
|
|
||||||
* Please see README.md on how to change this file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Please change the pin numbering below to match your board.
|
|
||||||
#define TOP_PIR_PIN D5
|
|
||||||
#define BOTTOM_PIR_PIN D6
|
|
||||||
|
|
||||||
// Or uncumment and a pir and use an ultrasound HC-SR04 sensor,
|
|
||||||
// see README.md for details
|
|
||||||
#ifndef TOP_PIR_PIN
|
|
||||||
#define TOP_TRIGGER_PIN D2
|
|
||||||
#define TOP_ECHO_PIN D3
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef BOTTOM_PIR_PIN
|
|
||||||
#define BOTTOM_TRIGGER_PIN D4
|
|
||||||
#define BOTTOM_ECHO_PIN D5
|
|
||||||
#endif
|
|
@ -20,44 +20,10 @@ Edit `usermods_list.cpp`:
|
|||||||
2. add `#include "../usermods/Animated_Staircase/Animated_Staircase.h"` to the top of the file
|
2. add `#include "../usermods/Animated_Staircase/Animated_Staircase.h"` to the top of the file
|
||||||
3. add `usermods.add(new Animated_Staircase());` to the end of the `void registerUsermods()` function.
|
3. add `usermods.add(new Animated_Staircase());` to the end of the `void registerUsermods()` function.
|
||||||
|
|
||||||
Edit `Animated_Staircase_config.h`:
|
You can configure usermod using Usermods settings page.
|
||||||
1. Open `usermods/Animated_Staircase/Animated_Staircase_config.h`
|
Please enter GPIO pins for PIR sensors or ultrasonic sensor (trigger and echo).
|
||||||
2. To use PIR sensors, change these lines to match your setup:
|
If you use PIR sensor enter -1 for echo pin.
|
||||||
Using D7 and D6 pin notation as used on several boards:
|
Maximum distance for ultrasonic sensor can be configured as a time needed for echo (see below).
|
||||||
|
|
||||||
```cpp
|
|
||||||
#define TOP_PIR_PIN D7
|
|
||||||
#define BOTTOM_PIR_PIN D6
|
|
||||||
```
|
|
||||||
|
|
||||||
Or using GPIO numbering for pins 25 and 26:
|
|
||||||
```cpp
|
|
||||||
#define TOP_PIR_PIN 26
|
|
||||||
#define BOTTOM_PIR_PIN 25
|
|
||||||
```
|
|
||||||
|
|
||||||
To use Ultrasonic HC-SR04 sensors instead of (one of the) PIR sensors,
|
|
||||||
uncomment one of the PIR sensor lines and adjust the pin numbers for the
|
|
||||||
connected Ultrasonic sensor. In the example below we use an Ultrasonic
|
|
||||||
sensor at the bottom of the stairs:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
#define TOP_PIR_PIN 32
|
|
||||||
//#define BOTTOM_PIR_PIN D6 /* This PIR sensor is disabled */
|
|
||||||
|
|
||||||
#ifndef TOP_PIR_PIN
|
|
||||||
#define TOP_SIGNAL_PIN D2
|
|
||||||
#define TOP_ECHO_PIN D3
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef BOTTOM_PIR_PIN /* If the bottom PIR is disabled, */
|
|
||||||
#define BOTTOM_SIGNAL_PIN 25 /* This Ultrasonic sensor is used */
|
|
||||||
#define BOTTOM_ECHO_PIN 26
|
|
||||||
#endif
|
|
||||||
```
|
|
||||||
|
|
||||||
After these modifications, compile and upload your WLED binary to your board
|
|
||||||
and check the WLED info page to see if this usermod is enabled.
|
|
||||||
|
|
||||||
## Hardware installation
|
## Hardware installation
|
||||||
1. Stick the LED strip under each step of the stairs.
|
1. Stick the LED strip under each step of the stairs.
|
||||||
@ -201,3 +167,7 @@ curl -X POST -H "Content-Type: application/json" \
|
|||||||
|
|
||||||
Have fun with this usermod.<br/>
|
Have fun with this usermod.<br/>
|
||||||
www.rolfje.com
|
www.rolfje.com
|
||||||
|
|
||||||
|
## Change log
|
||||||
|
2021-04
|
||||||
|
* Adaptation for runtime configuration.
|
@ -10,27 +10,14 @@ The LED strip is switched [using a relay](https://github.com/Aircoookie/WLED/wik
|
|||||||
## Webinterface
|
## Webinterface
|
||||||
|
|
||||||
The info page in the web interface shows the items below
|
The info page in the web interface shows the items below
|
||||||
|
|
||||||
- the state of the sensor. By clicking on the state the sensor can be deactivated/activated. Changes persist after a reboot.
|
|
||||||
**I recommend to deactivate the sensor before an OTA update and activate it again afterwards**.
|
|
||||||
- the remaining time of the off timer.
|
- the remaining time of the off timer.
|
||||||
|
**I recommend to deactivate the sensor before an OTA update and activate it again afterwards**.
|
||||||
## JSON API
|
|
||||||
|
|
||||||
The usermod supports the following state changes:
|
|
||||||
|
|
||||||
| JSON key | Value range | Description |
|
|
||||||
|------------|-------------|---------------------------------|
|
|
||||||
| PIRenabled | bool | Deactivdate/activate the sensor |
|
|
||||||
| PIRoffSec | 60 to 43200 | Off timer seconds |
|
|
||||||
|
|
||||||
Changes also persist after a reboot.
|
|
||||||
|
|
||||||
## Sensor connection
|
## Sensor connection
|
||||||
|
|
||||||
My setup uses an HC-SR501 sensor, a HC-SR505 should also work.
|
My setup uses an HC-SR501 sensor, a HC-SR505 should also work.
|
||||||
|
|
||||||
The usermod uses GPIO13 (D1 mini pin D7) for the sensor signal.
|
The usermod uses GPIO13 (D1 mini pin D7) by default for the sensor signal but can be changed in the Usermod settings page.
|
||||||
[This example page](http://www.esp8266learning.com/wemos-mini-pir-sensor-example.php) describes how to connect the sensor.
|
[This example page](http://www.esp8266learning.com/wemos-mini-pir-sensor-example.php) describes how to connect the sensor.
|
||||||
|
|
||||||
Use the potentiometers on the sensor to set the time-delay to the minimum and the sensitivity to about half, or slightly above.
|
Use the potentiometers on the sensor to set the time-delay to the minimum and the sensitivity to about half, or slightly above.
|
||||||
@ -76,8 +63,6 @@ void registerUsermods()
|
|||||||
|
|
||||||
## API to enable/disable the PIR sensor from outside. For example from another usermod.
|
## API to enable/disable the PIR sensor from outside. For example from another usermod.
|
||||||
|
|
||||||
The class provides the static method `PIRsensorSwitch* PIRsensorSwitch::GetInstance()` to get a pointer to the usermod object.
|
|
||||||
|
|
||||||
To query or change the PIR sensor state the methods `bool PIRsensorEnabled()` and `void EnablePIRsensor(bool enable)` are available.
|
To query or change the PIR sensor state the methods `bool PIRsensorEnabled()` and `void EnablePIRsensor(bool enable)` are available.
|
||||||
|
|
||||||
### There are two options to get access to the usermod instance:
|
### There are two options to get access to the usermod instance:
|
||||||
@ -98,12 +83,19 @@ class MyUsermod : public Usermod {
|
|||||||
//...
|
//...
|
||||||
|
|
||||||
void togglePIRSensor() {
|
void togglePIRSensor() {
|
||||||
if (PIRsensorSwitch::GetInstance() != nullptr) {
|
#ifdef USERMOD_PIR_SENSOR_SWITCH
|
||||||
PIRsensorSwitch::GetInstance()->EnablePIRsensor(!PIRsensorSwitch::GetInstance()->PIRsensorEnabled());
|
PIRsensorSwitch *PIRsensor = (PIRsensorSwitch::*) usermods.lookup(USERMOD_ID_PIRSWITCH);
|
||||||
|
if (PIRsensor != nullptr) {
|
||||||
|
PIRsensor->EnablePIRsensor(!PIRsensor->PIRsensorEnabled());
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
//...
|
//...
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
Have fun - @gegu
|
Have fun - @gegu
|
||||||
|
|
||||||
|
## Change log
|
||||||
|
2021-04
|
||||||
|
* Adaptation for runtime configuration.
|
@ -2,6 +2,15 @@
|
|||||||
|
|
||||||
#include "wled.h"
|
#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.
|
* 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.
|
||||||
@ -30,24 +39,11 @@ public:
|
|||||||
/**
|
/**
|
||||||
* constructor
|
* constructor
|
||||||
*/
|
*/
|
||||||
PIRsensorSwitch()
|
PIRsensorSwitch() {}
|
||||||
{
|
|
||||||
// set static instance pointer
|
|
||||||
PIRsensorSwitchInstance(this);
|
|
||||||
}
|
|
||||||
/**
|
/**
|
||||||
* desctructor
|
* desctructor
|
||||||
*/
|
*/
|
||||||
~PIRsensorSwitch()
|
~PIRsensorSwitch() {}
|
||||||
{
|
|
||||||
PIRsensorSwitchInstance(nullptr, true);
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* return the instance pointer of the class
|
|
||||||
*/
|
|
||||||
static PIRsensorSwitch *GetInstance() { return PIRsensorSwitchInstance(); }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enable/Disable the PIR sensor
|
* Enable/Disable the PIR sensor
|
||||||
@ -60,19 +56,24 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
// PIR sensor pin
|
// PIR sensor pin
|
||||||
const uint8_t PIRsensorPin = 13; // D7 on D1 mini
|
int8_t PIRsensorPin = PIR_SENSOR_PIN;
|
||||||
// notification mode for colorUpdated()
|
// notification mode for colorUpdated()
|
||||||
const byte NotifyUpdateMode = NOTIFIER_CALL_MODE_NO_NOTIFY; // NOTIFIER_CALL_MODE_DIRECT_CHANGE
|
const byte NotifyUpdateMode = NOTIFIER_CALL_MODE_NO_NOTIFY; // NOTIFIER_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;
|
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 m_PIRsensorPinState = LOW;
|
byte m_PIRsensorPinState = LOW;
|
||||||
// PIR sensor enabled - ISR attached
|
// PIR sensor enabled - ISR attached
|
||||||
bool m_PIRenabled = true;
|
bool m_PIRenabled = true;
|
||||||
// state if serializeConfig() should be called
|
// status of initialisation
|
||||||
bool m_updateConfig = false;
|
bool initDone = false;
|
||||||
|
|
||||||
|
// strings to reduce flash memory usage (used more than twice)
|
||||||
|
static const char _name[];
|
||||||
|
static const char _switchOffDelay[];
|
||||||
|
static const char _enabled[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* return or change if new PIR sensor state is available
|
* return or change if new PIR sensor state is available
|
||||||
@ -84,11 +85,6 @@ private:
|
|||||||
*/
|
*/
|
||||||
static void IRAM_ATTR ISR_PIRstateChange();
|
static void IRAM_ATTR ISR_PIRstateChange();
|
||||||
|
|
||||||
/**
|
|
||||||
* Set/get instance pointer
|
|
||||||
*/
|
|
||||||
static PIRsensorSwitch *PIRsensorSwitchInstance(PIRsensorSwitch *pInstance = nullptr, bool bRemoveInstance = false);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* switch strip on/off
|
* switch strip on/off
|
||||||
*/
|
*/
|
||||||
@ -107,6 +103,17 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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, true, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read and update PIR sensor state.
|
* Read and update PIR sensor state.
|
||||||
* Initilize/reset switch off timer
|
* Initilize/reset switch off timer
|
||||||
@ -121,6 +128,7 @@ private:
|
|||||||
{
|
{
|
||||||
m_offTimerStart = 0;
|
m_offTimerStart = 0;
|
||||||
switchStrip(true);
|
switchStrip(true);
|
||||||
|
publishMqtt("on");
|
||||||
}
|
}
|
||||||
else if (bri != 0)
|
else if (bri != 0)
|
||||||
{
|
{
|
||||||
@ -143,6 +151,7 @@ private:
|
|||||||
if (m_PIRenabled == true)
|
if (m_PIRenabled == true)
|
||||||
{
|
{
|
||||||
switchStrip(false);
|
switchStrip(false);
|
||||||
|
publishMqtt("off");
|
||||||
}
|
}
|
||||||
m_offTimerStart = 0;
|
m_offTimerStart = 0;
|
||||||
return true;
|
return true;
|
||||||
@ -159,14 +168,21 @@ public:
|
|||||||
*/
|
*/
|
||||||
void setup()
|
void setup()
|
||||||
{
|
{
|
||||||
|
// pin retrieved from cfg.json (readFromConfig()) prior to running setup()
|
||||||
|
if (!pinManager.allocatePin(PIRsensorPin,false)) {
|
||||||
|
PIRsensorPin = -1; // allocation failed
|
||||||
|
m_PIRenabled = false;
|
||||||
|
DEBUG_PRINTLN(F("PIRSensorSwitch pin allocation failed."));
|
||||||
|
} else {
|
||||||
// PIR Sensor mode INPUT_PULLUP
|
// PIR Sensor mode INPUT_PULLUP
|
||||||
pinMode(PIRsensorPin, INPUT_PULLUP);
|
pinMode(PIRsensorPin, INPUT_PULLUP);
|
||||||
if (m_PIRenabled)
|
if (m_PIRenabled) {
|
||||||
{
|
|
||||||
// assign interrupt function and set CHANGE mode
|
// assign interrupt function and set CHANGE mode
|
||||||
attachInterrupt(digitalPinToInterrupt(PIRsensorPin), ISR_PIRstateChange, CHANGE);
|
attachInterrupt(digitalPinToInterrupt(PIRsensorPin), ISR_PIRstateChange, CHANGE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
initDone = true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* connected() is called every time the WiFi is (re)connected
|
* connected() is called every time the WiFi is (re)connected
|
||||||
@ -181,14 +197,8 @@ public:
|
|||||||
*/
|
*/
|
||||||
void loop()
|
void loop()
|
||||||
{
|
{
|
||||||
if (!updatePIRsensorState())
|
if (!updatePIRsensorState()) {
|
||||||
{
|
|
||||||
handleOffTimer();
|
handleOffTimer();
|
||||||
if (m_updateConfig)
|
|
||||||
{
|
|
||||||
serializeConfig();
|
|
||||||
m_updateConfig = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,40 +209,42 @@ public:
|
|||||||
*/
|
*/
|
||||||
void addToJsonInfo(JsonObject &root)
|
void addToJsonInfo(JsonObject &root)
|
||||||
{
|
{
|
||||||
//this code adds "u":{"⏲ PIR sensor state":uiDomString} to the info object
|
|
||||||
// the value contains a button to toggle the sensor enabled/disabled
|
|
||||||
JsonObject user = root["u"];
|
JsonObject user = root["u"];
|
||||||
if (user.isNull())
|
if (user.isNull())
|
||||||
user = root.createNestedObject("u");
|
user = root.createNestedObject("u");
|
||||||
|
/*
|
||||||
JsonArray infoArr = user.createNestedArray("⏲ PIR sensor state"); //name
|
JsonArray infoArr = user.createNestedArray(F("<i class=\"icons\"></i> PIR sensor state")); //name
|
||||||
String uiDomString = "<button class=\"btn infobtn\" onclick=\"requestJson({PIRenabled:";
|
String uiDomString = F("<button class=\"btn infobtn\" onclick=\"requestJson({PIRenabled:");
|
||||||
String sensorStateInfo;
|
String sensorStateInfo;
|
||||||
|
|
||||||
// PIR sensor state
|
// PIR sensor state
|
||||||
if (m_PIRenabled)
|
if (m_PIRenabled)
|
||||||
{
|
{
|
||||||
uiDomString += "false";
|
uiDomString += "false";
|
||||||
sensorStateInfo = (m_PIRsensorPinState != LOW ? "active" : "inactive"); //value
|
sensorStateInfo = (m_PIRsensorPinState != LOW ? FPSTR(F("active")) : FPSTR(F("inactive"))); //value
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
uiDomString += "true";
|
uiDomString += "true";
|
||||||
sensorStateInfo = "Disabled !";
|
sensorStateInfo = F("Disabled!");
|
||||||
}
|
}
|
||||||
uiDomString += "});return false;\">";
|
uiDomString += F("});return false;\">");
|
||||||
uiDomString += sensorStateInfo;
|
uiDomString += sensorStateInfo;
|
||||||
uiDomString += "</button>";
|
uiDomString += F("</button>");
|
||||||
infoArr.add(uiDomString); //value
|
infoArr.add(uiDomString); //value
|
||||||
|
*/
|
||||||
//this code adds "u":{"⏲ switch off timer":uiDomString} to the info object
|
if (m_PIRenabled)
|
||||||
uiDomString = "⏲ switch off timer<span style=\"display:block;padding-left:25px;\">\
|
{
|
||||||
after <input type=\"number\" min=\"1\" max=\"720\" value=\"";
|
/*
|
||||||
|
JsonArray infoArr = user.createNestedArray(F("PIR switch-off timer after")); //name
|
||||||
|
String uiDomString = F("<input type=\"number\" min=\"1\" max=\"720\" value=\"");
|
||||||
uiDomString += (m_switchOffDelay / 60000);
|
uiDomString += (m_switchOffDelay / 60000);
|
||||||
uiDomString += "\" onchange=\"requestJson({PIRoffSec:parseInt(this.value)*60});\">min</span>";
|
uiDomString += F("\" onchange=\"requestJson({PIRoffSec:parseInt(this.value)*60});\">min");
|
||||||
infoArr = user.createNestedArray(uiDomString); //name
|
infoArr.add(uiDomString);
|
||||||
|
*/
|
||||||
// off timer
|
// off timer
|
||||||
|
String uiDomString = F("PIR <i class=\"icons\"></i>");
|
||||||
|
JsonArray infoArr = user.createNestedArray(uiDomString); // timer value
|
||||||
if (m_offTimerStart > 0)
|
if (m_offTimerStart > 0)
|
||||||
{
|
{
|
||||||
uiDomString = "";
|
uiDomString = "";
|
||||||
@ -240,7 +252,7 @@ after <input type=\"number\" min=\"1\" max=\"720\" value=\"";
|
|||||||
if (offSeconds >= 3600)
|
if (offSeconds >= 3600)
|
||||||
{
|
{
|
||||||
uiDomString += (offSeconds / 3600);
|
uiDomString += (offSeconds / 3600);
|
||||||
uiDomString += " hours ";
|
uiDomString += F("h ");
|
||||||
offSeconds %= 3600;
|
offSeconds %= 3600;
|
||||||
}
|
}
|
||||||
if (offSeconds >= 60)
|
if (offSeconds >= 60)
|
||||||
@ -254,14 +266,13 @@ after <input type=\"number\" min=\"1\" max=\"720\" value=\"";
|
|||||||
}
|
}
|
||||||
if (uiDomString.length() > 0)
|
if (uiDomString.length() > 0)
|
||||||
{
|
{
|
||||||
uiDomString += " min ";
|
uiDomString += F("min ");
|
||||||
}
|
}
|
||||||
uiDomString += (offSeconds);
|
uiDomString += (offSeconds);
|
||||||
infoArr.add(uiDomString + " sec");
|
infoArr.add(uiDomString + F("s"));
|
||||||
|
} else {
|
||||||
|
infoArr.add(F("inactive"));
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
infoArr.add("inactive");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -273,8 +284,8 @@ after <input type=\"number\" min=\"1\" max=\"720\" value=\"";
|
|||||||
*/
|
*/
|
||||||
void addToJsonState(JsonObject &root)
|
void addToJsonState(JsonObject &root)
|
||||||
{
|
{
|
||||||
root["PIRenabled"] = m_PIRenabled;
|
root[FPSTR(_enabled)] = m_PIRenabled;
|
||||||
root["PIRoffSec"] = (m_switchOffDelay / 1000);
|
root[FPSTR(_switchOffDelay)] = (m_switchOffDelay / 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -285,26 +296,40 @@ after <input type=\"number\" min=\"1\" max=\"720\" value=\"";
|
|||||||
*/
|
*/
|
||||||
void readFromJsonState(JsonObject &root)
|
void readFromJsonState(JsonObject &root)
|
||||||
{
|
{
|
||||||
if (root["PIRoffSec"] != nullptr)
|
if (root[FPSTR(_switchOffDelay)] != nullptr) {
|
||||||
{
|
m_switchOffDelay = (1000 * max(60UL, min(43200UL, root[FPSTR(_switchOffDelay)].as<unsigned long>())));
|
||||||
m_switchOffDelay = (1000 * max(60UL, min(43200UL, root["PIRoffSec"].as<unsigned long>())));
|
|
||||||
m_updateConfig = true;
|
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
if (root["PIRenabled"] != nullptr)
|
if (root["pin"] != nullptr) {
|
||||||
|
int8_t pin = (int)root["pin"];
|
||||||
|
// check if pin is OK
|
||||||
|
if (pin != PIRsensorPin && pin>=0 && pinManager.allocatePin(pin,false)) {
|
||||||
|
// deallocate old pin
|
||||||
|
pinManager.deallocatePin(PIRsensorPin);
|
||||||
|
// PIR Sensor mode INPUT_PULLUP
|
||||||
|
pinMode(pin, INPUT_PULLUP);
|
||||||
|
if (m_PIRenabled)
|
||||||
{
|
{
|
||||||
if (root["PIRenabled"] && !m_PIRenabled)
|
// remove old ISR
|
||||||
{
|
detachInterrupt(PIRsensorPin);
|
||||||
attachInterrupt(digitalPinToInterrupt(PIRsensorPin), ISR_PIRstateChange, CHANGE);
|
// assign interrupt function and set CHANGE mode
|
||||||
|
attachInterrupt(digitalPinToInterrupt(pin), ISR_PIRstateChange, CHANGE);
|
||||||
newPIRsensorState(true, true);
|
newPIRsensorState(true, true);
|
||||||
}
|
}
|
||||||
else if (m_PIRenabled)
|
PIRsensorPin = pin;
|
||||||
{
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root[FPSTR(_enabled)] != nullptr) {
|
||||||
|
if (root[FPSTR(_enabled)] && !m_PIRenabled && PIRsensorPin >= 0) {
|
||||||
|
attachInterrupt(digitalPinToInterrupt(PIRsensorPin), ISR_PIRstateChange, CHANGE);
|
||||||
|
newPIRsensorState(true, true);
|
||||||
|
} else if (m_PIRenabled && PIRsensorPin >= 0) {
|
||||||
detachInterrupt(PIRsensorPin);
|
detachInterrupt(PIRsensorPin);
|
||||||
}
|
}
|
||||||
m_PIRenabled = root["PIRenabled"];
|
m_PIRenabled = root[FPSTR(_enabled)];
|
||||||
m_updateConfig = true;
|
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -312,19 +337,72 @@ after <input type=\"number\" min=\"1\" max=\"720\" value=\"";
|
|||||||
*/
|
*/
|
||||||
void addToConfig(JsonObject &root)
|
void addToConfig(JsonObject &root)
|
||||||
{
|
{
|
||||||
JsonObject top = root.createNestedObject("PIRsensorSwitch");
|
JsonObject top = root.createNestedObject(FPSTR(_name));
|
||||||
top["PIRenabled"] = m_PIRenabled;
|
top[FPSTR(_enabled)] = m_PIRenabled;
|
||||||
top["PIRoffSec"] = m_switchOffDelay;
|
top[FPSTR(_switchOffDelay)] = m_switchOffDelay / 1000;
|
||||||
|
top["pin"] = PIRsensorPin;
|
||||||
|
DEBUG_PRINTLN(F("PIR config saved."));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* restore the changeable values
|
* restore the changeable values
|
||||||
|
* readFromConfig() is called before setup() to populate properties from values stored in cfg.json
|
||||||
*/
|
*/
|
||||||
void readFromConfig(JsonObject &root)
|
void readFromConfig(JsonObject &root)
|
||||||
{
|
{
|
||||||
JsonObject top = root["PIRsensorSwitch"];
|
bool oldEnabled = m_PIRenabled;
|
||||||
m_PIRenabled = (top["PIRenabled"] != nullptr ? top["PIRenabled"] : true);
|
int8_t oldPin = PIRsensorPin;
|
||||||
m_switchOffDelay = top["PIRoffSec"] | m_switchOffDelay;
|
|
||||||
|
JsonObject top = root[FPSTR(_name)];
|
||||||
|
if (top.isNull()) return;
|
||||||
|
|
||||||
|
if (top["pin"] != nullptr) {
|
||||||
|
PIRsensorPin = min(39,max(-1,top["pin"].as<int>())); // check bounds
|
||||||
|
}
|
||||||
|
|
||||||
|
if (top[FPSTR(_enabled)] != nullptr) {
|
||||||
|
if (top[FPSTR(_enabled)].is<bool>()) {
|
||||||
|
m_PIRenabled = top[FPSTR(_enabled)].as<bool>(); // reading from cfg.json
|
||||||
|
} else {
|
||||||
|
// change from settings page
|
||||||
|
String str = top[FPSTR(_enabled)]; // checkbox -> off or on
|
||||||
|
m_PIRenabled = (bool)(str!="off"); // off is guaranteed to be present
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (top[FPSTR(_switchOffDelay)] != nullptr) {
|
||||||
|
m_switchOffDelay = (top[FPSTR(_switchOffDelay)].as<int>() * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!initDone) {
|
||||||
|
// reading config prior to setup()
|
||||||
|
DEBUG_PRINTLN(F("PIR config loaded."));
|
||||||
|
} else {
|
||||||
|
if (oldPin != PIRsensorPin || oldEnabled != m_PIRenabled) {
|
||||||
|
if (oldEnabled) {
|
||||||
|
// remove old ISR if disabling usermod
|
||||||
|
detachInterrupt(oldPin);
|
||||||
|
}
|
||||||
|
// check if pin is OK
|
||||||
|
if (oldPin != PIRsensorPin && oldPin >= 0) {
|
||||||
|
// if we are changing pin in settings page
|
||||||
|
// deallocate old pin
|
||||||
|
pinManager.deallocatePin(oldPin);
|
||||||
|
if (pinManager.allocatePin(PIRsensorPin,false)) {
|
||||||
|
pinMode(PIRsensorPin, INPUT_PULLUP);
|
||||||
|
} else {
|
||||||
|
// allocation failed
|
||||||
|
PIRsensorPin = -1;
|
||||||
|
m_PIRenabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (m_PIRenabled) {
|
||||||
|
attachInterrupt(digitalPinToInterrupt(PIRsensorPin), ISR_PIRstateChange, CHANGE);
|
||||||
|
newPIRsensorState(true, true);
|
||||||
|
}
|
||||||
|
DEBUG_PRINTLN(F("PIR config (re)loaded."));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -355,12 +433,7 @@ void IRAM_ATTR PIRsensorSwitch::ISR_PIRstateChange()
|
|||||||
newPIRsensorState(true, true);
|
newPIRsensorState(true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
PIRsensorSwitch *PIRsensorSwitch::PIRsensorSwitchInstance(PIRsensorSwitch *pInstance, bool bRemoveInstance)
|
// strings to reduce flash memory usage (used more than twice)
|
||||||
{
|
const char PIRsensorSwitch::_name[] PROGMEM = "PIRsensorSwitch";
|
||||||
static PIRsensorSwitch *s_pPIRsensorSwitch = nullptr;
|
const char PIRsensorSwitch::_enabled[] PROGMEM = "PIRenabled";
|
||||||
if (pInstance != nullptr || bRemoveInstance)
|
const char PIRsensorSwitch::_switchOffDelay[] PROGMEM = "PIRoffSec";
|
||||||
{
|
|
||||||
s_pPIRsensorSwitch = pInstance;
|
|
||||||
}
|
|
||||||
return s_pPIRsensorSwitch;
|
|
||||||
}
|
|
||||||
|
@ -14,10 +14,10 @@ Copy the example `platformio_override.ini` to the root directory. This file sho
|
|||||||
### Define Your Options
|
### Define Your Options
|
||||||
|
|
||||||
* `USERMOD_DALLASTEMPERATURE` - define this to have this user mod included wled00\usermods_list.cpp
|
* `USERMOD_DALLASTEMPERATURE` - define this to have this user mod included wled00\usermods_list.cpp
|
||||||
* `USERMOD_DALLASTEMPERATURE_CELSIUS` - define this to report temperatures in degrees celsious, otherwise fahrenheit will be reported
|
|
||||||
* `USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL` - the number of milliseconds between measurements, defaults to 60 seconds
|
|
||||||
* `USERMOD_DALLASTEMPERATURE_FIRST_MEASUREMENT_AT` - the number of milliseconds after boot to take first measurement, defaults to 20 seconds
|
* `USERMOD_DALLASTEMPERATURE_FIRST_MEASUREMENT_AT` - the number of milliseconds after boot to take first measurement, defaults to 20 seconds
|
||||||
|
|
||||||
|
All parameters can be configured at runtime using Usermods settings page.
|
||||||
|
|
||||||
## Project link
|
## Project link
|
||||||
|
|
||||||
* [QuinLED-Dig-Uno](https://quinled.info/2018/09/15/quinled-dig-uno/) - Project link
|
* [QuinLED-Dig-Uno](https://quinled.info/2018/09/15/quinled-dig-uno/) - Project link
|
||||||
@ -41,10 +41,7 @@ default_envs = d1_mini
|
|||||||
...
|
...
|
||||||
lib_deps =
|
lib_deps =
|
||||||
...
|
...
|
||||||
#For use SSD1306 OLED display uncomment following
|
#For Dallas sensor uncomment following line
|
||||||
U8g2@~2.27.3
|
|
||||||
#For Dallas sensor uncomment following 2 lines
|
|
||||||
DallasTemperature@~3.8.0
|
|
||||||
OneWire@~2.3.5
|
OneWire@~2.3.5
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
@ -56,3 +53,5 @@ lib_deps =
|
|||||||
* Do not report low temperatures that indicate an error to mqtt
|
* Do not report low temperatures that indicate an error to mqtt
|
||||||
* Disable plugin if temperature sensor not detected
|
* Disable plugin if temperature sensor not detected
|
||||||
* Report the number of seconds until the first read in the info screen instead of sensor error
|
* Report the number of seconds until the first read in the info screen instead of sensor error
|
||||||
|
2021-04
|
||||||
|
* Adaptation for runtime configuration.
|
@ -2,15 +2,16 @@
|
|||||||
|
|
||||||
#include "wled.h"
|
#include "wled.h"
|
||||||
|
|
||||||
#include <DallasTemperature.h> //DS18B20
|
//#include <DallasTemperature.h> //DS18B20
|
||||||
|
#include "OneWire.h"
|
||||||
|
|
||||||
//Pin defaults for QuinLed Dig-Uno
|
//Pin defaults for QuinLed Dig-Uno if not overriden
|
||||||
#ifndef TEMPERATURE_PIN
|
#ifndef TEMPERATURE_PIN
|
||||||
#ifdef ARDUINO_ARCH_ESP32
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
#define TEMPERATURE_PIN 18
|
#define TEMPERATURE_PIN 18
|
||||||
#else //ESP8266 boards
|
#else //ESP8266 boards
|
||||||
#define TEMPERATURE_PIN 14
|
#define TEMPERATURE_PIN 14
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// the frequency to check temperature, 1 minute
|
// the frequency to check temperature, 1 minute
|
||||||
@ -23,16 +24,17 @@
|
|||||||
#define USERMOD_DALLASTEMPERATURE_FIRST_MEASUREMENT_AT 20000
|
#define USERMOD_DALLASTEMPERATURE_FIRST_MEASUREMENT_AT 20000
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
OneWire oneWire(TEMPERATURE_PIN);
|
|
||||||
DallasTemperature sensor(&oneWire);
|
|
||||||
|
|
||||||
class UsermodTemperature : public Usermod {
|
class UsermodTemperature : public Usermod {
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// The device's unique 64-bit serial code stored in on-board ROM.
|
|
||||||
// Reading directly from the sensor device address is faster than
|
bool initDone = false;
|
||||||
// reading from index. When reading by index, DallasTemperature
|
OneWire *oneWire;
|
||||||
// must first look up the device address at the specified index.
|
// GPIO pin used for sensor (with a default compile-time fallback)
|
||||||
DeviceAddress sensorDeviceAddress;
|
int8_t temperaturePin = TEMPERATURE_PIN;
|
||||||
|
// measurement unit (true==°C, false==°F)
|
||||||
|
bool degC = true;
|
||||||
|
unsigned long readingInterval = USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL;
|
||||||
// set last reading as "40 sec before boot", so first reading is taken after 20 sec
|
// set last reading as "40 sec before boot", so first reading is taken after 20 sec
|
||||||
unsigned long lastMeasurement = UINT32_MAX - (USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL - USERMOD_DALLASTEMPERATURE_FIRST_MEASUREMENT_AT);
|
unsigned long lastMeasurement = UINT32_MAX - (USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL - USERMOD_DALLASTEMPERATURE_FIRST_MEASUREMENT_AT);
|
||||||
// last time requestTemperatures was called
|
// last time requestTemperatures was called
|
||||||
@ -42,59 +44,96 @@ class UsermodTemperature : public Usermod {
|
|||||||
float temperature = -100; // default to -100, DS18B20 only goes down to -50C
|
float temperature = -100; // default to -100, DS18B20 only goes down to -50C
|
||||||
// indicates requestTemperatures has been called but the sensor measurement is not complete
|
// indicates requestTemperatures has been called but the sensor measurement is not complete
|
||||||
bool waitingForConversion = false;
|
bool waitingForConversion = false;
|
||||||
// flag to indicate we have finished the first getTemperature call
|
// flag to indicate we have finished the first readTemperature call
|
||||||
// allows this library to report to the user how long until the first
|
// allows this library to report to the user how long until the first
|
||||||
// measurement
|
// measurement
|
||||||
bool getTemperatureComplete = false;
|
bool readTemperatureComplete = false;
|
||||||
// flag set at startup if DS18B20 sensor not found, avoids trying to keep getting
|
// flag set at startup if DS18B20 sensor not found, avoids trying to keep getting
|
||||||
// temperature if flashed to a board without a sensor attached
|
// temperature if flashed to a board without a sensor attached
|
||||||
bool disabled = false;
|
bool disabled = false;
|
||||||
|
|
||||||
void requestTemperatures() {
|
// strings to reduce flash memory usage (used more than twice)
|
||||||
// there is requestTemperaturesByAddress however it
|
static const char _name[];
|
||||||
// appears to do more work,
|
static const char _enabled[];
|
||||||
// TODO: measure exection time difference
|
static const char _readInterval[];
|
||||||
sensor.requestTemperatures();
|
|
||||||
lastTemperaturesRequest = millis();
|
//Dallas sensor quick (& dirty) reading. Credit to - Author: Peter Scargill, August 17th, 2013
|
||||||
waitingForConversion = true;
|
int16_t readDallas() {
|
||||||
|
byte i;
|
||||||
|
byte data[2];
|
||||||
|
int16_t result; // raw data from sensor
|
||||||
|
oneWire->reset();
|
||||||
|
oneWire->write(0xCC); // skip ROM
|
||||||
|
oneWire->write(0xBE); // read (temperature) from EEPROM
|
||||||
|
for (i=0; i < 2; i++) data[i] = oneWire->read(); // first 2 bytes contain temperature
|
||||||
|
for (i=2; i < 8; i++) oneWire->read(); // read unused bytes
|
||||||
|
result = (data[1]<<8) | data[0];
|
||||||
|
result >>= 4; // 9-bit precision accurate to 1°C (/16)
|
||||||
|
if (data[1]&0x80) result |= 0xF000; // fix negative value
|
||||||
|
//if (data[0]&0x08) ++result;
|
||||||
|
oneWire->reset();
|
||||||
|
oneWire->write(0xCC); // skip ROM
|
||||||
|
oneWire->write(0x44,0); // request new temperature reading (without parasite power)
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void getTemperature() {
|
void requestTemperatures() {
|
||||||
if (strip.isUpdating()) return;
|
readDallas();
|
||||||
#ifdef USERMOD_DALLASTEMPERATURE_CELSIUS
|
lastTemperaturesRequest = millis();
|
||||||
temperature = sensor.getTempC(sensorDeviceAddress);
|
waitingForConversion = true;
|
||||||
#else
|
DEBUG_PRINTLN(F("Requested temperature."));
|
||||||
temperature = sensor.getTempF(sensorDeviceAddress);
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
|
void readTemperature() {
|
||||||
|
temperature = readDallas();
|
||||||
lastMeasurement = millis();
|
lastMeasurement = millis();
|
||||||
waitingForConversion = false;
|
waitingForConversion = false;
|
||||||
getTemperatureComplete = true;
|
readTemperatureComplete = true;
|
||||||
|
DEBUG_PRINTF("Read temperature %2.1f.\n", temperature);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool findSensor() {
|
||||||
|
DEBUG_PRINTLN(F("Searching for sensor..."));
|
||||||
|
uint8_t deviceAddress[8] = {0,0,0,0,0,0,0,0};
|
||||||
|
// find out if we have DS18xxx sensor attached
|
||||||
|
oneWire->reset_search();
|
||||||
|
while (oneWire->search(deviceAddress)) {
|
||||||
|
if (oneWire->crc8(deviceAddress, 7) == deviceAddress[7]) {
|
||||||
|
switch (deviceAddress[0]) {
|
||||||
|
case 0x10: // DS18S20
|
||||||
|
case 0x22: // DS18B20
|
||||||
|
case 0x28: // DS1822
|
||||||
|
case 0x3B: // DS1825
|
||||||
|
case 0x42: // DS28EA00
|
||||||
|
DEBUG_PRINTLN(F("Sensor found."));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
sensor.begin();
|
int retries = 10;
|
||||||
|
// pin retrieved from cfg.json (readFromConfig()) prior to running setup()
|
||||||
// get the unique 64-bit serial code stored in on-board ROM
|
if (!pinManager.allocatePin(temperaturePin,false)) {
|
||||||
// if getAddress returns false, the sensor was not found
|
temperaturePin = -1; // allocation failed
|
||||||
disabled = !sensor.getAddress(sensorDeviceAddress, 0);
|
|
||||||
|
|
||||||
if (!disabled) {
|
|
||||||
DEBUG_PRINTLN(F("Dallas Temperature found"));
|
|
||||||
// set the resolution for this specific device
|
|
||||||
sensor.setResolution(sensorDeviceAddress, 9, true);
|
|
||||||
// do not block waiting for reading
|
|
||||||
sensor.setWaitForConversion(false);
|
|
||||||
// allocate pin & prevent other use
|
|
||||||
if (!pinManager.allocatePin(TEMPERATURE_PIN,false))
|
|
||||||
disabled = true;
|
disabled = true;
|
||||||
|
DEBUG_PRINTLN(F("Temperature pin allocation failed."));
|
||||||
} else {
|
} else {
|
||||||
DEBUG_PRINTLN(F("Dallas Temperature not found"));
|
if (!disabled) {
|
||||||
|
// config says we are enabled
|
||||||
|
oneWire = new OneWire(temperaturePin);
|
||||||
|
if (!oneWire->reset())
|
||||||
|
disabled = true; // resetting 1-Wire bus yielded an error
|
||||||
|
else
|
||||||
|
while ((disabled=!findSensor()) && retries--) delay(25); // try to find sensor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
initDone = true;
|
||||||
|
}
|
||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
if (disabled || strip.isUpdating()) return;
|
if (disabled || strip.isUpdating()) return;
|
||||||
@ -104,23 +143,21 @@ class UsermodTemperature : public Usermod {
|
|||||||
// check to see if we are due for taking a measurement
|
// check to see if we are due for taking a measurement
|
||||||
// lastMeasurement will not be updated until the conversion
|
// lastMeasurement will not be updated until the conversion
|
||||||
// is complete the the reading is finished
|
// is complete the the reading is finished
|
||||||
if (now - lastMeasurement < USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL) return;
|
if (now - lastMeasurement < readingInterval) return;
|
||||||
|
|
||||||
// we are due for a measurement, if we are not already waiting
|
// we are due for a measurement, if we are not already waiting
|
||||||
// for a conversion to complete, then make a new request for temps
|
// for a conversion to complete, then make a new request for temps
|
||||||
if (!waitingForConversion)
|
if (!waitingForConversion) {
|
||||||
{
|
|
||||||
requestTemperatures();
|
requestTemperatures();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// we were waiting for a conversion to complete, have we waited log enough?
|
// we were waiting for a conversion to complete, have we waited log enough?
|
||||||
if (now - lastTemperaturesRequest >= 94 /* 93.75ms per the datasheet */)
|
if (now - lastTemperaturesRequest >= 100 /* 93.75ms per the datasheet but can be up to 750ms */) {
|
||||||
{
|
readTemperature();
|
||||||
getTemperature();
|
|
||||||
|
|
||||||
if (WLED_MQTT_CONNECTED) {
|
if (WLED_MQTT_CONNECTED) {
|
||||||
char subuf[45];
|
char subuf[64];
|
||||||
strcpy(subuf, mqttDeviceTopic);
|
strcpy(subuf, mqttDeviceTopic);
|
||||||
if (-100 <= temperature) {
|
if (-100 <= temperature) {
|
||||||
// dont publish super low temperature as the graph will get messed up
|
// dont publish super low temperature as the graph will get messed up
|
||||||
@ -128,6 +165,8 @@ class UsermodTemperature : public Usermod {
|
|||||||
// reading the sensor
|
// reading the sensor
|
||||||
strcat_P(subuf, PSTR("/temperature"));
|
strcat_P(subuf, PSTR("/temperature"));
|
||||||
mqtt->publish(subuf, 0, true, String(temperature).c_str());
|
mqtt->publish(subuf, 0, true, String(temperature).c_str());
|
||||||
|
strcat_P(subuf, PSTR("_f"));
|
||||||
|
mqtt->publish(subuf, 0, true, String((float)temperature * 1.8f + 32).c_str());
|
||||||
} else {
|
} else {
|
||||||
// publish something else to indicate status?
|
// publish something else to indicate status?
|
||||||
}
|
}
|
||||||
@ -135,16 +174,32 @@ class UsermodTemperature : public Usermod {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* API calls te enable data exchange between WLED modules
|
||||||
|
*/
|
||||||
|
inline float getTemperatureC() {
|
||||||
|
return (float)temperature;
|
||||||
|
}
|
||||||
|
inline float getTemperatureF() {
|
||||||
|
return (float)temperature * 1.8f + 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
|
||||||
|
* Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI.
|
||||||
|
* Below it is shown how this could be used for e.g. a light sensor
|
||||||
|
*/
|
||||||
void addToJsonInfo(JsonObject& root) {
|
void addToJsonInfo(JsonObject& root) {
|
||||||
// dont add temperature to info if we are disabled
|
// dont add temperature to info if we are disabled
|
||||||
if (disabled) return;
|
if (disabled) return;
|
||||||
|
|
||||||
JsonObject user = root[F("u")];
|
JsonObject user = root["u"];
|
||||||
if (user.isNull()) user = root.createNestedObject(F("u"));
|
if (user.isNull()) user = root.createNestedObject("u");
|
||||||
|
|
||||||
JsonArray temp = user.createNestedArray(F("Temperature"));
|
JsonArray temp = user.createNestedArray(FPSTR(_name));
|
||||||
|
//temp.add(F("Loaded."));
|
||||||
|
|
||||||
if (!getTemperatureComplete) {
|
if (!readTemperatureComplete) {
|
||||||
// if we haven't read the sensor yet, let the user know
|
// if we haven't read the sensor yet, let the user know
|
||||||
// that we are still waiting for the first measurement
|
// that we are still waiting for the first measurement
|
||||||
temp.add((USERMOD_DALLASTEMPERATURE_FIRST_MEASUREMENT_AT - millis()) / 1000);
|
temp.add((USERMOD_DALLASTEMPERATURE_FIRST_MEASUREMENT_AT - millis()) / 1000);
|
||||||
@ -158,12 +213,85 @@ class UsermodTemperature : public Usermod {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
temp.add(temperature);
|
temp.add(degC ? temperature : (float)temperature * 1.8f + 32);
|
||||||
#ifdef USERMOD_DALLASTEMPERATURE_CELSIUS
|
if (degC) temp.add(F("°C"));
|
||||||
temp.add(F("°C"));
|
else temp.add(F("°F"));
|
||||||
#else
|
}
|
||||||
temp.add(F("°F"));
|
|
||||||
#endif
|
/**
|
||||||
|
* 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
|
||||||
|
* Read "<usermodname>_<usermodparam>" from json state and and change settings (i.e. GPIO pin) used.
|
||||||
|
*/
|
||||||
|
//void readFromJsonState(JsonObject &root) {
|
||||||
|
// if (!initDone) return; // prevent crash on boot applyPreset()
|
||||||
|
//}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* addToConfig() (called from set.cpp) stores persistent properties to cfg.json
|
||||||
|
*/
|
||||||
|
void addToConfig(JsonObject &root) {
|
||||||
|
// we add JSON object: {"Temperature": {"pin": 0, "degC": true}}
|
||||||
|
JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname
|
||||||
|
top[FPSTR(_enabled)] = !disabled;
|
||||||
|
top["pin"] = temperaturePin; // usermodparam
|
||||||
|
top["degC"] = degC; // usermodparam
|
||||||
|
top[FPSTR(_readInterval)] = readingInterval / 1000;
|
||||||
|
DEBUG_PRINTLN(F("Temperature config saved."));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* readFromConfig() is called before setup() to populate properties from values stored in cfg.json
|
||||||
|
*/
|
||||||
|
void readFromConfig(JsonObject &root) {
|
||||||
|
// we look for JSON object: {"Temperature": {"pin": 0, "degC": true}}
|
||||||
|
JsonObject top = root[FPSTR(_name)];
|
||||||
|
int8_t newTemperaturePin = temperaturePin;
|
||||||
|
|
||||||
|
if (!top.isNull() && top["pin"] != nullptr) {
|
||||||
|
if (top[FPSTR(_enabled)].is<bool>()) {
|
||||||
|
disabled = !top[FPSTR(_enabled)].as<bool>();
|
||||||
|
} else {
|
||||||
|
String str = top[FPSTR(_enabled)]; // checkbox -> off or on
|
||||||
|
disabled = (bool)(str=="off"); // off is guaranteed to be present
|
||||||
|
}
|
||||||
|
newTemperaturePin = min(39,max(-1,top["pin"].as<int>()));
|
||||||
|
if (top["degC"].is<bool>()) {
|
||||||
|
// reading from cfg.json
|
||||||
|
degC = top["degC"].as<bool>();
|
||||||
|
} else {
|
||||||
|
// new configuration from set.cpp
|
||||||
|
String str = top["degC"]; // checkbox -> off or on
|
||||||
|
degC = (bool)(str!="off"); // off is guaranteed to be present
|
||||||
|
}
|
||||||
|
readingInterval = min(120,max(10,top[FPSTR(_readInterval)].as<int>())) * 1000; // convert to ms
|
||||||
|
DEBUG_PRINTLN(F("Temperature config (re)loaded."));
|
||||||
|
} else {
|
||||||
|
DEBUG_PRINTLN(F("No config found. (Using defaults.)"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!initDone) {
|
||||||
|
// first run: reading from cfg.json
|
||||||
|
temperaturePin = newTemperaturePin;
|
||||||
|
} else {
|
||||||
|
// changing paramters from settings page
|
||||||
|
if (newTemperaturePin != temperaturePin) {
|
||||||
|
// deallocate pin and release memory
|
||||||
|
delete oneWire;
|
||||||
|
pinManager.deallocatePin(temperaturePin);
|
||||||
|
temperaturePin = newTemperaturePin;
|
||||||
|
// initialise
|
||||||
|
setup();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t getId()
|
uint16_t getId()
|
||||||
@ -171,3 +299,8 @@ class UsermodTemperature : public Usermod {
|
|||||||
return USERMOD_ID_TEMPERATURE;
|
return USERMOD_ID_TEMPERATURE;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// strings to reduce flash memory usage (used more than twice)
|
||||||
|
const char UsermodTemperature::_name[] PROGMEM = "Temperature";
|
||||||
|
const char UsermodTemperature::_enabled[] PROGMEM = "enabled";
|
||||||
|
const char UsermodTemperature::_readInterval[] PROGMEM = "read-interval-s";
|
55
usermods/multi_relay/readme.md
Normal file
55
usermods/multi_relay/readme.md
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
# Multi Relay
|
||||||
|
|
||||||
|
This usermod-v2 modification allows the connection of multiple relays each with individual delay and on/off mode.
|
||||||
|
|
||||||
|
## Usermod installation
|
||||||
|
|
||||||
|
1. Register the usermod by adding `#include "../usermods/multi_relay/usermod_multi_relay.h"` at the top and `usermods.add(new MultiRelay());` at the bottom of `usermods_list.cpp`.
|
||||||
|
or
|
||||||
|
2. Use `#define USERMOD_MULTI_RELAY` in wled.h or `-D USERMOD_MULTI_RELAY`in your platformio.ini
|
||||||
|
|
||||||
|
You can override the default maximum number (4) of relays by defining MULTI_RELAY_MAX_RELAYS.
|
||||||
|
|
||||||
|
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 "../usermods/usermod_multi_relay.h"
|
||||||
|
|
||||||
|
void registerUsermods()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Add your usermod class name here
|
||||||
|
* || || ||
|
||||||
|
* \/ \/ \/
|
||||||
|
*/
|
||||||
|
//usermods.add(new MyExampleUsermod());
|
||||||
|
//usermods.add(new UsermodTemperature());
|
||||||
|
usermods.add(new MultiRelay());
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Usermod can be configured in Usermods settings page.
|
||||||
|
|
||||||
|
If there is no MultiRelay section, just save current configuration and re-open Usermods settings page.
|
||||||
|
|
||||||
|
Have fun - @blazoncek
|
||||||
|
|
||||||
|
## Change log
|
||||||
|
2021-04
|
||||||
|
* First implementation.
|
434
usermods/multi_relay/usermod_multi_relay.h
Normal file
434
usermods/multi_relay/usermod_multi_relay.h
Normal file
@ -0,0 +1,434 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "wled.h"
|
||||||
|
|
||||||
|
#ifndef MULTI_RELAY_MAX_RELAYS
|
||||||
|
#define MULTI_RELAY_MAX_RELAYS 4
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define ON true
|
||||||
|
#define OFF false
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This usermod handles multiple relay outputs.
|
||||||
|
* These outputs complement built-in relay output in a way that the activation can be delayed.
|
||||||
|
* They can also activate/deactivate in reverse logic independently.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct relay_t {
|
||||||
|
int8_t pin;
|
||||||
|
bool active;
|
||||||
|
bool mode;
|
||||||
|
bool state;
|
||||||
|
bool external;
|
||||||
|
uint16_t delay;
|
||||||
|
} Relay;
|
||||||
|
|
||||||
|
|
||||||
|
class MultiRelay : public Usermod {
|
||||||
|
|
||||||
|
private:
|
||||||
|
// array of relays
|
||||||
|
Relay _relay[MULTI_RELAY_MAX_RELAYS];
|
||||||
|
|
||||||
|
// switch timer start time
|
||||||
|
uint32_t _switchTimerStart = 0;
|
||||||
|
// old brightness
|
||||||
|
uint8_t _oldBrightness = 0;
|
||||||
|
|
||||||
|
// usermod enabled
|
||||||
|
bool enabled = false; // needs to be configured (no default config)
|
||||||
|
// status of initialisation
|
||||||
|
bool initDone = false;
|
||||||
|
|
||||||
|
// strings to reduce flash memory usage (used more than twice)
|
||||||
|
static const char _name[];
|
||||||
|
static const char _enabled[];
|
||||||
|
static const char _relay_str[];
|
||||||
|
static const char _delay_str[];
|
||||||
|
static const char _activeHigh[];
|
||||||
|
static const char _external[];
|
||||||
|
|
||||||
|
|
||||||
|
void publishMqtt(const char* state, int relay) {
|
||||||
|
//Check if MQTT Connected, otherwise it will crash the 8266
|
||||||
|
if (WLED_MQTT_CONNECTED){
|
||||||
|
char subuf[64];
|
||||||
|
sprintf_P(subuf, PSTR("%s/relay/%d"), mqttDeviceTopic, relay);
|
||||||
|
mqtt->publish(subuf, 0, true, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* switch off the strip if the delay has elapsed
|
||||||
|
*/
|
||||||
|
void handleOffTimer() {
|
||||||
|
bool activeRelays = false;
|
||||||
|
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||||
|
if (_relay[i].active && _switchTimerStart > 0 && millis() - _switchTimerStart > (_relay[i].delay*1000)) {
|
||||||
|
if (!_relay[i].external) toggleRelay(i);
|
||||||
|
_relay[i].active = false;
|
||||||
|
}
|
||||||
|
activeRelays = activeRelays || _relay[i].active;
|
||||||
|
}
|
||||||
|
if (!activeRelays) _switchTimerStart = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP API handler
|
||||||
|
* borrowed from:
|
||||||
|
* https://github.com/gsieben/WLED/blob/master/usermods/GeoGab-Relays/usermod_GeoGab.h
|
||||||
|
*/
|
||||||
|
#define GEOGABVERSION "0.1.3"
|
||||||
|
void InitHtmlAPIHandle() { // https://github.com/me-no-dev/ESPAsyncWebServer
|
||||||
|
DEBUG_PRINTLN(F("Relays: Initialize HTML API"));
|
||||||
|
|
||||||
|
server.on("/relays", HTTP_GET, [this](AsyncWebServerRequest *request) {
|
||||||
|
DEBUG_PRINTLN("Relays: HTML API");
|
||||||
|
String janswer;
|
||||||
|
String error = "";
|
||||||
|
int params = request->params();
|
||||||
|
janswer = F("{\"NoOfRelays\":");
|
||||||
|
janswer += String(MULTI_RELAY_MAX_RELAYS) + ",";
|
||||||
|
|
||||||
|
if (getActiveRelayCount()) {
|
||||||
|
// Commands
|
||||||
|
if(request->hasParam("switch")) {
|
||||||
|
/**** Switch ****/
|
||||||
|
AsyncWebParameter* p = request->getParam("switch");
|
||||||
|
// Get Values
|
||||||
|
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||||
|
int value = getValue(p->value(), ',', i);
|
||||||
|
if (value==-1) {
|
||||||
|
error = F("There must be as much arugments as relays");
|
||||||
|
} else {
|
||||||
|
// Switch
|
||||||
|
if (_relay[i].external) switchRelay(i, (bool)value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if(request->hasParam("toggle")) {
|
||||||
|
/**** Toggle ****/
|
||||||
|
AsyncWebParameter* p = request->getParam("toggle");
|
||||||
|
// Get Values
|
||||||
|
for (int i=0;i<MULTI_RELAY_MAX_RELAYS;i++) {
|
||||||
|
int value = getValue(p->value(), ',', i);
|
||||||
|
if (value==-1) {
|
||||||
|
error = F("There must be as mutch arugments as relays");
|
||||||
|
} else {
|
||||||
|
// Toggle
|
||||||
|
if (value && _relay[i].external) toggleRelay(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error = F("No valid command found");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error = F("No active relays");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status response
|
||||||
|
char sbuf[16];
|
||||||
|
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||||
|
sprintf_P(sbuf, PSTR("\"%d\":%d,"), i, (_relay[i].pin<0 ? -1 : (int)_relay[i].state));
|
||||||
|
janswer += sbuf;
|
||||||
|
}
|
||||||
|
janswer += F("\"error\":\"");
|
||||||
|
janswer += error;
|
||||||
|
janswer += F("\",");
|
||||||
|
janswer += F("\"SW Version\":\"");
|
||||||
|
janswer += String(GEOGABVERSION);
|
||||||
|
janswer += F("\"}");
|
||||||
|
request->send(200, "application/json", janswer);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
int getValue(String data, char separator, int index) {
|
||||||
|
int found = 0;
|
||||||
|
int strIndex[] = {0, -1};
|
||||||
|
int maxIndex = data.length()-1;
|
||||||
|
|
||||||
|
for(int i=0; i<=maxIndex && found<=index; i++){
|
||||||
|
if(data.charAt(i)==separator || i==maxIndex){
|
||||||
|
found++;
|
||||||
|
strIndex[0] = strIndex[1]+1;
|
||||||
|
strIndex[1] = (i == maxIndex) ? i+1 : i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return found>index ? data.substring(strIndex[0], strIndex[1]).toInt() : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* constructor
|
||||||
|
*/
|
||||||
|
MultiRelay() {
|
||||||
|
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||||
|
_relay[i].pin = -1;
|
||||||
|
_relay[i].delay = 0;
|
||||||
|
_relay[i].mode = false;
|
||||||
|
_relay[i].active = false;
|
||||||
|
_relay[i].state = false;
|
||||||
|
_relay[i].external = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* desctructor
|
||||||
|
*/
|
||||||
|
~MultiRelay() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable/Disable the usermod
|
||||||
|
*/
|
||||||
|
inline void enable(bool enable) { enabled = enable; }
|
||||||
|
/**
|
||||||
|
* Get usermod enabled/disabled state
|
||||||
|
*/
|
||||||
|
inline bool isEnabled() { return enabled; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* switch relay on/off
|
||||||
|
*/
|
||||||
|
void switchRelay(uint8_t relay, bool mode) {
|
||||||
|
if (relay>=MULTI_RELAY_MAX_RELAYS || _relay[relay].pin<0) return;
|
||||||
|
_relay[relay].state = mode;
|
||||||
|
pinMode(_relay[relay].pin, OUTPUT);
|
||||||
|
digitalWrite(_relay[relay].pin, mode ? !_relay[relay].mode : _relay[relay].mode);
|
||||||
|
publishMqtt(mode ? "on" : "off", relay);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* toggle relay
|
||||||
|
*/
|
||||||
|
inline void toggleRelay(uint8_t relay) {
|
||||||
|
switchRelay(relay, !_relay[relay].state);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t getActiveRelayCount() {
|
||||||
|
uint8_t count = 0;
|
||||||
|
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) if (_relay[i].pin>=0) count++;
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Functions called by WLED
|
||||||
|
|
||||||
|
/**
|
||||||
|
* handling of MQTT message
|
||||||
|
* topic only contains stripped topic (part after /wled/MAC)
|
||||||
|
* topic should look like: /relay/X/command; where X is relay number, 0 based
|
||||||
|
*/
|
||||||
|
bool onMqttMessage(char* topic, char* payload) {
|
||||||
|
if (strlen(topic) > 8 && strncmp_P(topic, PSTR("/relay/"), 7) == 0 && strncmp_P(topic+8, PSTR("/command"), 8) == 0) {
|
||||||
|
uint8_t relay = strtoul(topic+7, NULL, 10);
|
||||||
|
if (relay<MULTI_RELAY_MAX_RELAYS) {
|
||||||
|
String action = payload;
|
||||||
|
if (action == "on") {
|
||||||
|
if (_relay[relay].external) switchRelay(relay, true);
|
||||||
|
return true;
|
||||||
|
} else if (action == "off") {
|
||||||
|
if (_relay[relay].external) switchRelay(relay, false);
|
||||||
|
return true;
|
||||||
|
} else if (action == "toggle") {
|
||||||
|
if (_relay[relay].external) toggleRelay(relay);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* subscribe to MQTT topic for controlling relays
|
||||||
|
*/
|
||||||
|
void onMqttConnect(bool sessionPresent) {
|
||||||
|
//(re)subscribe to required topics
|
||||||
|
char subuf[64];
|
||||||
|
if (mqttDeviceTopic[0] != 0) {
|
||||||
|
strcpy(subuf, mqttDeviceTopic);
|
||||||
|
strcat_P(subuf, PSTR("/relay/#"));
|
||||||
|
mqtt->subscribe(subuf, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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() {
|
||||||
|
// pins retrieved from cfg.json (readFromConfig()) prior to running setup()
|
||||||
|
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||||
|
if (_relay[i].pin<0) continue;
|
||||||
|
if (!pinManager.allocatePin(_relay[i].pin,true)) {
|
||||||
|
_relay[i].pin = -1; // allocation failed
|
||||||
|
} else {
|
||||||
|
switchRelay(i, _relay[i].state = (bool)bri);
|
||||||
|
_relay[i].active = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_oldBrightness = bri;
|
||||||
|
initDone = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* connected() is called every time the WiFi is (re)connected
|
||||||
|
* Use it to initialize network interfaces
|
||||||
|
*/
|
||||||
|
void connected() {
|
||||||
|
InitHtmlAPIHandle();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* loop() is called continuously. Here you can check for events, read sensors, etc.
|
||||||
|
*/
|
||||||
|
void loop() {
|
||||||
|
if (!enabled) return;
|
||||||
|
|
||||||
|
static unsigned long lastUpdate = 0;
|
||||||
|
if (millis() - lastUpdate < 200) return; // update only 5 times/s
|
||||||
|
lastUpdate = millis();
|
||||||
|
|
||||||
|
//set relay when LEDs turn on
|
||||||
|
if (_oldBrightness != bri) {
|
||||||
|
_oldBrightness = bri;
|
||||||
|
_switchTimerStart = millis();
|
||||||
|
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||||
|
if (_relay[i].pin>=0) _relay[i].active = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleOffTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
|
||||||
|
*/
|
||||||
|
void addToJsonInfo(JsonObject &root) {
|
||||||
|
if (enabled) {
|
||||||
|
JsonObject user = root["u"];
|
||||||
|
if (user.isNull())
|
||||||
|
user = root.createNestedObject("u");
|
||||||
|
|
||||||
|
JsonArray infoArr = user.createNestedArray(F("Number of relays")); //name
|
||||||
|
infoArr.add(String(getActiveRelayCount()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* provide the changeable values
|
||||||
|
*/
|
||||||
|
void addToConfig(JsonObject &root) {
|
||||||
|
JsonObject top = root.createNestedObject(FPSTR(_name));
|
||||||
|
|
||||||
|
top[FPSTR(_enabled)] = enabled;
|
||||||
|
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||||
|
String parName = FPSTR(_relay_str); parName += "-"; parName += i; parName += "-";
|
||||||
|
top[parName+"pin"] = _relay[i].pin;
|
||||||
|
top[parName+FPSTR(_activeHigh)] = _relay[i].mode;
|
||||||
|
top[parName+FPSTR(_delay_str)] = _relay[i].delay;
|
||||||
|
top[parName+FPSTR(_external)] = _relay[i].external;
|
||||||
|
}
|
||||||
|
DEBUG_PRINTLN(F("MultiRelay config saved."));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* restore the changeable values
|
||||||
|
* readFromConfig() is called before setup() to populate properties from values stored in cfg.json
|
||||||
|
*/
|
||||||
|
void readFromConfig(JsonObject &root) {
|
||||||
|
int8_t oldPin[MULTI_RELAY_MAX_RELAYS];
|
||||||
|
|
||||||
|
JsonObject top = root[FPSTR(_name)];
|
||||||
|
if (top.isNull()) return;
|
||||||
|
|
||||||
|
if (top[FPSTR(_enabled)] != nullptr) {
|
||||||
|
if (top[FPSTR(_enabled)].is<bool>()) {
|
||||||
|
enabled = top[FPSTR(_enabled)].as<bool>(); // reading from cfg.json
|
||||||
|
} else {
|
||||||
|
// change from settings page
|
||||||
|
String str = top[FPSTR(_enabled)]; // checkbox -> off or on
|
||||||
|
enabled = (bool)(str!="off"); // off is guaranteed to be present
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||||
|
String parName = FPSTR(_relay_str); parName += "-"; parName += i; parName += "-";
|
||||||
|
|
||||||
|
oldPin[i] = _relay[i].pin;
|
||||||
|
if (top[parName+"pin"] != nullptr) _relay[i].pin = min(39,max(-1,top[parName+"pin"].as<int>()));
|
||||||
|
|
||||||
|
if (top[parName+FPSTR(_activeHigh)] != nullptr) {
|
||||||
|
if (top[parName+FPSTR(_activeHigh)].is<bool>()) {
|
||||||
|
_relay[i].mode = top[parName+FPSTR(_activeHigh)].as<bool>(); // reading from cfg.json
|
||||||
|
} else {
|
||||||
|
// change from settings page
|
||||||
|
String str = top[parName+FPSTR(_activeHigh)]; // checkbox -> off or on
|
||||||
|
_relay[i].mode = (bool)(str!="off"); // off is guaranteed to be present
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (top[parName+FPSTR(_external)] != nullptr) {
|
||||||
|
if (top[parName+FPSTR(_external)].is<bool>()) {
|
||||||
|
_relay[i].external = top[parName+FPSTR(_external)].as<bool>(); // reading from cfg.json
|
||||||
|
} else {
|
||||||
|
// change from settings page
|
||||||
|
String str = top[parName+FPSTR(_external)]; // checkbox -> off or on
|
||||||
|
_relay[i].external = (bool)(str!="off"); // off is guaranteed to be present
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_relay[i].delay = min(600,max(0,abs(top[parName+FPSTR(_delay_str)].as<int>())));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!initDone) {
|
||||||
|
// reading config prior to setup()
|
||||||
|
DEBUG_PRINTLN(F("MultiRelay config loaded."));
|
||||||
|
} else {
|
||||||
|
// deallocate all pins 1st
|
||||||
|
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++)
|
||||||
|
if (oldPin[i]>=0) {
|
||||||
|
pinManager.deallocatePin(oldPin[i]);
|
||||||
|
}
|
||||||
|
// allocate new pins
|
||||||
|
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||||
|
if (_relay[i].pin>=0 && pinManager.allocatePin(_relay[i].pin,true)) {
|
||||||
|
if (!_relay[i].external) switchRelay(i, _relay[i].state = (bool)bri);
|
||||||
|
} else {
|
||||||
|
_relay[i].pin = -1;
|
||||||
|
}
|
||||||
|
_relay[i].active = false;
|
||||||
|
}
|
||||||
|
DEBUG_PRINTLN(F("MultiRelay 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.
|
||||||
|
*/
|
||||||
|
uint16_t getId()
|
||||||
|
{
|
||||||
|
return USERMOD_ID_MULTI_RELAY;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// strings to reduce flash memory usage (used more than twice)
|
||||||
|
const char MultiRelay::_name[] PROGMEM = "MultiRelay";
|
||||||
|
const char MultiRelay::_enabled[] PROGMEM = "enabled";
|
||||||
|
const char MultiRelay::_relay_str[] PROGMEM = "relay";
|
||||||
|
const char MultiRelay::_delay_str[] PROGMEM = "delay-s";
|
||||||
|
const char MultiRelay::_activeHigh[] PROGMEM = "active-high";
|
||||||
|
const char MultiRelay::_external[] PROGMEM = "external";
|
@ -29,9 +29,9 @@ This file should be placed in the same directory as `platformio.ini`.
|
|||||||
### Define Your Options
|
### Define Your Options
|
||||||
|
|
||||||
* `USERMOD_AUTO_SAVE` - define this to have this the Auto Save usermod included wled00\usermods_list.cpp
|
* `USERMOD_AUTO_SAVE` - define this to have this the Auto Save usermod included wled00\usermods_list.cpp
|
||||||
* `USERMOD_FOUR_LINE_DISLAY` - define this to have this the Four Line Display mod included wled00\usermods_list.cpp - also tells this usermod that the display is available (see the Four Line Display usermod `readme.md` for more details)
|
* `USERMOD_FOUR_LINE_DISPLAY` - define this to have this the Four Line Display mod included wled00\usermods_list.cpp - also tells this usermod that the display is available (see the Four Line Display usermod `readme.md` for more details)
|
||||||
* `AUTOSAVE_SETTLE_MS` - Minimum time to wave before auto saving, defaults to 10000 (10s)
|
|
||||||
* `AUTOSAVE_PRESET_NUM` - Preset number to auto-save to, auto-load at startup from, defaults to 99
|
You can configure auto-save parameters using Usermods settings page.
|
||||||
|
|
||||||
### PlatformIO requirements
|
### PlatformIO requirements
|
||||||
|
|
||||||
@ -43,3 +43,5 @@ Note: the Four Line Display usermod requires the libraries `U8g2` and `Wire`.
|
|||||||
|
|
||||||
2021-02
|
2021-02
|
||||||
* First public release
|
* First public release
|
||||||
|
2021-04
|
||||||
|
* Adaptation for runtime configuration.
|
@ -2,9 +2,8 @@
|
|||||||
|
|
||||||
#include "wled.h"
|
#include "wled.h"
|
||||||
|
|
||||||
//
|
|
||||||
// v2 Usermod to automatically save settings
|
// v2 Usermod to automatically save settings
|
||||||
// to preset number AUTOSAVE_PRESET_NUM after a change to any of
|
// to configurable preset after a change to any of
|
||||||
//
|
//
|
||||||
// * brightness
|
// * brightness
|
||||||
// * effect speed
|
// * effect speed
|
||||||
@ -12,45 +11,34 @@
|
|||||||
// * mode (effect)
|
// * mode (effect)
|
||||||
// * palette
|
// * palette
|
||||||
//
|
//
|
||||||
// but it will wait for AUTOSAVE_SETTLE_MS milliseconds, a "settle"
|
// but it will wait for configurable number of seconds, a "settle"
|
||||||
// period in case there are other changes (any change will
|
// period in case there are other changes (any change will
|
||||||
// extend the "settle" window).
|
// extend the "settle" window).
|
||||||
//
|
//
|
||||||
// It will additionally load preset AUTOSAVE_PRESET_NUM at startup.
|
// It can be configured to load auto saved preset at startup,
|
||||||
// during the first `loop()`. Reasoning below.
|
// during the first `loop()`.
|
||||||
//
|
//
|
||||||
// AutoSaveUsermod is standalone, but if FourLineDisplayUsermod
|
// AutoSaveUsermod is standalone, but if FourLineDisplayUsermod
|
||||||
// is installed, it will notify the user of the saved changes.
|
// is installed, it will notify the user of the saved changes.
|
||||||
//
|
|
||||||
// Note: I don't love that WLED doesn't respect the brightness
|
|
||||||
// of the preset being auto loaded, so the AutoSaveUsermod
|
|
||||||
// will set the AUTOSAVE_PRESET_NUM preset in the first loop,
|
|
||||||
// so brightness IS honored. This means WLED will effectively
|
|
||||||
// ignore Default brightness and Apply N preset at boot when
|
|
||||||
// the AutoSaveUsermod is installed.
|
|
||||||
|
|
||||||
//How long to wait after settings change to auto-save
|
// format: "~ MM-DD HH:MM:SS ~"
|
||||||
#ifndef AUTOSAVE_SETTLE_MS
|
|
||||||
#define AUTOSAVE_SETTLE_MS 10*1000
|
|
||||||
#endif
|
|
||||||
|
|
||||||
//Preset number to save to
|
|
||||||
#ifndef AUTOSAVE_PRESET_NUM
|
|
||||||
#define AUTOSAVE_PRESET_NUM 99
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// "Auto save MM-DD HH:MM:SS"
|
|
||||||
#define PRESET_NAME_BUFFER_SIZE 25
|
#define PRESET_NAME_BUFFER_SIZE 25
|
||||||
|
|
||||||
class AutoSaveUsermod : public Usermod {
|
class AutoSaveUsermod : public Usermod {
|
||||||
private:
|
|
||||||
// If we've detected the need to auto save, this will
|
|
||||||
// be non zero.
|
|
||||||
unsigned long autoSaveAfter = 0;
|
|
||||||
|
|
||||||
char presetNameBuffer[PRESET_NAME_BUFFER_SIZE];
|
private:
|
||||||
|
|
||||||
bool firstLoop = true;
|
bool firstLoop = true;
|
||||||
|
bool initDone = false;
|
||||||
|
bool enabled = true;
|
||||||
|
|
||||||
|
// configurable parameters
|
||||||
|
unsigned long autoSaveAfterSec = 15; // 15s by default
|
||||||
|
uint8_t autoSavePreset = 250; // last possible preset
|
||||||
|
bool applyAutoSaveOnBoot = false; // do we load auto-saved preset on boot?
|
||||||
|
|
||||||
|
// If we've detected the need to auto save, this will be non zero.
|
||||||
|
unsigned long autoSaveAfter = 0;
|
||||||
|
|
||||||
uint8_t knownBrightness = 0;
|
uint8_t knownBrightness = 0;
|
||||||
uint8_t knownEffectSpeed = 0;
|
uint8_t knownEffectSpeed = 0;
|
||||||
@ -58,35 +46,65 @@ class AutoSaveUsermod : public Usermod {
|
|||||||
uint8_t knownMode = 0;
|
uint8_t knownMode = 0;
|
||||||
uint8_t knownPalette = 0;
|
uint8_t knownPalette = 0;
|
||||||
|
|
||||||
#ifdef USERMOD_FOUR_LINE_DISLAY
|
#ifdef USERMOD_FOUR_LINE_DISPLAY
|
||||||
FourLineDisplayUsermod* display;
|
FourLineDisplayUsermod* display;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// strings to reduce flash memory usage (used more than twice)
|
||||||
|
static const char _name[];
|
||||||
|
static const char _autoSaveEnabled[];
|
||||||
|
static const char _autoSaveAfterSec[];
|
||||||
|
static const char _autoSavePreset[];
|
||||||
|
static const char _autoSaveApplyOnBoot[];
|
||||||
|
|
||||||
|
void inline saveSettings() {
|
||||||
|
char presetNameBuffer[PRESET_NAME_BUFFER_SIZE];
|
||||||
|
updateLocalTime();
|
||||||
|
sprintf_P(presetNameBuffer,
|
||||||
|
PSTR("~ %02d-%02d %02d:%02d:%02d ~"),
|
||||||
|
month(localTime), day(localTime),
|
||||||
|
hour(localTime), minute(localTime), second(localTime));
|
||||||
|
savePreset(autoSavePreset, true, presetNameBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void inline displayOverlay() {
|
||||||
|
#ifdef USERMOD_FOUR_LINE_DISPLAY
|
||||||
|
if (display != nullptr) {
|
||||||
|
display->wakeDisplay();
|
||||||
|
display->overlay("Settings", "Auto Saved", 1500);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
// gets called once at boot. Do all initialization that doesn't depend on
|
// gets called once at boot. Do all initialization that doesn't depend on
|
||||||
// network here
|
// network here
|
||||||
void setup() {
|
void setup() {
|
||||||
#ifdef USERMOD_FOUR_LINE_DISLAY
|
#ifdef USERMOD_FOUR_LINE_DISPLAY
|
||||||
// This Usermod has enhanced funcionality if
|
// This Usermod has enhanced funcionality if
|
||||||
// FourLineDisplayUsermod is available.
|
// FourLineDisplayUsermod is available.
|
||||||
display = (FourLineDisplayUsermod*) usermods.lookup(USERMOD_ID_FOUR_LINE_DISP);
|
display = (FourLineDisplayUsermod*) usermods.lookup(USERMOD_ID_FOUR_LINE_DISP);
|
||||||
#endif
|
#endif
|
||||||
|
initDone = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// gets called every time WiFi is (re-)connected. Initialize own network
|
// gets called every time WiFi is (re-)connected. Initialize own network
|
||||||
// interfaces here
|
// interfaces here
|
||||||
void connected() {}
|
void connected() {}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Da loop.
|
* Da loop.
|
||||||
*/
|
*/
|
||||||
void loop() {
|
void loop() {
|
||||||
|
if (!autoSaveAfterSec || !enabled) return; // setting 0 as autosave seconds disables autosave
|
||||||
|
|
||||||
unsigned long now = millis();
|
unsigned long now = millis();
|
||||||
uint8_t currentMode = strip.getMode();
|
uint8_t currentMode = strip.getMode();
|
||||||
uint8_t currentPalette = strip.getSegment(0).palette;
|
uint8_t currentPalette = strip.getSegment(0).palette;
|
||||||
if (firstLoop) {
|
if (firstLoop) {
|
||||||
firstLoop = false;
|
firstLoop = false;
|
||||||
applyPreset(AUTOSAVE_PRESET_NUM);
|
if (applyAutoSaveOnBoot) applyPreset(autoSavePreset);
|
||||||
knownBrightness = bri;
|
knownBrightness = bri;
|
||||||
knownEffectSpeed = effectSpeed;
|
knownEffectSpeed = effectSpeed;
|
||||||
knownEffectIntensity = effectIntensity;
|
knownEffectIntensity = effectIntensity;
|
||||||
@ -95,7 +113,7 @@ class AutoSaveUsermod : public Usermod {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned long wouldAutoSaveAfter = now + AUTOSAVE_SETTLE_MS;
|
unsigned long wouldAutoSaveAfter = now + autoSaveAfterSec*1000;
|
||||||
if (knownBrightness != bri) {
|
if (knownBrightness != bri) {
|
||||||
knownBrightness = bri;
|
knownBrightness = bri;
|
||||||
autoSaveAfter = wouldAutoSaveAfter;
|
autoSaveAfter = wouldAutoSaveAfter;
|
||||||
@ -121,37 +139,32 @@ class AutoSaveUsermod : public Usermod {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void saveSettings() {
|
/*
|
||||||
updateLocalTime();
|
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
|
||||||
sprintf(presetNameBuffer,
|
* Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI.
|
||||||
"Auto save %02d-%02d %02d:%02d:%02d",
|
* Below it is shown how this could be used for e.g. a light sensor
|
||||||
month(localTime), day(localTime),
|
*/
|
||||||
hour(localTime), minute(localTime), second(localTime));
|
//void addToJsonInfo(JsonObject& root) {
|
||||||
savePreset(AUTOSAVE_PRESET_NUM, true, presetNameBuffer);
|
//JsonObject user = root["u"];
|
||||||
}
|
//if (user.isNull()) user = root.createNestedObject("u");
|
||||||
|
//JsonArray data = user.createNestedArray(F("Autosave"));
|
||||||
void displayOverlay() {
|
//data.add(F("Loaded."));
|
||||||
#ifdef USERMOD_FOUR_LINE_DISLAY
|
//}
|
||||||
if (display != nullptr) {
|
|
||||||
display->wakeDisplay();
|
|
||||||
display->overlay("Settings", "Auto Saved", 1500);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object).
|
* 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
|
* Values in the state object may be modified by connected clients
|
||||||
*/
|
*/
|
||||||
void addToJsonState(JsonObject& root) {
|
//void addToJsonState(JsonObject& root) {
|
||||||
}
|
//}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 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()
|
||||||
|
//}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object.
|
* addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object.
|
||||||
@ -168,6 +181,13 @@ class AutoSaveUsermod : public Usermod {
|
|||||||
* I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings!
|
* I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings!
|
||||||
*/
|
*/
|
||||||
void addToConfig(JsonObject& root) {
|
void addToConfig(JsonObject& root) {
|
||||||
|
// we add JSON object: {"Autosave": {"autoSaveAfterSec": 10, "autoSavePreset": 99}}
|
||||||
|
JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname
|
||||||
|
top[FPSTR(_autoSaveEnabled)] = enabled;
|
||||||
|
top[FPSTR(_autoSaveAfterSec)] = autoSaveAfterSec; // usermodparam
|
||||||
|
top[FPSTR(_autoSavePreset)] = autoSavePreset; // usermodparam
|
||||||
|
top[FPSTR(_autoSaveApplyOnBoot)] = applyAutoSaveOnBoot;
|
||||||
|
DEBUG_PRINTLN(F("Autosave config saved."));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -179,6 +199,32 @@ class AutoSaveUsermod : public Usermod {
|
|||||||
* If you don't know what that is, don't fret. It most likely doesn't affect your use case :)
|
* If you don't know what that is, don't fret. It most likely doesn't affect your use case :)
|
||||||
*/
|
*/
|
||||||
void readFromConfig(JsonObject& root) {
|
void readFromConfig(JsonObject& root) {
|
||||||
|
// we look for JSON object: {"Autosave": {"autoSaveAfterSec": 10, "autoSavePreset": 99}}
|
||||||
|
JsonObject top = root[FPSTR(_name)];
|
||||||
|
if (top.isNull()) {
|
||||||
|
DEBUG_PRINTLN(F("No config found. (Using defaults.)"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (top[FPSTR(_autoSaveEnabled)].is<bool>()) {
|
||||||
|
// reading from cfg.json
|
||||||
|
enabled = top[FPSTR(_autoSaveEnabled)].as<bool>();
|
||||||
|
} else {
|
||||||
|
// reading from POST message
|
||||||
|
String str = top[FPSTR(_autoSaveEnabled)]; // checkbox -> off or on
|
||||||
|
enabled = (bool)(str!="off"); // off is guaranteed to be present
|
||||||
|
}
|
||||||
|
autoSaveAfterSec = min(3600,max(10,top[FPSTR(_autoSaveAfterSec)].as<int>()));
|
||||||
|
autoSavePreset = min(250,max(100,top[FPSTR(_autoSavePreset)].as<int>()));
|
||||||
|
if (top[FPSTR(_autoSaveApplyOnBoot)].is<bool>()) {
|
||||||
|
// reading from cfg.json
|
||||||
|
applyAutoSaveOnBoot = top[FPSTR(_autoSaveApplyOnBoot)].as<bool>();
|
||||||
|
} else {
|
||||||
|
// reading from POST message
|
||||||
|
String str = top[FPSTR(_autoSaveApplyOnBoot)]; // checkbox -> off or on
|
||||||
|
applyAutoSaveOnBoot = (bool)(str!="off"); // off is guaranteed to be present
|
||||||
|
}
|
||||||
|
DEBUG_PRINTLN(F("Autosave config (re)loaded."));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -188,5 +234,11 @@ class AutoSaveUsermod : public Usermod {
|
|||||||
uint16_t getId() {
|
uint16_t getId() {
|
||||||
return USERMOD_ID_AUTO_SAVE;
|
return USERMOD_ID_AUTO_SAVE;
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// strings to reduce flash memory usage (used more than twice)
|
||||||
|
const char AutoSaveUsermod::_name[] PROGMEM = "Autosave";
|
||||||
|
const char AutoSaveUsermod::_autoSaveEnabled[] PROGMEM = "enabled";
|
||||||
|
const char AutoSaveUsermod::_autoSaveAfterSec[] PROGMEM = "autoSaveAfterSec";
|
||||||
|
const char AutoSaveUsermod::_autoSavePreset[] PROGMEM = "autoSavePreset";
|
||||||
|
const char AutoSaveUsermod::_autoSaveApplyOnBoot[] PROGMEM = "autoSaveApplyOnBoot";
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Rotary Encoder UI Usermod
|
# I2C 4 Line Display Usermod
|
||||||
|
|
||||||
First, thanks to the authors of the ssd11306_i2c_oled_u8g2 mod.
|
First, thanks to the authors of the ssd11306_i2c_oled_u8g2 mod.
|
||||||
|
|
||||||
@ -19,13 +19,11 @@ This file should be placed in the same directory as `platformio.ini`.
|
|||||||
|
|
||||||
### Define Your Options
|
### Define Your Options
|
||||||
|
|
||||||
* `USERMOD_FOUR_LINE_DISLAY` - define this to have this the Four Line Display mod included wled00\usermods_list.cpp - also tells Rotary Encoder usermod, if installed, that the display is available
|
* `USERMOD_FOUR_LINE_DISPLAY` - define this to have this the Four Line Display mod included wled00\usermods_list.cpp - also tells Rotary Encoder usermod, if installed, that the display is available
|
||||||
* `FLD_PIN_SCL` - The display SCL pin, defaults to 5
|
* `FLD_PIN_SCL` - The display SCL pin, defaults to 5
|
||||||
* `FLD_PIN_SDA` - The display SDA pin, defaults to 4
|
* `FLD_PIN_SDA` - The display SDA pin, defaults to 4
|
||||||
* `FLIP_MODE` - Set to 0 or 1
|
|
||||||
* `LINE_HEIGHT` - Set to 1 or 2
|
|
||||||
|
|
||||||
There are other `#define` values in the Usermod that might be of interest.
|
All of the parameters can be configured using Usermods settings page, inluding GPIO pins.
|
||||||
|
|
||||||
### PlatformIO requirements
|
### PlatformIO requirements
|
||||||
|
|
||||||
@ -37,3 +35,5 @@ UI usermod folder for how to include these using `platformio_override.ini`.
|
|||||||
|
|
||||||
2021-02
|
2021-02
|
||||||
* First public release
|
* First public release
|
||||||
|
2021-04
|
||||||
|
* Adaptation for runtime configuration.
|
@ -24,50 +24,25 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
//The SCL and SDA pins are defined here.
|
//The SCL and SDA pins are defined here.
|
||||||
#ifndef FLD_PIN_SCL
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
#define FLD_PIN_SCL 5
|
#ifndef FLD_PIN_SCL
|
||||||
#endif
|
#define FLD_PIN_SCL 22
|
||||||
|
#endif
|
||||||
#ifndef FLD_PIN_SDA
|
#ifndef FLD_PIN_SDA
|
||||||
#define FLD_PIN_SDA 4
|
#define FLD_PIN_SDA 21
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(
|
|
||||||
// U8X8_PIN_NONE, FLD_PIN_SCL, FLD_PIN_SDA);
|
|
||||||
U8X8_SH1106_128X64_WINSTAR_HW_I2C u8x8(
|
|
||||||
U8X8_PIN_NONE, FLD_PIN_SCL, FLD_PIN_SDA);
|
|
||||||
|
|
||||||
// Screen upside down? Change to 0 or 1
|
|
||||||
#ifndef FLIP_MODE
|
|
||||||
#define FLIP_MODE 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// LINE_HEIGHT 1 is single height, for 128x32 displays.
|
|
||||||
// LINE_HEIGHT 2 makes the 128x64 screen display at double height.
|
|
||||||
#ifndef LINE_HEIGHT
|
|
||||||
#define LINE_HEIGHT 2
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// If you aren't also including RotaryEncoderUIUsermod
|
|
||||||
// you probably want to set both
|
|
||||||
// SLEEP_MODE_ENABLED false
|
|
||||||
// CLOCK_MODE_ENABLED false
|
|
||||||
// as you will never be able wake the display / disable the clock.
|
|
||||||
#ifdef USERMOD_ROTARY_ENCODER_UI
|
|
||||||
#ifndef SLEEP_MODE_ENABLED
|
|
||||||
#define SLEEP_MODE_ENABLED true
|
|
||||||
#endif
|
|
||||||
#ifndef CLOCK_MODE_ENABLED
|
|
||||||
#define CLOCK_MODE_ENABLED true
|
|
||||||
#endif
|
|
||||||
#else
|
#else
|
||||||
#define SLEEP_MODE_ENABLED false
|
#ifndef FLD_PIN_SCL
|
||||||
#define CLOCK_MODE_ENABLED false
|
#define FLD_PIN_SCL 5
|
||||||
|
#endif
|
||||||
|
#ifndef FLD_PIN_SDA
|
||||||
|
#define FLD_PIN_SDA 4
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// When to time out to the clock or blank the screen
|
// When to time out to the clock or blank the screen
|
||||||
// if SLEEP_MODE_ENABLED.
|
// if SLEEP_MODE_ENABLED.
|
||||||
#define SCREEN_TIMEOUT_MS 15*1000
|
#define SCREEN_TIMEOUT_MS 60*1000 // 1 min
|
||||||
|
|
||||||
#define TIME_INDENT 0
|
#define TIME_INDENT 0
|
||||||
#define DATE_INDENT 2
|
#define DATE_INDENT 2
|
||||||
@ -75,33 +50,43 @@ U8X8_SH1106_128X64_WINSTAR_HW_I2C u8x8(
|
|||||||
// Minimum time between redrawing screen in ms
|
// Minimum time between redrawing screen in ms
|
||||||
#define USER_LOOP_REFRESH_RATE_MS 1000
|
#define USER_LOOP_REFRESH_RATE_MS 1000
|
||||||
|
|
||||||
#if LINE_HEIGHT == 2
|
|
||||||
#define DRAW_STRING draw1x2String
|
|
||||||
#define DRAW_GLYPH draw1x2Glyph
|
|
||||||
#define DRAW_BIG_STRING draw2x2String
|
|
||||||
#else
|
|
||||||
#define DRAW_STRING drawString
|
|
||||||
#define DRAW_GLYPH drawGlyph
|
|
||||||
#define DRAW_BIG_STRING draw2x2String
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Extra char (+1) for null
|
// Extra char (+1) for null
|
||||||
#define LINE_BUFFER_SIZE 16+1
|
#define LINE_BUFFER_SIZE 16+1
|
||||||
#define FLD_LINE_3_BRIGHTNESS 0
|
|
||||||
#define FLD_LINE_3_EFFECT_SPEED 1
|
|
||||||
#define FLD_LINE_3_EFFECT_INTENSITY 2
|
|
||||||
#define FLD_LINE_3_PALETTE 3
|
|
||||||
|
|
||||||
#if LINE_HEIGHT == 2
|
typedef enum {
|
||||||
#define TIME_LINE 1
|
FLD_LINE_4_BRIGHTNESS = 0,
|
||||||
#else
|
FLD_LINE_4_EFFECT_SPEED,
|
||||||
#define TIME_LINE 0
|
FLD_LINE_4_EFFECT_INTENSITY,
|
||||||
#endif
|
FLD_LINE_4_MODE,
|
||||||
|
FLD_LINE_4_PALETTE
|
||||||
|
} Line4Type;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
NONE = 0,
|
||||||
|
SSD1306, // U8X8_SSD1306_128X32_UNIVISION_HW_I2C
|
||||||
|
SH1106, // U8X8_SH1106_128X64_WINSTAR_HW_I2C
|
||||||
|
SSD1306_64 // U8X8_SSD1306_128X64_NONAME_HW_I2C
|
||||||
|
} DisplayType;
|
||||||
|
|
||||||
class FourLineDisplayUsermod : public Usermod {
|
class FourLineDisplayUsermod : public Usermod {
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
bool initDone = false;
|
||||||
unsigned long lastTime = 0;
|
unsigned long lastTime = 0;
|
||||||
|
|
||||||
|
// HW interface & configuration
|
||||||
|
U8X8 *u8x8 = nullptr; // pointer to U8X8 display object
|
||||||
|
int8_t sclPin=FLD_PIN_SCL, sdaPin=FLD_PIN_SDA; // I2C pins for interfacing, get initialised in readFromConfig()
|
||||||
|
DisplayType type = SSD1306; // display type
|
||||||
|
bool flip = false; // flip display 180°
|
||||||
|
uint8_t contrast = 10; // screen contrast
|
||||||
|
uint8_t lineHeight = 1; // 1 row or 2 rows
|
||||||
|
uint32_t refreshRate = USER_LOOP_REFRESH_RATE_MS; // in ms
|
||||||
|
uint32_t screenTimeout = SCREEN_TIMEOUT_MS; // in ms
|
||||||
|
bool sleepMode = true; // allow screen sleep?
|
||||||
|
bool clockMode = false; // display clock
|
||||||
|
|
||||||
// needRedraw marks if redraw is required to prevent often redrawing.
|
// needRedraw marks if redraw is required to prevent often redrawing.
|
||||||
bool needRedraw = true;
|
bool needRedraw = true;
|
||||||
|
|
||||||
@ -118,38 +103,72 @@ class FourLineDisplayUsermod : public Usermod {
|
|||||||
uint8_t knownHour = 99;
|
uint8_t knownHour = 99;
|
||||||
|
|
||||||
bool displayTurnedOff = false;
|
bool displayTurnedOff = false;
|
||||||
long lastUpdate = 0;
|
unsigned long lastUpdate = 0;
|
||||||
long lastRedraw = 0;
|
unsigned long lastRedraw = 0;
|
||||||
long overlayUntil = 0;
|
unsigned long overlayUntil = 0;
|
||||||
byte lineThreeType = FLD_LINE_3_BRIGHTNESS;
|
Line4Type lineFourType = FLD_LINE_4_BRIGHTNESS;
|
||||||
// Set to 2 or 3 to mark lines 2 or 3. Other values ignored.
|
// Set to 2 or 3 to mark lines 2 or 3. Other values ignored.
|
||||||
byte markLineNum = 0;
|
byte markLineNum = 0;
|
||||||
|
|
||||||
char lineBuffer[LINE_BUFFER_SIZE];
|
// strings to reduce flash memory usage (used more than twice)
|
||||||
|
static const char _name[];
|
||||||
char **modes_qstrings = nullptr;
|
static const char _contrast[];
|
||||||
char **palettes_qstrings = nullptr;
|
static const char _refreshRate[];
|
||||||
|
static const char _screenTimeOut[];
|
||||||
|
static const char _flip[];
|
||||||
|
static const char _sleepMode[];
|
||||||
|
static const char _clockMode[];
|
||||||
|
|
||||||
// If display does not work or looks corrupted check the
|
// If display does not work or looks corrupted check the
|
||||||
// constructor reference:
|
// constructor reference:
|
||||||
// https://github.com/olikraus/u8g2/wiki/u8x8setupcpp
|
// https://github.com/olikraus/u8g2/wiki/u8x8setupcpp
|
||||||
// or check the gallery:
|
// or check the gallery:
|
||||||
// https://github.com/olikraus/u8g2/wiki/gallery
|
// https://github.com/olikraus/u8g2/wiki/gallery
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
// gets called once at boot. Do all initialization that doesn't depend on
|
// gets called once at boot. Do all initialization that doesn't depend on
|
||||||
// network here
|
// network here
|
||||||
void setup() {
|
void setup() {
|
||||||
u8x8.begin();
|
if (type==NONE) return;
|
||||||
u8x8.setFlipMode(FLIP_MODE);
|
if (!pinManager.allocatePin(sclPin)) { sclPin = -1; type = NONE; return;}
|
||||||
u8x8.setPowerSave(0);
|
if (!pinManager.allocatePin(sdaPin)) { pinManager.deallocatePin(sclPin); sclPin = sdaPin = -1; type = NONE; return; }
|
||||||
u8x8.setContrast(10); //Contrast setup will help to preserve OLED lifetime. In case OLED need to be brighter increase number up to 255
|
switch (type) {
|
||||||
u8x8.setFont(u8x8_font_chroma48medium8_r);
|
case SSD1306:
|
||||||
u8x8.DRAW_STRING(0, 0*LINE_HEIGHT, "Loading...");
|
#ifdef ESP8266
|
||||||
|
if (!(sclPin==5 && sdaPin==4))
|
||||||
ModeSortUsermod *modeSortUsermod = (ModeSortUsermod*) usermods.lookup(USERMOD_ID_MODE_SORT);
|
u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_SW_I2C(sclPin, sdaPin); // SCL, SDA, reset
|
||||||
modes_qstrings = modeSortUsermod->getModesQStrings();
|
else
|
||||||
palettes_qstrings = modeSortUsermod->getPalettesQStrings();
|
#endif
|
||||||
|
u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_HW_I2C(U8X8_PIN_NONE, sclPin, sdaPin); // Pins are Reset, SCL, SDA
|
||||||
|
break;
|
||||||
|
case SH1106:
|
||||||
|
#ifdef ESP8266
|
||||||
|
if (!(sclPin==5 && sdaPin==4))
|
||||||
|
u8x8 = (U8X8 *) new U8X8_SH1106_128X64_WINSTAR_SW_I2C(sclPin, sdaPin); // SCL, SDA, reset
|
||||||
|
else
|
||||||
|
#endif
|
||||||
|
u8x8 = (U8X8 *) new U8X8_SH1106_128X64_WINSTAR_HW_I2C(U8X8_PIN_NONE, sclPin, sdaPin); // Pins are Reset, SCL, SDA
|
||||||
|
break;
|
||||||
|
case SSD1306_64:
|
||||||
|
#ifdef ESP8266
|
||||||
|
if (!(sclPin==5 && sdaPin==4))
|
||||||
|
u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_SW_I2C(sclPin, sdaPin); // SCL, SDA, reset
|
||||||
|
else
|
||||||
|
#endif
|
||||||
|
u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_HW_I2C(U8X8_PIN_NONE, sclPin, sdaPin); // Pins are Reset, SCL, SDA
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
u8x8 = nullptr;
|
||||||
|
type = NONE;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(static_cast<U8X8*>(u8x8))->begin();
|
||||||
|
setFlipMode(flip);
|
||||||
|
setContrast(contrast); //Contrast setup will help to preserve OLED lifetime. In case OLED need to be brighter increase number up to 255
|
||||||
|
setPowerSave(0);
|
||||||
|
drawString(0, 0, "Loading...");
|
||||||
|
initDone = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// gets called every time WiFi is (re-)connected. Initialize own network
|
// gets called every time WiFi is (re-)connected. Initialize own network
|
||||||
@ -160,7 +179,7 @@ class FourLineDisplayUsermod : public Usermod {
|
|||||||
* Da loop.
|
* Da loop.
|
||||||
*/
|
*/
|
||||||
void loop() {
|
void loop() {
|
||||||
if (millis() - lastUpdate < USER_LOOP_REFRESH_RATE_MS) {
|
if (millis() - lastUpdate < (clockMode?1000:refreshRate)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
lastUpdate = millis();
|
lastUpdate = millis();
|
||||||
@ -168,18 +187,59 @@ class FourLineDisplayUsermod : public Usermod {
|
|||||||
redraw(false);
|
redraw(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrappers for screen drawing
|
||||||
|
*/
|
||||||
|
void setFlipMode(uint8_t mode) {
|
||||||
|
if (type==NONE) return;
|
||||||
|
(static_cast<U8X8*>(u8x8))->setFlipMode(mode);
|
||||||
|
}
|
||||||
|
void setContrast(uint8_t contrast) {
|
||||||
|
if (type==NONE) return;
|
||||||
|
(static_cast<U8X8*>(u8x8))->setContrast(contrast);
|
||||||
|
}
|
||||||
|
void drawString(uint8_t col, uint8_t row, const char *string, bool ignoreLH=false) {
|
||||||
|
if (type==NONE) return;
|
||||||
|
(static_cast<U8X8*>(u8x8))->setFont(u8x8_font_chroma48medium8_r);
|
||||||
|
if (!ignoreLH && lineHeight==2) (static_cast<U8X8*>(u8x8))->draw1x2String(col, row, string);
|
||||||
|
else (static_cast<U8X8*>(u8x8))->drawString(col, row, string);
|
||||||
|
}
|
||||||
|
void draw2x2String(uint8_t col, uint8_t row, const char *string) {
|
||||||
|
if (type==NONE) return;
|
||||||
|
(static_cast<U8X8*>(u8x8))->setFont(u8x8_font_chroma48medium8_r);
|
||||||
|
(static_cast<U8X8*>(u8x8))->draw2x2String(col, row, string);
|
||||||
|
}
|
||||||
|
void drawGlyph(uint8_t col, uint8_t row, char glyph, const uint8_t *font, bool ignoreLH=false) {
|
||||||
|
if (type==NONE) return;
|
||||||
|
(static_cast<U8X8*>(u8x8))->setFont(font);
|
||||||
|
if (!ignoreLH && lineHeight==2) (static_cast<U8X8*>(u8x8))->draw1x2Glyph(col, row, glyph);
|
||||||
|
else (static_cast<U8X8*>(u8x8))->drawGlyph(col, row, glyph);
|
||||||
|
}
|
||||||
|
uint8_t getCols() {
|
||||||
|
if (type==NONE) return 0;
|
||||||
|
return (static_cast<U8X8*>(u8x8))->getCols();
|
||||||
|
}
|
||||||
|
void clear() {
|
||||||
|
if (type==NONE) return;
|
||||||
|
(static_cast<U8X8*>(u8x8))->clear();
|
||||||
|
}
|
||||||
|
void setPowerSave(uint8_t save) {
|
||||||
|
if (type==NONE) return;
|
||||||
|
(static_cast<U8X8*>(u8x8))->setPowerSave(save);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Redraw the screen (but only if things have changed
|
* Redraw the screen (but only if things have changed
|
||||||
* or if forceRedraw).
|
* or if forceRedraw).
|
||||||
*/
|
*/
|
||||||
void redraw(bool forceRedraw) {
|
void redraw(bool forceRedraw) {
|
||||||
|
if (type==NONE) return;
|
||||||
if (overlayUntil > 0) {
|
if (overlayUntil > 0) {
|
||||||
if (millis() >= overlayUntil) {
|
if (millis() >= overlayUntil) {
|
||||||
// Time to display the overlay has elapsed.
|
// Time to display the overlay has elapsed.
|
||||||
overlayUntil = 0;
|
overlayUntil = 0;
|
||||||
forceRedraw = true;
|
forceRedraw = true;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
// We are still displaying the overlay
|
// We are still displaying the overlay
|
||||||
// Don't redraw.
|
// Don't redraw.
|
||||||
return;
|
return;
|
||||||
@ -208,22 +268,46 @@ class FourLineDisplayUsermod : public Usermod {
|
|||||||
if (!needRedraw) {
|
if (!needRedraw) {
|
||||||
// Nothing to change.
|
// Nothing to change.
|
||||||
// Turn off display after 3 minutes with no change.
|
// Turn off display after 3 minutes with no change.
|
||||||
if(SLEEP_MODE_ENABLED && !displayTurnedOff &&
|
if(sleepMode && !displayTurnedOff && (millis() - lastRedraw > screenTimeout)) {
|
||||||
(millis() - lastRedraw > SCREEN_TIMEOUT_MS)) {
|
|
||||||
// We will still check if there is a change in redraw()
|
// We will still check if there is a change in redraw()
|
||||||
// and turn it back on if it changed.
|
// and turn it back on if it changed.
|
||||||
|
knownHour = 99; // force screen clear
|
||||||
sleepOrClock(true);
|
sleepOrClock(true);
|
||||||
}
|
} else if (displayTurnedOff && clockMode) {
|
||||||
else if (displayTurnedOff && CLOCK_MODE_ENABLED) {
|
|
||||||
showTime();
|
showTime();
|
||||||
|
} else if ((millis() - lastRedraw)/1000%3 == 0) {
|
||||||
|
// change 4th line every 3s
|
||||||
|
switch (lineFourType) {
|
||||||
|
case FLD_LINE_4_BRIGHTNESS:
|
||||||
|
setLineFourType(FLD_LINE_4_EFFECT_SPEED);
|
||||||
|
break;
|
||||||
|
case FLD_LINE_4_MODE:
|
||||||
|
setLineFourType(FLD_LINE_4_BRIGHTNESS);
|
||||||
|
break;
|
||||||
|
case FLD_LINE_4_PALETTE:
|
||||||
|
setLineFourType(clockMode ? FLD_LINE_4_MODE : FLD_LINE_4_BRIGHTNESS);
|
||||||
|
break;
|
||||||
|
case FLD_LINE_4_EFFECT_SPEED:
|
||||||
|
setLineFourType(FLD_LINE_4_EFFECT_INTENSITY);
|
||||||
|
break;
|
||||||
|
case FLD_LINE_4_EFFECT_INTENSITY:
|
||||||
|
setLineFourType(FLD_LINE_4_PALETTE);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
drawLineFour();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
} else {
|
||||||
|
knownHour = 99; // force time display
|
||||||
|
clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
needRedraw = false;
|
needRedraw = false;
|
||||||
lastRedraw = millis();
|
lastRedraw = millis();
|
||||||
|
|
||||||
if (displayTurnedOff)
|
if (displayTurnedOff) {
|
||||||
{
|
|
||||||
// Turn the display back on
|
// Turn the display back on
|
||||||
sleepOrClock(false);
|
sleepOrClock(false);
|
||||||
}
|
}
|
||||||
@ -242,79 +326,94 @@ class FourLineDisplayUsermod : public Usermod {
|
|||||||
knownEffectIntensity = effectIntensity;
|
knownEffectIntensity = effectIntensity;
|
||||||
|
|
||||||
// Do the actual drawing
|
// Do the actual drawing
|
||||||
u8x8.clear();
|
|
||||||
u8x8.setFont(u8x8_font_chroma48medium8_r);
|
|
||||||
|
|
||||||
// First row with Wifi name
|
// First row with Wifi name
|
||||||
String ssidString = knownSsid.substring(0, u8x8.getCols() > 1 ? u8x8.getCols() - 2 : 0);
|
drawGlyph(0, 0, 80, u8x8_font_open_iconic_embedded_1x1); // wifi icon
|
||||||
u8x8.DRAW_STRING(1, 0*LINE_HEIGHT, ssidString.c_str());
|
String ssidString = knownSsid.substring(0, getCols() > 1 ? getCols() - 2 : 0);
|
||||||
// Print `~` char to indicate that SSID is longer, than owr dicplay
|
drawString(1, 0, ssidString.c_str());
|
||||||
if (knownSsid.length() > u8x8.getCols()) {
|
// Print `~` char to indicate that SSID is longer, than our display
|
||||||
u8x8.DRAW_STRING(u8x8.getCols() - 1, 0*LINE_HEIGHT, "~");
|
if (knownSsid.length() > getCols()) {
|
||||||
|
drawString(getCols() - 1, 0, "~");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Second row with IP or Psssword
|
// Second row with IP or Psssword
|
||||||
|
drawGlyph(0, lineHeight, 68, u8x8_font_open_iconic_embedded_1x1); // home icon
|
||||||
// Print password in AP mode and if led is OFF.
|
// Print password in AP mode and if led is OFF.
|
||||||
if (apActive && bri == 0) {
|
if (apActive && bri == 0) {
|
||||||
u8x8.DRAW_STRING(1, 1*LINE_HEIGHT, apPass);
|
drawString(1, lineHeight, apPass);
|
||||||
}
|
} else {
|
||||||
else {
|
drawString(1, lineHeight, (knownIp.toString()).c_str());
|
||||||
String ipString = knownIp.toString();
|
|
||||||
u8x8.DRAW_STRING(1, 1*LINE_HEIGHT, ipString.c_str());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Third row with mode name
|
// Third row with mode name or current time
|
||||||
showCurrentEffectOrPalette(modes_qstrings[knownMode], 2);
|
if (clockMode) showTime(false);
|
||||||
|
else showCurrentEffectOrPalette(knownMode, JSON_mode_names, 2);
|
||||||
|
|
||||||
switch(lineThreeType) {
|
// Fourth row
|
||||||
case FLD_LINE_3_BRIGHTNESS:
|
drawLineFour();
|
||||||
sprintf(lineBuffer, "Brightness %d", bri);
|
|
||||||
u8x8.DRAW_STRING(1, 3*LINE_HEIGHT, lineBuffer);
|
drawGlyph(0, 2*lineHeight, 66 + (bri > 0 ? 3 : 0), u8x8_font_open_iconic_weather_2x2); // sun/moon icon
|
||||||
break;
|
//if (markLineNum>1) drawGlyph(2, markLineNum*lineHeight, 66, u8x8_font_open_iconic_arrow_1x1); // arrow icon
|
||||||
case FLD_LINE_3_EFFECT_SPEED:
|
|
||||||
sprintf(lineBuffer, "FX Speed %d", effectSpeed);
|
|
||||||
u8x8.DRAW_STRING(1, 3*LINE_HEIGHT, lineBuffer);
|
|
||||||
break;
|
|
||||||
case FLD_LINE_3_EFFECT_INTENSITY:
|
|
||||||
sprintf(lineBuffer, "FX Intense %d", effectIntensity);
|
|
||||||
u8x8.DRAW_STRING(1, 3*LINE_HEIGHT, lineBuffer);
|
|
||||||
break;
|
|
||||||
case FLD_LINE_3_PALETTE:
|
|
||||||
showCurrentEffectOrPalette(palettes_qstrings[knownPalette], 3);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
u8x8.setFont(u8x8_font_open_iconic_arrow_1x1);
|
void drawLineFour() {
|
||||||
u8x8.DRAW_GLYPH(0, markLineNum*LINE_HEIGHT, 66); // arrow icon
|
char lineBuffer[LINE_BUFFER_SIZE];
|
||||||
|
switch(lineFourType) {
|
||||||
u8x8.setFont(u8x8_font_open_iconic_embedded_1x1);
|
case FLD_LINE_4_BRIGHTNESS:
|
||||||
u8x8.DRAW_GLYPH(0, 0*LINE_HEIGHT, 80); // wifi icon
|
sprintf_P(lineBuffer, PSTR("Brightness %3d"), bri);
|
||||||
u8x8.DRAW_GLYPH(0, 1*LINE_HEIGHT, 68); // home icon
|
drawString(2, 3*lineHeight, lineBuffer);
|
||||||
|
break;
|
||||||
|
case FLD_LINE_4_EFFECT_SPEED:
|
||||||
|
sprintf_P(lineBuffer, PSTR("FX Speed %3d"), effectSpeed);
|
||||||
|
drawString(2, 3*lineHeight, lineBuffer);
|
||||||
|
break;
|
||||||
|
case FLD_LINE_4_EFFECT_INTENSITY:
|
||||||
|
sprintf_P(lineBuffer, PSTR("FX Intens. %3d"), effectIntensity);
|
||||||
|
drawString(2, 3*lineHeight, lineBuffer);
|
||||||
|
break;
|
||||||
|
case FLD_LINE_4_MODE:
|
||||||
|
showCurrentEffectOrPalette(knownMode, JSON_mode_names, 3);
|
||||||
|
break;
|
||||||
|
case FLD_LINE_4_PALETTE:
|
||||||
|
default:
|
||||||
|
showCurrentEffectOrPalette(knownPalette, JSON_palette_names, 3);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display the current effect or palette (desiredEntry)
|
* Display the current effect or palette (desiredEntry)
|
||||||
* on the appropriate line (row).
|
* on the appropriate line (row).
|
||||||
*
|
|
||||||
* TODO: Should we cache the current effect and
|
|
||||||
* TODO: palette name? This seems expensive.
|
|
||||||
*/
|
*/
|
||||||
void showCurrentEffectOrPalette(char *qstring, uint8_t row) {
|
void showCurrentEffectOrPalette(int knownMode, const char *qstring, uint8_t row) {
|
||||||
uint8_t printedChars = 1;
|
char lineBuffer[LINE_BUFFER_SIZE];
|
||||||
|
uint8_t qComma = 0;
|
||||||
|
bool insideQuotes = false;
|
||||||
|
uint8_t printedChars = 0;
|
||||||
char singleJsonSymbol;
|
char singleJsonSymbol;
|
||||||
int i = 0;
|
|
||||||
while (true) {
|
// Find the mode name in JSON
|
||||||
|
for (size_t i = 0; i < strlen_P(qstring); i++) {
|
||||||
singleJsonSymbol = pgm_read_byte_near(qstring + i);
|
singleJsonSymbol = pgm_read_byte_near(qstring + i);
|
||||||
if (singleJsonSymbol == '"' || singleJsonSymbol == '\0' ) {
|
if (singleJsonSymbol == '\0') break;
|
||||||
|
switch (singleJsonSymbol) {
|
||||||
|
case '"':
|
||||||
|
insideQuotes = !insideQuotes;
|
||||||
break;
|
break;
|
||||||
}
|
case '[':
|
||||||
u8x8.DRAW_GLYPH(printedChars, row * LINE_HEIGHT, singleJsonSymbol);
|
case ']':
|
||||||
printedChars++;
|
|
||||||
if ( (printedChars > u8x8.getCols() - 2)) {
|
|
||||||
break;
|
break;
|
||||||
|
case ',':
|
||||||
|
qComma++;
|
||||||
|
default:
|
||||||
|
if (!insideQuotes || (qComma != knownMode)) break;
|
||||||
|
lineBuffer[printedChars++] = singleJsonSymbol;
|
||||||
}
|
}
|
||||||
i++;
|
if ((qComma > knownMode) || (printedChars > getCols()-2) || printedChars > sizeof(lineBuffer)-2) break;
|
||||||
}
|
}
|
||||||
|
for (;printedChars < getCols()-2 || printedChars > sizeof(lineBuffer)-2; printedChars++) lineBuffer[printedChars]=' ';
|
||||||
|
lineBuffer[printedChars] = 0;
|
||||||
|
drawString(2, row*lineHeight, lineBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -324,6 +423,7 @@ class FourLineDisplayUsermod : public Usermod {
|
|||||||
* to wake up the screen.
|
* to wake up the screen.
|
||||||
*/
|
*/
|
||||||
bool wakeDisplay() {
|
bool wakeDisplay() {
|
||||||
|
knownHour = 99;
|
||||||
if (displayTurnedOff) {
|
if (displayTurnedOff) {
|
||||||
// Turn the display back on
|
// Turn the display back on
|
||||||
sleepOrClock(false);
|
sleepOrClock(false);
|
||||||
@ -345,36 +445,31 @@ class FourLineDisplayUsermod : public Usermod {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Print the overlay
|
// Print the overlay
|
||||||
u8x8.clear();
|
clear();
|
||||||
u8x8.setFont(u8x8_font_chroma48medium8_r);
|
if (line1) drawString(0, 1*lineHeight, line1);
|
||||||
if (line1) {
|
if (line2) drawString(0, 2*lineHeight, line2);
|
||||||
u8x8.DRAW_STRING(0, 1*LINE_HEIGHT, line1);
|
|
||||||
}
|
|
||||||
if (line2) {
|
|
||||||
u8x8.DRAW_STRING(0, 2*LINE_HEIGHT, line2);
|
|
||||||
}
|
|
||||||
overlayUntil = millis() + showHowLong;
|
overlayUntil = millis() + showHowLong;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specify what data should be defined on line 3
|
* Specify what data should be defined on line 4
|
||||||
* (the last line).
|
* (the last line).
|
||||||
*/
|
*/
|
||||||
void setLineThreeType(byte newLineThreeType) {
|
void setLineFourType(Line4Type newLineFourType) {
|
||||||
if (newLineThreeType == FLD_LINE_3_BRIGHTNESS ||
|
if (newLineFourType == FLD_LINE_4_BRIGHTNESS ||
|
||||||
newLineThreeType == FLD_LINE_3_EFFECT_SPEED ||
|
newLineFourType == FLD_LINE_4_EFFECT_SPEED ||
|
||||||
newLineThreeType == FLD_LINE_3_EFFECT_INTENSITY ||
|
newLineFourType == FLD_LINE_4_EFFECT_INTENSITY ||
|
||||||
newLineThreeType == FLD_LINE_3_PALETTE) {
|
newLineFourType == FLD_LINE_4_MODE ||
|
||||||
lineThreeType = newLineThreeType;
|
newLineFourType == FLD_LINE_4_PALETTE) {
|
||||||
}
|
lineFourType = newLineFourType;
|
||||||
else {
|
} else {
|
||||||
// Unknown value.
|
// Unknown value
|
||||||
lineThreeType = FLD_LINE_3_BRIGHTNESS;
|
lineFourType = FLD_LINE_4_BRIGHTNESS;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Line 2 or 3 (last two lines) can be marked with an
|
* Line 3 or 4 (last two lines) can be marked with an
|
||||||
* arrow in the first column. Pass 2 or 3 to this to
|
* arrow in the first column. Pass 2 or 3 to this to
|
||||||
* specify which line to mark with an arrow.
|
* specify which line to mark with an arrow.
|
||||||
* Any other values are ignored.
|
* Any other values are ignored.
|
||||||
@ -388,42 +483,17 @@ class FourLineDisplayUsermod : public Usermod {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
|
|
||||||
* Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI.
|
|
||||||
* Below it is shown how this could be used for e.g. a light sensor
|
|
||||||
*/
|
|
||||||
/*
|
|
||||||
void addToJsonInfo(JsonObject& root)
|
|
||||||
{
|
|
||||||
int reading = 20;
|
|
||||||
//this code adds "u":{"Light":[20," lux"]} to the info object
|
|
||||||
JsonObject user = root["u"];
|
|
||||||
if (user.isNull()) user = root.createNestedObject("u");
|
|
||||||
|
|
||||||
JsonArray lightArr = user.createNestedArray("Light"); //name
|
|
||||||
lightArr.add(reading); //value
|
|
||||||
lightArr.add(" lux"); //unit
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enable sleep (turn the display off) or clock mode.
|
* Enable sleep (turn the display off) or clock mode.
|
||||||
*/
|
*/
|
||||||
void sleepOrClock(bool enabled) {
|
void sleepOrClock(bool enabled) {
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
if (CLOCK_MODE_ENABLED) {
|
if (clockMode) showTime();
|
||||||
showTime();
|
else setPowerSave(1);
|
||||||
}
|
|
||||||
else {
|
|
||||||
u8x8.setPowerSave(1);
|
|
||||||
}
|
|
||||||
displayTurnedOff = true;
|
displayTurnedOff = true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (!CLOCK_MODE_ENABLED) {
|
setPowerSave(0);
|
||||||
u8x8.setPowerSave(0);
|
|
||||||
}
|
|
||||||
displayTurnedOff = false;
|
displayTurnedOff = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -433,23 +503,28 @@ class FourLineDisplayUsermod : public Usermod {
|
|||||||
* on the middle rows. Based 24 or 12 hour depending on
|
* on the middle rows. Based 24 or 12 hour depending on
|
||||||
* the useAMPM configuration.
|
* the useAMPM configuration.
|
||||||
*/
|
*/
|
||||||
void showTime() {
|
void showTime(bool fullScreen = true) {
|
||||||
|
char lineBuffer[LINE_BUFFER_SIZE];
|
||||||
|
|
||||||
updateLocalTime();
|
updateLocalTime();
|
||||||
byte minuteCurrent = minute(localTime);
|
byte minuteCurrent = minute(localTime);
|
||||||
byte hourCurrent = hour(localTime);
|
byte hourCurrent = hour(localTime);
|
||||||
|
byte secondCurrent = second(localTime);
|
||||||
if (knownMinute == minuteCurrent && knownHour == hourCurrent) {
|
if (knownMinute == minuteCurrent && knownHour == hourCurrent) {
|
||||||
// Time hasn't changed.
|
// Time hasn't changed.
|
||||||
return;
|
if (!fullScreen) return;
|
||||||
|
} else {
|
||||||
|
if (fullScreen) clear();
|
||||||
}
|
}
|
||||||
knownMinute = minuteCurrent;
|
knownMinute = minuteCurrent;
|
||||||
knownHour = hourCurrent;
|
knownHour = hourCurrent;
|
||||||
|
|
||||||
u8x8.clear();
|
byte currentMonth = month(localTime);
|
||||||
u8x8.setFont(u8x8_font_chroma48medium8_r);
|
sprintf_P(lineBuffer, PSTR("%s %2d "), monthShortStr(currentMonth), day(localTime));
|
||||||
|
if (fullScreen)
|
||||||
int currentMonth = month(localTime);
|
draw2x2String(DATE_INDENT, lineHeight==1 ? 0 : lineHeight, lineBuffer); // adjust for 8 line displays
|
||||||
sprintf(lineBuffer, "%s %d", monthShortStr(currentMonth), day(localTime));
|
else
|
||||||
u8x8.DRAW_BIG_STRING(DATE_INDENT, TIME_LINE*LINE_HEIGHT, lineBuffer);
|
drawString(2, lineHeight*2, lineBuffer);
|
||||||
|
|
||||||
byte showHour = hourCurrent;
|
byte showHour = hourCurrent;
|
||||||
boolean isAM = false;
|
boolean isAM = false;
|
||||||
@ -467,25 +542,45 @@ class FourLineDisplayUsermod : public Usermod {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sprintf(lineBuffer, "%02d:%02d %s", showHour, minuteCurrent, useAMPM ? (isAM ? "AM" : "PM") : "");
|
sprintf_P(lineBuffer, (secondCurrent%2 || !fullScreen) ? PSTR("%2d:%02d") : PSTR("%2d %02d"), (useAMPM ? showHour : hourCurrent), minuteCurrent);
|
||||||
// For time, we always use LINE_HEIGHT of 2 since
|
// For time, we always use LINE_HEIGHT of 2 since
|
||||||
// we are printing it big.
|
// we are printing it big.
|
||||||
u8x8.DRAW_BIG_STRING(TIME_INDENT + (useAMPM ? 0 : 2), (TIME_LINE + 1) * 2, lineBuffer);
|
if (fullScreen) {
|
||||||
|
draw2x2String(TIME_INDENT+2, lineHeight*2, lineBuffer);
|
||||||
|
sprintf_P(lineBuffer, PSTR("%02d"), secondCurrent);
|
||||||
|
if (!useAMPM) drawString(12, lineHeight*2+1, lineBuffer, true); // even with double sized rows print seconds in 1 line
|
||||||
|
} else {
|
||||||
|
drawString(9+(useAMPM?0:2), lineHeight*2, lineBuffer);
|
||||||
}
|
}
|
||||||
|
if (useAMPM) drawString(12+(fullScreen?0:2), lineHeight*2, (isAM ? "AM" : "PM"), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
|
||||||
|
* Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI.
|
||||||
|
* Below it is shown how this could be used for e.g. a light sensor
|
||||||
|
*/
|
||||||
|
//void addToJsonInfo(JsonObject& root) {
|
||||||
|
//JsonObject user = root["u"];
|
||||||
|
//if (user.isNull()) user = root.createNestedObject("u");
|
||||||
|
//JsonArray data = user.createNestedArray(F("4LineDisplay"));
|
||||||
|
//data.add(F("Loaded."));
|
||||||
|
//}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object).
|
* 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
|
* Values in the state object may be modified by connected clients
|
||||||
*/
|
*/
|
||||||
void addToJsonState(JsonObject& root) {
|
//void addToJsonState(JsonObject& root) {
|
||||||
}
|
//}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 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()
|
||||||
|
//}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object.
|
* addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object.
|
||||||
@ -502,6 +597,18 @@ class FourLineDisplayUsermod : public Usermod {
|
|||||||
* I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings!
|
* I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings!
|
||||||
*/
|
*/
|
||||||
void addToConfig(JsonObject& root) {
|
void addToConfig(JsonObject& root) {
|
||||||
|
JsonObject top = root.createNestedObject(FPSTR(_name));
|
||||||
|
JsonArray i2c_pin = top.createNestedArray("pin");
|
||||||
|
i2c_pin.add(sclPin);
|
||||||
|
i2c_pin.add(sdaPin);
|
||||||
|
top["type"] = type;
|
||||||
|
top[FPSTR(_flip)] = (bool) flip;
|
||||||
|
top[FPSTR(_contrast)] = contrast;
|
||||||
|
top[FPSTR(_refreshRate)] = refreshRate/1000;
|
||||||
|
top[FPSTR(_screenTimeOut)] = screenTimeout/1000;
|
||||||
|
top[FPSTR(_sleepMode)] = (bool) sleepMode;
|
||||||
|
top[FPSTR(_clockMode)] = (bool) clockMode;
|
||||||
|
DEBUG_PRINTLN(F("4 Line Display config saved."));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -513,6 +620,74 @@ class FourLineDisplayUsermod : public Usermod {
|
|||||||
* If you don't know what that is, don't fret. It most likely doesn't affect your use case :)
|
* If you don't know what that is, don't fret. It most likely doesn't affect your use case :)
|
||||||
*/
|
*/
|
||||||
void readFromConfig(JsonObject& root) {
|
void readFromConfig(JsonObject& root) {
|
||||||
|
bool needsRedraw = false;
|
||||||
|
DisplayType newType = type;
|
||||||
|
int8_t newScl = sclPin;
|
||||||
|
int8_t newSda = sdaPin;
|
||||||
|
|
||||||
|
JsonObject top = root[FPSTR(_name)];
|
||||||
|
if (!top.isNull() && top["pin"] != nullptr) {
|
||||||
|
newScl = top["pin"][0];
|
||||||
|
newSda = top["pin"][1];
|
||||||
|
newType = top["type"];
|
||||||
|
if (top[FPSTR(_flip)].is<bool>()) {
|
||||||
|
flip = top[FPSTR(_flip)].as<bool>();
|
||||||
|
} else {
|
||||||
|
String str = top[FPSTR(_flip)]; // checkbox -> off or on
|
||||||
|
flip = (bool)(str!="off"); // off is guaranteed to be present
|
||||||
|
needRedraw |= true;
|
||||||
|
}
|
||||||
|
contrast = top[FPSTR(_contrast)].as<int>();
|
||||||
|
refreshRate = top[FPSTR(_refreshRate)].as<int>() * 1000;
|
||||||
|
screenTimeout = top[FPSTR(_screenTimeOut)].as<int>() * 1000;
|
||||||
|
if (top[FPSTR(_sleepMode)].is<bool>()) {
|
||||||
|
sleepMode = top[FPSTR(_sleepMode)].as<bool>();
|
||||||
|
} else {
|
||||||
|
String str = top[FPSTR(_sleepMode)]; // checkbox -> off or on
|
||||||
|
sleepMode = (bool)(str!="off"); // off is guaranteed to be present
|
||||||
|
needRedraw |= true;
|
||||||
|
}
|
||||||
|
if (top[FPSTR(_clockMode)].is<bool>()) {
|
||||||
|
clockMode = top[FPSTR(_clockMode)].as<bool>();
|
||||||
|
} else {
|
||||||
|
String str = top[FPSTR(_clockMode)]; // checkbox -> off or on
|
||||||
|
clockMode = (bool)(str!="off"); // off is guaranteed to be present
|
||||||
|
needRedraw |= true;
|
||||||
|
}
|
||||||
|
DEBUG_PRINTLN(F("4 Line Display config (re)loaded."));
|
||||||
|
} else {
|
||||||
|
DEBUG_PRINTLN(F("No config found. (Using defaults.)"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!initDone) {
|
||||||
|
// first run: reading from cfg.json
|
||||||
|
sclPin = newScl;
|
||||||
|
sdaPin = newSda;
|
||||||
|
type = newType;
|
||||||
|
lineHeight = type==SSD1306 ? 1 : 2;
|
||||||
|
} else {
|
||||||
|
// changing paramters from settings page
|
||||||
|
if (sclPin!=newScl || sdaPin!=newSda || type!=newType) {
|
||||||
|
if (type==SSD1306) delete (static_cast<U8X8*>(u8x8));
|
||||||
|
if (type==SH1106) delete (static_cast<U8X8*>(u8x8));
|
||||||
|
if (type==SSD1306_64) delete (static_cast<U8X8*>(u8x8));
|
||||||
|
pinManager.deallocatePin(sclPin);
|
||||||
|
pinManager.deallocatePin(sdaPin);
|
||||||
|
sclPin = newScl;
|
||||||
|
sdaPin = newSda;
|
||||||
|
if (newScl<0 || newSda<0) {
|
||||||
|
type = NONE;
|
||||||
|
return;
|
||||||
|
} else
|
||||||
|
type = newType;
|
||||||
|
lineHeight = type==SSD1306 ? 1 : 2;
|
||||||
|
setup();
|
||||||
|
needRedraw |= true;
|
||||||
|
}
|
||||||
|
setContrast(contrast);
|
||||||
|
setFlipMode(flip);
|
||||||
|
if (needsRedraw && !wakeDisplay()) redraw(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -522,5 +697,13 @@ class FourLineDisplayUsermod : public Usermod {
|
|||||||
uint16_t getId() {
|
uint16_t getId() {
|
||||||
return USERMOD_ID_FOUR_LINE_DISP;
|
return USERMOD_ID_FOUR_LINE_DISP;
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// strings to reduce flash memory usage (used more than twice)
|
||||||
|
const char FourLineDisplayUsermod::_name[] PROGMEM = "4LineDisplay";
|
||||||
|
const char FourLineDisplayUsermod::_contrast[] PROGMEM = "contrast";
|
||||||
|
const char FourLineDisplayUsermod::_refreshRate[] PROGMEM = "refreshRateSec";
|
||||||
|
const char FourLineDisplayUsermod::_screenTimeOut[] PROGMEM = "screenTimeOutSec";
|
||||||
|
const char FourLineDisplayUsermod::_flip[] PROGMEM = "flip";
|
||||||
|
const char FourLineDisplayUsermod::_sleepMode[] PROGMEM = "sleepMode";
|
||||||
|
const char FourLineDisplayUsermod::_clockMode[] PROGMEM = "clockMode";
|
||||||
|
@ -11,12 +11,24 @@
|
|||||||
#define DEFAULT_OTA_PASS "wledota"
|
#define DEFAULT_OTA_PASS "wledota"
|
||||||
|
|
||||||
//increase if you need more
|
//increase if you need more
|
||||||
#define WLED_MAX_USERMODS 4
|
#ifndef WLED_MAX_USERMODS
|
||||||
|
#ifdef ESP8266
|
||||||
|
#define WLED_MAX_USERMODS 4
|
||||||
|
#else
|
||||||
|
#define WLED_MAX_USERMODS 6
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef ESP8266
|
#ifndef WLED_MAX_BUSSES
|
||||||
#define WLED_MAX_BUSSES 3
|
#ifdef ESP8266
|
||||||
#else
|
#define WLED_MAX_BUSSES 3
|
||||||
#define WLED_MAX_BUSSES 10
|
#else
|
||||||
|
#ifdef CONFIG_IDF_TARGET_ESP32S2
|
||||||
|
#define WLED_MAX_BUSSES 5
|
||||||
|
#else
|
||||||
|
#define WLED_MAX_BUSSES 10
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
//Usermod IDs
|
//Usermod IDs
|
||||||
@ -33,6 +45,7 @@
|
|||||||
#define USERMOD_ID_DHT 10 //Usermod "usermod_dht.h"
|
#define USERMOD_ID_DHT 10 //Usermod "usermod_dht.h"
|
||||||
#define USERMOD_ID_MODE_SORT 11 //Usermod "usermod_v2_mode_sort.h"
|
#define USERMOD_ID_MODE_SORT 11 //Usermod "usermod_v2_mode_sort.h"
|
||||||
#define USERMOD_ID_VL53L0X 12 //Usermod "usermod_vl53l0x_gestures.h"
|
#define USERMOD_ID_VL53L0X 12 //Usermod "usermod_vl53l0x_gestures.h"
|
||||||
|
#define USERMOD_ID_MULTI_RELAY 101 //Usermod "usermod_multi_relay.h"
|
||||||
|
|
||||||
//Access point behavior
|
//Access point behavior
|
||||||
#define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot
|
#define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot
|
||||||
@ -116,10 +129,10 @@
|
|||||||
#define TYPE_LPD8806 52
|
#define TYPE_LPD8806 52
|
||||||
#define TYPE_P9813 53
|
#define TYPE_P9813 53
|
||||||
|
|
||||||
#define IS_DIGITAL(t) (t & 0x10) //digital are 16-31 and 48-63
|
#define IS_DIGITAL(t) ((t) & 0x10) //digital are 16-31 and 48-63
|
||||||
#define IS_PWM(t) (t > 40 && t < 46)
|
#define IS_PWM(t) ((t) > 40 && (t) < 46)
|
||||||
#define NUM_PWM_PINS(t) (t - 40) //for analog PWM 41-45 only
|
#define NUM_PWM_PINS(t) ((t) - 40) //for analog PWM 41-45 only
|
||||||
#define IS_2PIN(t) (t > 47)
|
#define IS_2PIN(t) ((t) > 47)
|
||||||
|
|
||||||
//Color orders
|
//Color orders
|
||||||
#define COL_ORDER_GRB 0 //GRB(w),defaut
|
#define COL_ORDER_GRB 0 //GRB(w),defaut
|
||||||
@ -241,7 +254,11 @@
|
|||||||
|
|
||||||
//this is merely a default now and can be changed at runtime
|
//this is merely a default now and can be changed at runtime
|
||||||
#ifndef LEDPIN
|
#ifndef LEDPIN
|
||||||
#define LEDPIN 2
|
#ifdef ESP8266
|
||||||
|
#define LEDPIN 2 // GPIO2 (D4) on Wemod D1 mini compatible boards
|
||||||
|
#else
|
||||||
|
#define LEDPIN 16 // alligns with GPIO2 (D4) on Wemos D1 mini32 compatible boards
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef WLED_ENABLE_DMX
|
#ifdef WLED_ENABLE_DMX
|
||||||
|
@ -41,6 +41,7 @@
|
|||||||
<form action="/settings/ui"><button type="submit">User Interface</button></form>
|
<form action="/settings/ui"><button type="submit">User Interface</button></form>
|
||||||
<form action="/settings/sync"><button type="submit">Sync Interfaces</button></form>
|
<form action="/settings/sync"><button type="submit">Sync Interfaces</button></form>
|
||||||
<form action="/settings/time"><button type="submit">Time & Macros</button></form>
|
<form action="/settings/time"><button type="submit">Time & Macros</button></form>
|
||||||
|
<form action="/settings/um"><button type="submit">Usermods</button></form>
|
||||||
<form action="/settings/sec"><button type="submit">Security & Updates</button></form>
|
<form action="/settings/sec"><button type="submit">Security & Updates</button></form>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
136
wled00/data/settings_um.htm
Normal file
136
wled00/data/settings_um.htm
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head lang="en">
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=500">
|
||||||
|
<title>UI Settings</title>
|
||||||
|
<script>
|
||||||
|
var d = document;
|
||||||
|
var umCfg = {};
|
||||||
|
var pins = [6,7,8,9,10,11];
|
||||||
|
var pinO = ["reserved","reserved","reserved","reserved","reserved","reserved"], owner;
|
||||||
|
var loc = false, locip;
|
||||||
|
var urows;
|
||||||
|
function gId(s) { return d.getElementById(s); }
|
||||||
|
function isO(i) { return (i && typeof i === 'object' && !Array.isArray(i)); }
|
||||||
|
function H() { window.open("https://github.com/Aircoookie/WLED/wiki/Settings#usermod-settings"); }
|
||||||
|
function B() { window.open("/settings","_self"); }
|
||||||
|
function S() {
|
||||||
|
if (window.location.protocol == "file:") {
|
||||||
|
loc = true;
|
||||||
|
locip = localStorage.getItem('locIp');
|
||||||
|
if (!locip) {
|
||||||
|
locip = prompt("File Mode. Please enter WLED IP!");
|
||||||
|
localStorage.setItem('locIp', locip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ldS();
|
||||||
|
}
|
||||||
|
function check(o,k) {
|
||||||
|
var n = o.name.replace("[]","").substr(-3);
|
||||||
|
if (o.type=="number" && n.substr(0,3)=="pin") {
|
||||||
|
for (var i=0; i<pins.length; i++) {
|
||||||
|
if (k==pinO[i]) continue;
|
||||||
|
if (o.value==pins[i] || o.value<-1 || o.value>39) { o.style.color="red"; break; } else o.style.color=o.value>33?"orange":"#fff";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function getPins(o) {
|
||||||
|
if (isO(o)) {
|
||||||
|
for (const [k,v] of Object.entries(o)) {
|
||||||
|
if (isO(v)) {
|
||||||
|
owner = k;
|
||||||
|
getPins(v);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (k.replace("[]","").substr(-3)=="pin") {
|
||||||
|
if (Array.isArray(v)) {
|
||||||
|
for (var i=0; i<v.length; i++) if (v[i]>=0) { pins.push(v[i]); pinO.push(owner); }
|
||||||
|
} else {
|
||||||
|
if (v>=0) { pins.push(v); pinO.push(owner); }
|
||||||
|
}
|
||||||
|
} else if (Array.isArray(v)) {
|
||||||
|
for (var i=0; i<v.length; i++) getPins(v[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function addField(k,f,o,a=false) {
|
||||||
|
if (isO(o)) {
|
||||||
|
for (const [s,v] of Object.entries(o)) {
|
||||||
|
addField(k,s,v);
|
||||||
|
}
|
||||||
|
} else if (Array.isArray(o)) {
|
||||||
|
for (var j=0; j<o.length; j++) {
|
||||||
|
addField(k,f,o[j],true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var t,c;
|
||||||
|
switch (typeof o) {
|
||||||
|
case "boolean":
|
||||||
|
t = "checkbox"; c = o ? `checked value="on"` : ""; break;
|
||||||
|
case "number":
|
||||||
|
t = "number"; c = `value="${parseInt(o,10)}"`; break;
|
||||||
|
case "string":
|
||||||
|
t = "text"; c = `value="${o}"`; break;
|
||||||
|
default:
|
||||||
|
t = "text"; c = `value="${o}"`; break;
|
||||||
|
}
|
||||||
|
// https://stackoverflow.com/questions/11657123/posting-both-checked-and-unchecked-checkboxes
|
||||||
|
if (t=="checkbox") urows += `<input type="hidden" name="${k}_${f}${a?"[]":""}" value="off">`;
|
||||||
|
urows += `${f}: <input type="${t}" name="${k}_${f}${a?"[]":""}" ${c} oninput="check(this,'${k}')"><br>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function ldS() {
|
||||||
|
var url = (loc?`http://${locip}`:'') + '/cfg.json';
|
||||||
|
fetch(url, {
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
if (!res.ok) gId('lserr').style.display = "inline";
|
||||||
|
return res.json();
|
||||||
|
})
|
||||||
|
.then(json => {
|
||||||
|
umCfg = json.um;
|
||||||
|
getPins(json);
|
||||||
|
urows="";
|
||||||
|
if (isO(umCfg)) {
|
||||||
|
for (const [k,o] of Object.entries(umCfg)) {
|
||||||
|
urows += `<hr><h3>${k}</h3>`;
|
||||||
|
addField(k,'unknown',o);
|
||||||
|
}
|
||||||
|
gId("um").innerHTML = urows;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
gId('lserr').style.display = "inline"
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function svS(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
console.log(d.Sf);
|
||||||
|
if (d.Sf.checkValidity()) d.Sf.submit(); //https://stackoverflow.com/q/37323914
|
||||||
|
}
|
||||||
|
function GetV() {}
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
@import url("style.css");
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body onload="S()">
|
||||||
|
<form id="form_s" name="Sf" method="post" onsubmit="svS(event)">
|
||||||
|
<div class="toprow">
|
||||||
|
<div class="helpB"><button type="button" onclick="H()">?</button></div>
|
||||||
|
<button type="button" onclick="B()">Back</button><button type="submit">Save</button><br>
|
||||||
|
<span id="lssuc" style="color:green; display:none">✔ Configuration saved!</span>
|
||||||
|
<span id="lserr" style="color:red; display:none">⚠ Could not load configuration.</span><hr>
|
||||||
|
</div>
|
||||||
|
<h2>Usermod Setup</h2>
|
||||||
|
<div id="um">Loading settings...</div>
|
||||||
|
<hr><button type="button" onclick="B()">Back</button><button type="submit">Save</button>
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
@ -150,6 +150,7 @@ void _overlayCronixie();
|
|||||||
void _drawOverlayCronixie();
|
void _drawOverlayCronixie();
|
||||||
|
|
||||||
//playlist.cpp
|
//playlist.cpp
|
||||||
|
void shufflePlaylist();
|
||||||
void unloadPlaylist();
|
void unloadPlaylist();
|
||||||
void loadPlaylist(JsonObject playlistObject);
|
void loadPlaylist(JsonObject playlistObject);
|
||||||
void handlePlaylist();
|
void handlePlaylist();
|
||||||
@ -186,10 +187,12 @@ class Usermod {
|
|||||||
virtual void readFromJsonState(JsonObject& obj) {}
|
virtual void readFromJsonState(JsonObject& obj) {}
|
||||||
virtual void addToConfig(JsonObject& obj) {}
|
virtual void addToConfig(JsonObject& obj) {}
|
||||||
virtual void readFromConfig(JsonObject& obj) {}
|
virtual void readFromConfig(JsonObject& obj) {}
|
||||||
|
virtual void onMqttConnect(bool sessionPresent) {}
|
||||||
|
virtual bool onMqttMessage(char* topic, char* payload) { return false; }
|
||||||
virtual uint16_t getId() {return USERMOD_ID_UNSPECIFIED;}
|
virtual uint16_t getId() {return USERMOD_ID_UNSPECIFIED;}
|
||||||
};
|
};
|
||||||
|
|
||||||
class UsermodManager {
|
class UsermodManager : public Usermod {
|
||||||
private:
|
private:
|
||||||
Usermod* ums[WLED_MAX_USERMODS];
|
Usermod* ums[WLED_MAX_USERMODS];
|
||||||
byte numMods = 0;
|
byte numMods = 0;
|
||||||
@ -206,7 +209,8 @@ class UsermodManager {
|
|||||||
|
|
||||||
void addToConfig(JsonObject& obj);
|
void addToConfig(JsonObject& obj);
|
||||||
void readFromConfig(JsonObject& obj);
|
void readFromConfig(JsonObject& obj);
|
||||||
|
void onMqttConnect(bool sessionPresent);
|
||||||
|
bool onMqttMessage(char* topic, char* payload);
|
||||||
bool add(Usermod* um);
|
bool add(Usermod* um);
|
||||||
Usermod* lookup(uint16_t mod_id);
|
Usermod* lookup(uint16_t mod_id);
|
||||||
byte getModCount();
|
byte getModCount();
|
||||||
|
@ -22,7 +22,8 @@ LED Preferences</button></form><form action="/settings/ui"><button
|
|||||||
type="submit">User Interface</button></form>%DMXMENU%<form action="/settings/sync">
|
type="submit">User Interface</button></form>%DMXMENU%<form action="/settings/sync">
|
||||||
<button type="submit">Sync Interfaces</button></form><form
|
<button type="submit">Sync Interfaces</button></form><form
|
||||||
action="/settings/time"><button type="submit">Time & Macros</button></form><form
|
action="/settings/time"><button type="submit">Time & Macros</button></form><form
|
||||||
action="/settings/sec"><button type="submit">Security & Updates</button></form>
|
action="/settings/um"><button type="submit">Usermods</button></form><form
|
||||||
|
action="/settings/sec"><button type="submit">Security & Updates</button></form>
|
||||||
</body></html>)=====";
|
</body></html>)=====";
|
||||||
|
|
||||||
|
|
||||||
@ -390,3 +391,18 @@ MIT license</a></i><br><br>Server message: <span class="sip">Response error!
|
|||||||
</span><hr><button type="button" onclick="B()">Back</button><button
|
</span><hr><button type="button" onclick="B()">Back</button><button
|
||||||
type="submit">Save & Reboot</button></form></body></html>)=====";
|
type="submit">Save & Reboot</button></form></body></html>)=====";
|
||||||
|
|
||||||
|
|
||||||
|
// Autogenerated from wled00/data/settings_um.htm, do not edit!!
|
||||||
|
const char PAGE_settings_um[] PROGMEM = R"=====(<!DOCTYPE html><html><head lang="en"><meta charset="utf-8"><meta
|
||||||
|
name="viewport" content="width=500"><title>UI Settings</title><script>
|
||||||
|
var owner,locip,urows,d=document,umCfg={},pins=[6,7,8,9,10,11],pinO=["reserved","reserved","reserved","reserved","reserved","reserved"],loc=!1;function gId(e){return d.getElementById(e)}function isO(e){return e&&"object"==typeof e&&!Array.isArray(e)}function H(){window.open("https://github.com/Aircoookie/WLED/wiki/Settings#usermod-settings")}function B(){window.open("/settings","_self")}function S(){"file:"==window.location.protocol&&(loc=!0,(locip=localStorage.getItem("locIp"))||(locip=prompt("File Mode. Please enter WLED IP!"),localStorage.setItem("locIp",locip))),ldS()}function check(e,n){var o=e.name.replace("[]","").substr(-3);if("number"==e.type&&"pin"==o.substr(0,3))for(var i=0;i<pins.length;i++)if(n!=pinO[i]){if(e.value==pins[i]||e.value<-1||e.value>39){e.style.color="red";break}e.style.color=e.value>33?"orange":"#fff"}}function getPins(e){if(isO(e))for(const[o,i]of Object.entries(e))if(isO(i))owner=o,getPins(i);else if("pin"==o.replace("[]","").substr(-3))if(Array.isArray(i))for(var n=0;n<i.length;n++)i[n]>=0&&(pins.push(i[n]),pinO.push(owner));else i>=0&&(pins.push(i),pinO.push(owner));else if(Array.isArray(i))for(n=0;n<i.length;n++)getPins(i[n])}function addField(e,n,o,i=!1){if(isO(o))for(const[n,i]of Object.entries(o))addField(e,n,i);else if(Array.isArray(o))for(var r=0;r<o.length;r++)addField(e,n,o[r],!0);else{var t,s;switch(typeof o){case"boolean":t="checkbox",s=o?'checked value="on"':"";break;case"number":t="number",s=`value="${parseInt(o,10)}"`;break;case"string":default:t="text",s=`value="${o}"`}"checkbox"==t&&(urows+=`<input type="hidden" name="${e}_${n}${i?"[]":""}" value="off">`),urows+=`${n}: <input type="${t}" name="${e}_${n}${i?"[]":""}" ${s} oninput="check(this,'${e}')"><br>`}}function ldS(){fetch((loc?"http://"+locip:"")+"/cfg.json",{method:"get"}).then(e=>(e.ok||(gId("lserr").style.display="inline"),e.json())).then(e=>{if(umCfg=e.um,getPins(e),urows="",isO(umCfg)){for(const[e,n]of Object.entries(umCfg))urows+=`<hr><h3>${e}</h3>`,addField(e,"unknown",n);gId("um").innerHTML=urows}}).catch((function(e){gId("lserr").style.display="inline",console.log(e)}))}function svS(e){e.preventDefault(),console.log(d.Sf),d.Sf.checkValidity()&&d.Sf.submit()}function GetV(){}
|
||||||
|
</script>%CSS%%SCSS%</head><body onload="S()"><form
|
||||||
|
id="form_s" name="Sf" method="post" onsubmit="svS(event)"><div class="toprow">
|
||||||
|
<div class="helpB"><button type="button" onclick="H()">?</button></div><button
|
||||||
|
type="button" onclick="B()">Back</button><button type="submit">Save</button><br>
|
||||||
|
<span id="lssuc" style="color:green;display:none">✔ Configuration saved!
|
||||||
|
</span> <span id="lserr" style="color:red;display:none">
|
||||||
|
⚠ Could not load configuration.</span><hr></div><h2>Usermod Setup</h2><div
|
||||||
|
id="um">Loading settings...</div><hr><button type="button" onclick="B()">Back
|
||||||
|
</button><button type="submit">Save</button></form></body></html>)=====";
|
||||||
|
|
||||||
|
1354
wled00/html_ui.h
1354
wled00/html_ui.h
File diff suppressed because it is too large
Load Diff
@ -25,28 +25,28 @@ void onMqttConnect(bool sessionPresent)
|
|||||||
//(re)subscribe to required topics
|
//(re)subscribe to required topics
|
||||||
char subuf[38];
|
char subuf[38];
|
||||||
|
|
||||||
if (mqttDeviceTopic[0] != 0)
|
if (mqttDeviceTopic[0] != 0) {
|
||||||
{
|
|
||||||
strcpy(subuf, mqttDeviceTopic);
|
strcpy(subuf, mqttDeviceTopic);
|
||||||
mqtt->subscribe(subuf, 0);
|
mqtt->subscribe(subuf, 0);
|
||||||
strcat(subuf, "/col");
|
strcat_P(subuf, PSTR("/col"));
|
||||||
mqtt->subscribe(subuf, 0);
|
mqtt->subscribe(subuf, 0);
|
||||||
strcpy(subuf, mqttDeviceTopic);
|
strcpy(subuf, mqttDeviceTopic);
|
||||||
strcat(subuf, "/api");
|
strcat_P(subuf, PSTR("/api"));
|
||||||
mqtt->subscribe(subuf, 0);
|
mqtt->subscribe(subuf, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mqttGroupTopic[0] != 0)
|
if (mqttGroupTopic[0] != 0) {
|
||||||
{
|
|
||||||
strcpy(subuf, mqttGroupTopic);
|
strcpy(subuf, mqttGroupTopic);
|
||||||
mqtt->subscribe(subuf, 0);
|
mqtt->subscribe(subuf, 0);
|
||||||
strcat(subuf, "/col");
|
strcat_P(subuf, PSTR("/col"));
|
||||||
mqtt->subscribe(subuf, 0);
|
mqtt->subscribe(subuf, 0);
|
||||||
strcpy(subuf, mqttGroupTopic);
|
strcpy(subuf, mqttGroupTopic);
|
||||||
strcat(subuf, "/api");
|
strcat_P(subuf, PSTR("/api"));
|
||||||
mqtt->subscribe(subuf, 0);
|
mqtt->subscribe(subuf, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
usermods.onMqttConnect(sessionPresent);
|
||||||
|
|
||||||
doPublishMqtt = true;
|
doPublishMqtt = true;
|
||||||
DEBUG_PRINTLN(F("MQTT ready"));
|
DEBUG_PRINTLN(F("MQTT ready"));
|
||||||
}
|
}
|
||||||
@ -72,19 +72,18 @@ void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties
|
|||||||
if (strncmp(topic, mqttGroupTopic, topicPrefixLen) == 0) {
|
if (strncmp(topic, mqttGroupTopic, topicPrefixLen) == 0) {
|
||||||
topic += topicPrefixLen;
|
topic += topicPrefixLen;
|
||||||
} else {
|
} else {
|
||||||
// Topic not used here. Probably a usermod subscribed to this topic.
|
// Non-Wled Topic used here. Probably a usermod subscribed to this topic.
|
||||||
|
usermods.onMqttMessage(topic, payload);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Prefix is stripped from the topic at this point
|
//Prefix is stripped from the topic at this point
|
||||||
|
|
||||||
if (strcmp(topic, "/col") == 0)
|
if (strcmp_P(topic, PSTR("/col")) == 0) {
|
||||||
{
|
|
||||||
colorFromDecOrHexString(col, (char*)payload);
|
colorFromDecOrHexString(col, (char*)payload);
|
||||||
colorUpdated(NOTIFIER_CALL_MODE_DIRECT_CHANGE);
|
colorUpdated(NOTIFIER_CALL_MODE_DIRECT_CHANGE);
|
||||||
} else if (strcmp(topic, "/api") == 0)
|
} else if (strcmp_P(topic, PSTR("/api")) == 0) {
|
||||||
{
|
|
||||||
if (payload[0] == '{') { //JSON API
|
if (payload[0] == '{') { //JSON API
|
||||||
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
|
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
|
||||||
deserializeJson(doc, payload);
|
deserializeJson(doc, payload);
|
||||||
@ -94,8 +93,11 @@ void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties
|
|||||||
apireq += (char*)payload;
|
apireq += (char*)payload;
|
||||||
handleSet(nullptr, apireq);
|
handleSet(nullptr, apireq);
|
||||||
}
|
}
|
||||||
} else if (strcmp(topic, "") == 0)
|
} else if (strlen(topic) != 0) {
|
||||||
{
|
// non standard topic, check with usermods
|
||||||
|
usermods.onMqttMessage(topic, payload);
|
||||||
|
} else {
|
||||||
|
// topmost topic (just wled/MAC)
|
||||||
parseMQTTBriPayload(payload);
|
parseMQTTBriPayload(payload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -110,24 +112,24 @@ void publishMqtt()
|
|||||||
char s[10];
|
char s[10];
|
||||||
char subuf[38];
|
char subuf[38];
|
||||||
|
|
||||||
sprintf(s, "%u", bri);
|
sprintf_P(s, PSTR("%u"), bri);
|
||||||
strcpy(subuf, mqttDeviceTopic);
|
strcpy(subuf, mqttDeviceTopic);
|
||||||
strcat(subuf, "/g");
|
strcat_P(subuf, PSTR("/g"));
|
||||||
mqtt->publish(subuf, 0, true, s);
|
mqtt->publish(subuf, 0, true, s);
|
||||||
|
|
||||||
sprintf(s, "#%06X", (col[3] << 24) | (col[0] << 16) | (col[1] << 8) | (col[2]));
|
sprintf_P(s, PSTR("#%06X"), (col[3] << 24) | (col[0] << 16) | (col[1] << 8) | (col[2]));
|
||||||
strcpy(subuf, mqttDeviceTopic);
|
strcpy(subuf, mqttDeviceTopic);
|
||||||
strcat(subuf, "/c");
|
strcat_P(subuf, PSTR("/c"));
|
||||||
mqtt->publish(subuf, 0, true, s);
|
mqtt->publish(subuf, 0, true, s);
|
||||||
|
|
||||||
strcpy(subuf, mqttDeviceTopic);
|
strcpy(subuf, mqttDeviceTopic);
|
||||||
strcat(subuf, "/status");
|
strcat_P(subuf, PSTR("/status"));
|
||||||
mqtt->publish(subuf, 0, true, "online");
|
mqtt->publish(subuf, 0, true, "online");
|
||||||
|
|
||||||
char apires[1024];
|
char apires[1024];
|
||||||
XML_response(nullptr, apires);
|
XML_response(nullptr, apires);
|
||||||
strcpy(subuf, mqttDeviceTopic);
|
strcpy(subuf, mqttDeviceTopic);
|
||||||
strcat(subuf, "/v");
|
strcat_P(subuf, PSTR("/v"));
|
||||||
mqtt->publish(subuf, 0, true, apires);
|
mqtt->publish(subuf, 0, true, apires);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,7 +159,7 @@ bool initMqtt()
|
|||||||
if (mqttUser[0] && mqttPass[0]) mqtt->setCredentials(mqttUser, mqttPass);
|
if (mqttUser[0] && mqttPass[0]) mqtt->setCredentials(mqttUser, mqttPass);
|
||||||
|
|
||||||
strcpy(mqttStatusTopic, mqttDeviceTopic);
|
strcpy(mqttStatusTopic, mqttDeviceTopic);
|
||||||
strcat(mqttStatusTopic, "/status");
|
strcat_P(mqttStatusTopic, PSTR("/status"));
|
||||||
mqtt->setWill(mqttStatusTopic, 0, true, "offline");
|
mqtt->setWill(mqttStatusTopic, 0, true, "offline");
|
||||||
mqtt->setKeepAlive(MQTT_KEEP_ALIVE_TIME);
|
mqtt->setKeepAlive(MQTT_KEEP_ALIVE_TIME);
|
||||||
mqtt->connect();
|
mqtt->connect();
|
||||||
|
@ -14,6 +14,11 @@ void UsermodManager::addToJsonInfo(JsonObject& obj) { for (byte i = 0; i < n
|
|||||||
void UsermodManager::readFromJsonState(JsonObject& obj) { for (byte i = 0; i < numMods; i++) ums[i]->readFromJsonState(obj); }
|
void UsermodManager::readFromJsonState(JsonObject& obj) { for (byte i = 0; i < numMods; i++) ums[i]->readFromJsonState(obj); }
|
||||||
void UsermodManager::addToConfig(JsonObject& obj) { for (byte i = 0; i < numMods; i++) ums[i]->addToConfig(obj); }
|
void UsermodManager::addToConfig(JsonObject& obj) { for (byte i = 0; i < numMods; i++) ums[i]->addToConfig(obj); }
|
||||||
void UsermodManager::readFromConfig(JsonObject& obj) { for (byte i = 0; i < numMods; i++) ums[i]->readFromConfig(obj); }
|
void UsermodManager::readFromConfig(JsonObject& obj) { for (byte i = 0; i < numMods; i++) ums[i]->readFromConfig(obj); }
|
||||||
|
void UsermodManager::onMqttConnect(bool sessionPresent) { for (byte i = 0; i < numMods; i++) ums[i]->onMqttConnect(sessionPresent); }
|
||||||
|
bool UsermodManager::onMqttMessage(char* topic, char* payload) {
|
||||||
|
for (byte i = 0; i < numMods; i++) if (ums[i]->onMqttMessage(topic, payload)) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Enables usermods to lookup another Usermod.
|
* Enables usermods to lookup another Usermod.
|
||||||
|
@ -20,7 +20,10 @@
|
|||||||
#include "../usermods/buzzer/usermod_v2_buzzer.h"
|
#include "../usermods/buzzer/usermod_v2_buzzer.h"
|
||||||
#endif
|
#endif
|
||||||
#ifdef USERMOD_SENSORSTOMQTT
|
#ifdef USERMOD_SENSORSTOMQTT
|
||||||
#include "usermod_v2_SensorsToMqtt.h"
|
#include "../usermods/sensors_to_mqtt/usermod_v2_SensorsToMqtt.h"
|
||||||
|
#endif
|
||||||
|
#ifdef USERMOD_PIRSWITCH
|
||||||
|
#include "../usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USERMOD_MODE_SORT
|
#ifdef USERMOD_MODE_SORT
|
||||||
@ -31,7 +34,7 @@
|
|||||||
#ifdef USERMOD_BME280
|
#ifdef USERMOD_BME280
|
||||||
#include "../usermods/BME280_v2/usermod_bme280.h"
|
#include "../usermods/BME280_v2/usermod_bme280.h"
|
||||||
#endif
|
#endif
|
||||||
#ifdef USERMOD_FOUR_LINE_DISLAY
|
#ifdef USERMOD_FOUR_LINE_DISPLAY
|
||||||
#include "../usermods/usermod_v2_four_line_display/usermod_v2_four_line_display.h"
|
#include "../usermods/usermod_v2_four_line_display/usermod_v2_four_line_display.h"
|
||||||
#endif
|
#endif
|
||||||
#ifdef USERMOD_ROTARY_ENCODER_UI
|
#ifdef USERMOD_ROTARY_ENCODER_UI
|
||||||
@ -50,6 +53,14 @@
|
|||||||
#include "../usermods/VL53L0X_gestures/usermod_vl53l0x_gestures.h"
|
#include "../usermods/VL53L0X_gestures/usermod_vl53l0x_gestures.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USERMOD_ANIMATED_STAIRCASE
|
||||||
|
#include "../usermods/Animated_Staircase/Animated_Staircase.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef USERMOD_MULTI_RELAY
|
||||||
|
#include "../usermods/multi_relay/usermod_multi_relay.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
void registerUsermods()
|
void registerUsermods()
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
@ -72,28 +83,39 @@ void registerUsermods()
|
|||||||
#ifdef USERMOD_BME280
|
#ifdef USERMOD_BME280
|
||||||
usermods.add(new UsermodBME280());
|
usermods.add(new UsermodBME280());
|
||||||
#endif
|
#endif
|
||||||
#ifdef USERMOD_SENSORSTOMQTT
|
#ifdef USERMOD_SENSORSTOMQTT
|
||||||
usermods.add(new UserMod_SensorsToMQTT());
|
usermods.add(new UserMod_SensorsToMQTT());
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USERMOD_PIRSWITCH
|
||||||
|
usermods.add(new PIRsensorSwitch());
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef USERMOD_MODE_SORT
|
#ifdef USERMOD_MODE_SORT
|
||||||
usermods.add(new ModeSortUsermod());
|
usermods.add(new ModeSortUsermod());
|
||||||
#endif
|
#endif
|
||||||
#ifdef USERMOD_FOUR_LINE_DISLAY
|
#ifdef USERMOD_FOUR_LINE_DISPLAY
|
||||||
usermods.add(new FourLineDisplayUsermod());
|
usermods.add(new FourLineDisplayUsermod());
|
||||||
#endif
|
#endif
|
||||||
#ifdef USERMOD_ROTARY_ENCODER_UI
|
#ifdef USERMOD_ROTARY_ENCODER_UI
|
||||||
usermods.add(new RotaryEncoderUIUsermod());
|
usermods.add(new RotaryEncoderUIUsermod()); // can use USERMOD_FOUR_LINE_DISPLAY
|
||||||
#endif
|
#endif
|
||||||
#ifdef USERMOD_AUTO_SAVE
|
#ifdef USERMOD_AUTO_SAVE
|
||||||
usermods.add(new AutoSaveUsermod());
|
usermods.add(new AutoSaveUsermod()); // can use USERMOD_FOUR_LINE_DISPLAY
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USERMOD_DHT
|
#ifdef USERMOD_DHT
|
||||||
usermods.add(new UsermodDHT());
|
usermods.add(new UsermodDHT());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USERMOD_VL53L0X_GESTURES
|
#ifdef USERMOD_VL53L0X_GESTURES
|
||||||
usermods.add(new UsermodVL53L0XGestures());
|
usermods.add(new UsermodVL53L0XGestures());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USERMOD_ANIMATED_STAIRCASE
|
||||||
|
usermods.add(new Animated_Staircase());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef USERMOD_MULTI_RELAY
|
||||||
|
usermods.add(new MultiRelay());
|
||||||
|
#endif
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user