Updated usermod readme and usability enhancements.
This commit is contained in:
parent
e0f17e1778
commit
12f9ad8f7f
@ -10,465 +10,510 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "wled.h"
|
#include "wled.h"
|
||||||
|
|
||||||
#define USERMOD_ID_ANIMATED_STAIRCASE 1011
|
|
||||||
|
|
||||||
class Animated_Staircase : public Usermod {
|
class Animated_Staircase : public Usermod {
|
||||||
private:
|
private:
|
||||||
|
|
||||||
/* configuration (available in API and stored in flash) */
|
/* configuration (available in API and stored in flash) */
|
||||||
bool enabled = false; // Enable this usermod
|
bool enabled = false; // Enable this usermod
|
||||||
unsigned long segment_delay_ms = 150; // Time between switching each segment
|
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
|
unsigned long on_time_ms = 30000; // The time for the light to stay on
|
||||||
int8_t topPIRorTriggerPin = -1; // disabled
|
int8_t topPIRorTriggerPin = -1; // disabled
|
||||||
int8_t bottomPIRorTriggerPin = -1; // disabled
|
int8_t bottomPIRorTriggerPin = -1; // disabled
|
||||||
int8_t topEchoPin = -1; // disabled
|
int8_t topEchoPin = -1; // disabled
|
||||||
int8_t bottomEchoPin = -1; // disabled
|
int8_t bottomEchoPin = -1; // disabled
|
||||||
bool useUSSensorTop = false; // using PIR or UltraSound sensor?
|
bool useUSSensorTop = false; // using PIR or UltraSound sensor?
|
||||||
bool useUSSensorBottom = false; // using PIR or UltraSound sensor?
|
bool useUSSensorBottom = false; // using PIR or UltraSound sensor?
|
||||||
unsigned int topMaxTimeUs = 1749; // default echo timout, top
|
unsigned int topMaxDist = 50; // default maximum measured distance in cm, top
|
||||||
unsigned int bottomMaxTimeUs = 1749; // default echo timout, bottom
|
unsigned int bottomMaxDist = 50; // default maximum measured distance in cm, bottom
|
||||||
|
|
||||||
/* runtime variables */
|
/* runtime variables */
|
||||||
bool initDone = false;
|
bool initDone = false;
|
||||||
|
|
||||||
// Time between checking of the sensors
|
// Time between checking of the sensors
|
||||||
const unsigned int scanDelay = 50;
|
const unsigned int scanDelay = 100;
|
||||||
|
|
||||||
// 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;
|
||||||
|
|
||||||
// Swipe direction for current transition
|
// Swipe direction for current transition
|
||||||
#define SWIPE_UP true
|
#define SWIPE_UP true
|
||||||
#define SWIPE_DOWN false
|
#define SWIPE_DOWN false
|
||||||
bool swipe = SWIPE_UP;
|
bool swipe = SWIPE_UP;
|
||||||
|
|
||||||
// Indicates which Sensor was seen last (to determine
|
// Indicates which Sensor was seen last (to determine
|
||||||
// the direction when swiping off)
|
// the direction when swiping off)
|
||||||
#define LOWER false
|
#define LOWER false
|
||||||
#define UPPER true
|
#define UPPER true
|
||||||
bool lastSensor = LOWER;
|
bool lastSensor = LOWER;
|
||||||
|
|
||||||
// Time of the last transition action
|
// Time of the last transition action
|
||||||
unsigned long lastTime = 0;
|
unsigned long lastTime = 0;
|
||||||
|
|
||||||
// Time of the last sensor check
|
// Time of the last sensor check
|
||||||
unsigned long lastScanTime = 0;
|
unsigned long lastScanTime = 0;
|
||||||
|
|
||||||
// Last time the lights were switched on or off
|
// Last time the lights were switched on or off
|
||||||
unsigned long lastSwitchTime = 0;
|
unsigned long lastSwitchTime = 0;
|
||||||
|
|
||||||
// segment id between onIndex and offIndex are on.
|
// segment id between onIndex and offIndex are on.
|
||||||
// controll the swipe by setting/moving these indices around.
|
// controll the swipe by setting/moving these indices around.
|
||||||
// onIndex must be less than or equal to offIndex
|
// onIndex must be less than or equal to offIndex
|
||||||
byte onIndex = 0;
|
byte onIndex = 0;
|
||||||
byte offIndex = 0;
|
byte offIndex = 0;
|
||||||
|
|
||||||
// The maximum number of configured segments.
|
// The maximum number of configured segments.
|
||||||
// Dynamically updated based on user configuration.
|
// Dynamically updated based on user configuration.
|
||||||
byte maxSegmentId = 1;
|
byte maxSegmentId = 1;
|
||||||
byte mainSegmentId = 0;
|
byte mainSegmentId = 0;
|
||||||
|
|
||||||
// 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
|
||||||
bool topSensorRead = false;
|
bool topSensorRead = false;
|
||||||
bool topSensorWrite = false;
|
bool topSensorWrite = false;
|
||||||
bool bottomSensorRead = false;
|
bool bottomSensorRead = false;
|
||||||
bool bottomSensorWrite = false;
|
bool bottomSensorWrite = false;
|
||||||
|
bool topSensorState = false;
|
||||||
|
bool bottomSensorState = false;
|
||||||
|
|
||||||
// strings to reduce flash memory usage (used more than twice)
|
// strings to reduce flash memory usage (used more than twice)
|
||||||
static const char _name[];
|
static const char _name[];
|
||||||
static const char _enabled[];
|
static const char _enabled[];
|
||||||
static const char _segmentDelay[];
|
static const char _segmentDelay[];
|
||||||
static const char _onTime[];
|
static const char _onTime[];
|
||||||
static const char _useTopUltrasoundSensor[];
|
static const char _useTopUltrasoundSensor[];
|
||||||
static const char _topPIRorTrigger_pin[];
|
static const char _topPIRorTrigger_pin[];
|
||||||
static const char _topEcho_pin[];
|
static const char _topEcho_pin[];
|
||||||
static const char _useBottomUltrasoundSensor[];
|
static const char _useBottomUltrasoundSensor[];
|
||||||
static const char _bottomPIRorTrigger_pin[];
|
static const char _bottomPIRorTrigger_pin[];
|
||||||
static const char _bottomEcho_pin[];
|
static const char _bottomEcho_pin[];
|
||||||
static const char _topEchoTime[];
|
static const char _topEchoCm[];
|
||||||
static const char _bottomEchoTime[];
|
static const char _bottomEchoCm[];
|
||||||
static const char _[];
|
|
||||||
|
void publishMqtt(bool bottom, const char* state)
|
||||||
void updateSegments() {
|
{
|
||||||
// mainSegmentId = strip.getMainSegmentId();
|
//Check if MQTT Connected, otherwise it will crash the 8266
|
||||||
// WS2812FX::Segment mainsegment = strip.getSegment(mainSegmentId);
|
if (WLED_MQTT_CONNECTED){
|
||||||
WS2812FX::Segment* segments = strip.getSegments();
|
char subuf[64];
|
||||||
for (int i = 0; i < MAX_NUM_SEGMENTS; i++, segments++) {
|
sprintf_P(subuf, PSTR("%s/motion/%d"), mqttDeviceTopic, (int)bottom);
|
||||||
if (!segments->isActive()) {
|
mqtt->publish(subuf, 0, false, state);
|
||||||
maxSegmentId = i - 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i >= onIndex && i < offIndex) {
|
|
||||||
segments->setOption(SEG_OPTION_ON, 1, 1);
|
|
||||||
|
|
||||||
// We may need to copy mode and colors from segment 0 to make sure
|
|
||||||
// changes are propagated even when the config is changed during a wipe
|
|
||||||
// segments->mode = mainsegment.mode;
|
|
||||||
// segments->colors[0] = mainsegment.colors[0];
|
|
||||||
} else {
|
|
||||||
segments->setOption(SEG_OPTION_ON, 0, 1);
|
|
||||||
}
|
|
||||||
// Always mark segments as "transitional", we are animating the staircase
|
|
||||||
segments->setOption(SEG_OPTION_TRANSITIONAL, 1, 1);
|
|
||||||
}
|
|
||||||
colorUpdated(NOTIFIER_CALL_MODE_DIRECT_CHANGE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Detects if an object is within ultrasound range.
|
|
||||||
* signalPin: The pin where the pulse is sent
|
|
||||||
* echoPin: The pin where the echo is received
|
|
||||||
* maxTimeUs: Detection timeout in microseconds. If an echo is
|
|
||||||
* received within this time, an object is detected
|
|
||||||
* and the function will return true.
|
|
||||||
*
|
|
||||||
* The speed of sound is 343 meters per second at 20 degress Celcius.
|
|
||||||
* Since the sound has to travel back and forth, the detection
|
|
||||||
* distance for the sensor in cm is (0.0343 * maxTimeUs) / 2.
|
|
||||||
*
|
|
||||||
* For practical reasons, here are some useful distances:
|
|
||||||
*
|
|
||||||
* Distance = maxtime
|
|
||||||
* 5 cm = 292 uS
|
|
||||||
* 10 cm = 583 uS
|
|
||||||
* 20 cm = 1166 uS
|
|
||||||
* 30 cm = 1749 uS
|
|
||||||
* 50 cm = 2915 uS
|
|
||||||
* 100 cm = 5831 uS
|
|
||||||
*/
|
|
||||||
bool ultrasoundRead(uint8_t signalPin,
|
|
||||||
uint8_t echoPin,
|
|
||||||
unsigned int maxTimeUs) {
|
|
||||||
digitalWrite(signalPin, HIGH);
|
|
||||||
delayMicroseconds(10);
|
|
||||||
digitalWrite(signalPin, LOW);
|
|
||||||
return pulseIn(echoPin, HIGH, maxTimeUs) > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void checkSensors() {
|
|
||||||
if ((millis() - lastScanTime) > scanDelay) {
|
|
||||||
lastScanTime = millis();
|
|
||||||
|
|
||||||
if (!useUSSensorBottom)
|
|
||||||
bottomSensorRead = bottomSensorWrite || (digitalRead(bottomPIRorTriggerPin) == HIGH);
|
|
||||||
else
|
|
||||||
bottomSensorRead = bottomSensorWrite || ultrasoundRead(bottomPIRorTriggerPin, bottomEchoPin, bottomMaxTimeUs);
|
|
||||||
|
|
||||||
if (!useUSSensorTop)
|
|
||||||
topSensorRead = topSensorWrite || (digitalRead(topPIRorTriggerPin) == HIGH);
|
|
||||||
else
|
|
||||||
topSensorRead = topSensorWrite || ultrasoundRead(topPIRorTriggerPin, topEchoPin, topMaxTimeUs);
|
|
||||||
|
|
||||||
// Values read, reset the flags for next API call
|
|
||||||
topSensorWrite = false;
|
|
||||||
bottomSensorWrite = false;
|
|
||||||
|
|
||||||
if (topSensorRead != bottomSensorRead) {
|
|
||||||
lastSwitchTime = millis();
|
|
||||||
|
|
||||||
if (on) {
|
|
||||||
lastSensor = topSensorRead;
|
|
||||||
} else {
|
|
||||||
// If the bottom sensor triggered, we need to swipe up, ON
|
|
||||||
swipe = bottomSensorRead;
|
|
||||||
|
|
||||||
if (swipe) {
|
|
||||||
DEBUG_PRINTLN(F("ON -> Swipe up."));
|
|
||||||
} else {
|
|
||||||
DEBUG_PRINTLN(F("ON -> Swipe down."));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (onIndex == offIndex) {
|
|
||||||
// Position the indices for a correct on-swipe
|
|
||||||
if (swipe == SWIPE_UP) {
|
|
||||||
onIndex = mainSegmentId;
|
|
||||||
} else {
|
|
||||||
onIndex = maxSegmentId+1;
|
|
||||||
}
|
|
||||||
offIndex = onIndex;
|
|
||||||
}
|
|
||||||
on = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void autoPowerOff() {
|
void updateSegments() {
|
||||||
// TODO: add logic to wait until PIR sensor deactivates
|
mainSegmentId = strip.getMainSegmentId();
|
||||||
if (on && ((millis() - lastSwitchTime) > on_time_ms)) {
|
|
||||||
// Swipe OFF in the direction of the last sensor detection
|
|
||||||
swipe = lastSensor;
|
|
||||||
on = false;
|
|
||||||
|
|
||||||
if (swipe) {
|
|
||||||
DEBUG_PRINTLN(F("OFF -> Swipe up."));
|
|
||||||
} else {
|
|
||||||
DEBUG_PRINTLN(F("OFF -> Swipe down."));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void updateSwipe() {
|
|
||||||
if ((millis() - lastTime) > segment_delay_ms) {
|
|
||||||
lastTime = millis();
|
|
||||||
|
|
||||||
// byte oldOnIndex = onIndex;
|
|
||||||
// byte oldOffIndex = offIndex;
|
|
||||||
|
|
||||||
if (on) {
|
|
||||||
// Turn on all segments
|
|
||||||
onIndex = MAX(mainSegmentId, onIndex - 1);
|
|
||||||
offIndex = MIN(maxSegmentId + 1, offIndex + 1);
|
|
||||||
} else {
|
|
||||||
if (swipe == SWIPE_UP) {
|
|
||||||
onIndex = MIN(offIndex, onIndex + 1);
|
|
||||||
} else {
|
|
||||||
offIndex = MAX(onIndex, offIndex - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateSegments();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// send sesnor values to JSON API
|
|
||||||
void writeSensorsToJson(JsonObject& staircase) {
|
|
||||||
staircase[F("top-sensor")] = topSensorRead;
|
|
||||||
staircase[F("bottom-sensor")] = bottomSensorRead;
|
|
||||||
}
|
|
||||||
|
|
||||||
// allow overrides from JSON API
|
|
||||||
void readSensorsFromJson(JsonObject& staircase) {
|
|
||||||
bottomSensorWrite = bottomSensorRead || (staircase[F("bottom-sensor")].as<bool>());
|
|
||||||
topSensorWrite = topSensorRead || (staircase[F("top-sensor")].as<bool>());
|
|
||||||
}
|
|
||||||
|
|
||||||
void enable(bool enable) {
|
|
||||||
if (enable) {
|
|
||||||
DEBUG_PRINTLN(F("Animated Staircase enabled."));
|
|
||||||
DEBUG_PRINT(F("Delay between steps: "));
|
|
||||||
DEBUG_PRINT(segment_delay_ms);
|
|
||||||
DEBUG_PRINT(F(" milliseconds.\nStairs switch off after: "));
|
|
||||||
DEBUG_PRINT(on_time_ms / 1000);
|
|
||||||
DEBUG_PRINTLN(F(" seconds."));
|
|
||||||
|
|
||||||
// TODO: attach interrupts
|
|
||||||
if (!useUSSensorBottom)
|
|
||||||
pinMode(bottomPIRorTriggerPin, INPUT);
|
|
||||||
else {
|
|
||||||
pinMode(bottomPIRorTriggerPin, OUTPUT);
|
|
||||||
pinMode(bottomEchoPin, INPUT);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!useUSSensorTop)
|
|
||||||
pinMode(topPIRorTriggerPin, INPUT);
|
|
||||||
else {
|
|
||||||
pinMode(topPIRorTriggerPin, OUTPUT);
|
|
||||||
pinMode(topEchoPin, INPUT);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Restore segment options
|
|
||||||
// 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()) {
|
||||||
maxSegmentId = i - 1;
|
maxSegmentId = i - 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
segments->setOption(SEG_OPTION_ON, 1, 1);
|
|
||||||
|
if (i >= onIndex && i < offIndex) {
|
||||||
|
segments->setOption(SEG_OPTION_ON, 1, 1);
|
||||||
|
|
||||||
|
// We may need to copy mode and colors from segment 0 to make sure
|
||||||
|
// changes are propagated even when the config is changed during a wipe
|
||||||
|
// segments->mode = mainsegment.mode;
|
||||||
|
// segments->colors[0] = mainsegment.colors[0];
|
||||||
|
} else {
|
||||||
|
segments->setOption(SEG_OPTION_ON, 0, 1);
|
||||||
|
}
|
||||||
|
// Always mark segments as "transitional", we are animating the staircase
|
||||||
|
segments->setOption(SEG_OPTION_TRANSITIONAL, 1, 1);
|
||||||
}
|
}
|
||||||
colorUpdated(NOTIFIER_CALL_MODE_DIRECT_CHANGE);
|
colorUpdated(NOTIFIER_CALL_MODE_DIRECT_CHANGE);
|
||||||
DEBUG_PRINTLN(F("Animated Staircase disabled."));
|
|
||||||
}
|
}
|
||||||
enabled = enable;
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
/*
|
||||||
void setup() {
|
* Detects if an object is within ultrasound range.
|
||||||
// allocate pins
|
* signalPin: The pin where the pulse is sent
|
||||||
if (topPIRorTriggerPin >= 0) {
|
* echoPin: The pin where the echo is received
|
||||||
if (!pinManager.allocatePin(topPIRorTriggerPin,useUSSensorTop))
|
* maxTimeUs: Detection timeout in microseconds. If an echo is
|
||||||
topPIRorTriggerPin = -1;
|
* received within this time, an object is detected
|
||||||
|
* and the function will return true.
|
||||||
|
*
|
||||||
|
* The speed of sound is 343 meters per second at 20 degress Celcius.
|
||||||
|
* Since the sound has to travel back and forth, the detection
|
||||||
|
* distance for the sensor in cm is (0.0343 * maxTimeUs) / 2.
|
||||||
|
*
|
||||||
|
* For practical reasons, here are some useful distances:
|
||||||
|
*
|
||||||
|
* Distance = maxtime
|
||||||
|
* 5 cm = 292 uS
|
||||||
|
* 10 cm = 583 uS
|
||||||
|
* 20 cm = 1166 uS
|
||||||
|
* 30 cm = 1749 uS
|
||||||
|
* 50 cm = 2915 uS
|
||||||
|
* 100 cm = 5831 uS
|
||||||
|
*/
|
||||||
|
bool ultrasoundRead(int8_t signalPin, int8_t echoPin, unsigned int maxTimeUs) {
|
||||||
|
if (signalPin<0 || echoPin<0) return false;
|
||||||
|
digitalWrite(signalPin, LOW);
|
||||||
|
delayMicroseconds(2);
|
||||||
|
digitalWrite(signalPin, HIGH);
|
||||||
|
delayMicroseconds(10);
|
||||||
|
digitalWrite(signalPin, LOW);
|
||||||
|
return pulseIn(echoPin, HIGH, maxTimeUs) > 0;
|
||||||
}
|
}
|
||||||
if (topEchoPin >= 0) {
|
|
||||||
if (!pinManager.allocatePin(topEchoPin,false))
|
bool checkSensors() {
|
||||||
topEchoPin = -1;
|
bool sensorChanged = false;
|
||||||
|
|
||||||
|
if ((millis() - lastScanTime) > scanDelay) {
|
||||||
|
lastScanTime = millis();
|
||||||
|
|
||||||
|
bottomSensorRead = bottomSensorWrite ||
|
||||||
|
(!useUSSensorBottom ?
|
||||||
|
(bottomPIRorTriggerPin<0 ? false : digitalRead(bottomPIRorTriggerPin)) :
|
||||||
|
ultrasoundRead(bottomPIRorTriggerPin, bottomEchoPin, bottomMaxDist*59) // cm to us
|
||||||
|
);
|
||||||
|
topSensorRead = topSensorWrite ||
|
||||||
|
(!useUSSensorTop ?
|
||||||
|
(topPIRorTriggerPin<0 ? false : digitalRead(topPIRorTriggerPin)) :
|
||||||
|
ultrasoundRead(topPIRorTriggerPin, topEchoPin, topMaxDist*59) // cm to us
|
||||||
|
);
|
||||||
|
|
||||||
|
if (bottomSensorRead != bottomSensorState) {
|
||||||
|
bottomSensorState = bottomSensorRead; // change previous state
|
||||||
|
sensorChanged = true;
|
||||||
|
publishMqtt(true, bottomSensorState ? "on" : "off");
|
||||||
|
DEBUG_PRINTLN(F("Bottom sensor changed."));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (topSensorRead != topSensorState) {
|
||||||
|
topSensorState = topSensorRead; // change previous state
|
||||||
|
sensorChanged = true;
|
||||||
|
publishMqtt(false, topSensorState ? "on" : "off");
|
||||||
|
DEBUG_PRINTLN(F("Top sensor changed."));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Values read, reset the flags for next API call
|
||||||
|
topSensorWrite = false;
|
||||||
|
bottomSensorWrite = false;
|
||||||
|
|
||||||
|
if (topSensorRead != bottomSensorRead) {
|
||||||
|
lastSwitchTime = millis();
|
||||||
|
|
||||||
|
if (on) {
|
||||||
|
lastSensor = topSensorRead;
|
||||||
|
} else {
|
||||||
|
// If the bottom sensor triggered, we need to swipe up, ON
|
||||||
|
swipe = bottomSensorRead;
|
||||||
|
|
||||||
|
DEBUG_PRINT(F("ON -> Swipe "));
|
||||||
|
DEBUG_PRINTLN(swipe ? F("up.") : F("down."));
|
||||||
|
|
||||||
|
if (onIndex == offIndex) {
|
||||||
|
// Position the indices for a correct on-swipe
|
||||||
|
if (swipe == SWIPE_UP) {
|
||||||
|
onIndex = mainSegmentId;
|
||||||
|
} else {
|
||||||
|
onIndex = maxSegmentId+1;
|
||||||
|
}
|
||||||
|
offIndex = onIndex;
|
||||||
|
}
|
||||||
|
on = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sensorChanged;
|
||||||
}
|
}
|
||||||
if (bottomPIRorTriggerPin >= 0) {
|
|
||||||
if (!pinManager.allocatePin(bottomPIRorTriggerPin,useUSSensorBottom))
|
void autoPowerOff() {
|
||||||
bottomPIRorTriggerPin = -1;
|
if (on && ((millis() - lastSwitchTime) > on_time_ms)) {
|
||||||
|
// if sensors are still on, do nothing
|
||||||
|
if (bottomSensorState || topSensorState) return;
|
||||||
|
|
||||||
|
// Swipe OFF in the direction of the last sensor detection
|
||||||
|
swipe = lastSensor;
|
||||||
|
on = false;
|
||||||
|
|
||||||
|
DEBUG_PRINT(F("OFF -> Swipe "));
|
||||||
|
DEBUG_PRINTLN(swipe ? F("up.") : F("down."));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (bottomEchoPin >= 0) {
|
|
||||||
if (!pinManager.allocatePin(bottomPIRorTriggerPin,false))
|
void updateSwipe() {
|
||||||
bottomEchoPin = -1;
|
if ((millis() - lastTime) > segment_delay_ms) {
|
||||||
|
lastTime = millis();
|
||||||
|
|
||||||
|
if (on) {
|
||||||
|
// Turn on all segments
|
||||||
|
onIndex = MAX(mainSegmentId, onIndex - 1);
|
||||||
|
offIndex = MIN(maxSegmentId + 1, offIndex + 1);
|
||||||
|
} else {
|
||||||
|
if (swipe == SWIPE_UP) {
|
||||||
|
onIndex = MIN(offIndex, onIndex + 1);
|
||||||
|
} else {
|
||||||
|
offIndex = MAX(onIndex, offIndex - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateSegments();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// TODO: attach interrupts in enable()
|
|
||||||
|
|
||||||
// validate pins
|
// send sesnor values to JSON API
|
||||||
if ( topPIRorTriggerPin < 0 || bottomPIRorTriggerPin < 0 ||
|
void writeSensorsToJson(JsonObject& staircase) {
|
||||||
(useUSSensorTop && topEchoPin < 0) || (useUSSensorBottom && bottomEchoPin < 0) )
|
staircase[F("top-sensor")] = topSensorRead;
|
||||||
enabled = false;
|
staircase[F("bottom-sensor")] = bottomSensorRead;
|
||||||
|
|
||||||
enable(enabled);
|
|
||||||
initDone = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
if (!enabled) return;
|
|
||||||
checkSensors();
|
|
||||||
autoPowerOff();
|
|
||||||
updateSwipe();
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t getId() { return USERMOD_ID_ANIMATED_STAIRCASE; }
|
|
||||||
|
|
||||||
void addToJsonState(JsonObject& root) {
|
|
||||||
JsonObject staircase = root[FPSTR(_name)];
|
|
||||||
if (staircase.isNull()) {
|
|
||||||
staircase = root.createNestedObject(FPSTR(_name));
|
|
||||||
}
|
}
|
||||||
writeSensorsToJson(staircase);
|
|
||||||
DEBUG_PRINTLN(F("Staircase sensor state exposed in API."));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
// allow overrides from JSON API
|
||||||
* Reads configuration settings from the json API.
|
void readSensorsFromJson(JsonObject& staircase) {
|
||||||
* See void addToJsonState(JsonObject& root)
|
bottomSensorWrite = bottomSensorState || (staircase[F("bottom-sensor")].as<bool>());
|
||||||
*/
|
topSensorWrite = topSensorState || (staircase[F("top-sensor")].as<bool>());
|
||||||
void readFromJsonState(JsonObject& root) {
|
}
|
||||||
if (!initDone) return; // prevent crash on boot applyPreset()
|
|
||||||
JsonObject staircase = root[FPSTR(_name)];
|
void enable(bool enable) {
|
||||||
if (!staircase.isNull()) {
|
if (enable) {
|
||||||
if (staircase[FPSTR(_enabled)].is<bool>()) {
|
DEBUG_PRINTLN(F("Animated Staircase enabled."));
|
||||||
enabled = staircase[FPSTR(_enabled)].as<bool>();
|
DEBUG_PRINT(F("Delay between steps: "));
|
||||||
|
DEBUG_PRINT(segment_delay_ms);
|
||||||
|
DEBUG_PRINT(F(" milliseconds.\nStairs switch off after: "));
|
||||||
|
DEBUG_PRINT(on_time_ms / 1000);
|
||||||
|
DEBUG_PRINTLN(F(" seconds."));
|
||||||
|
|
||||||
|
if (!useUSSensorBottom)
|
||||||
|
pinMode(bottomPIRorTriggerPin, INPUT_PULLUP);
|
||||||
|
else {
|
||||||
|
pinMode(bottomPIRorTriggerPin, OUTPUT);
|
||||||
|
pinMode(bottomEchoPin, INPUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!useUSSensorTop)
|
||||||
|
pinMode(topPIRorTriggerPin, INPUT_PULLUP);
|
||||||
|
else {
|
||||||
|
pinMode(topPIRorTriggerPin, OUTPUT);
|
||||||
|
pinMode(topEchoPin, INPUT);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
String str = staircase[FPSTR(_enabled)]; // checkbox -> off or on
|
// Restore segment options
|
||||||
enabled = (bool)(str!="off"); // off is guaranteed to be present
|
WS2812FX::Segment* segments = strip.getSegments();
|
||||||
|
for (int i = 0; i < MAX_NUM_SEGMENTS; i++, segments++) {
|
||||||
|
if (!segments->isActive()) {
|
||||||
|
maxSegmentId = i - 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
segments->setOption(SEG_OPTION_ON, 1, 1);
|
||||||
|
}
|
||||||
|
colorUpdated(NOTIFIER_CALL_MODE_DIRECT_CHANGE);
|
||||||
|
DEBUG_PRINTLN(F("Animated Staircase disabled."));
|
||||||
}
|
}
|
||||||
readSensorsFromJson(root);
|
enabled = enable;
|
||||||
DEBUG_PRINTLN(F("Staircase sensor state read from API."));
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
public:
|
||||||
* Writes the configuration to internal flash memory.
|
void setup() {
|
||||||
*/
|
// allocate pins
|
||||||
void addToConfig(JsonObject& root) {
|
if (topPIRorTriggerPin >= 0) {
|
||||||
JsonObject staircase = root[FPSTR(_name)];
|
if (!pinManager.allocatePin(topPIRorTriggerPin,useUSSensorTop))
|
||||||
if (staircase.isNull()) {
|
topPIRorTriggerPin = -1;
|
||||||
staircase = root.createNestedObject(FPSTR(_name));
|
}
|
||||||
|
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(bottomEchoPin,false))
|
||||||
|
bottomEchoPin = -1;
|
||||||
|
}
|
||||||
|
enable(enabled);
|
||||||
|
initDone = true;
|
||||||
}
|
}
|
||||||
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."));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
void loop() {
|
||||||
* Reads the configuration to internal flash memory before setup() is called.
|
if (!enabled || strip.isUpdating()) return;
|
||||||
*/
|
checkSensors();
|
||||||
void readFromConfig(JsonObject& root) {
|
autoPowerOff();
|
||||||
bool oldUseUSSensorTop = useUSSensorTop;
|
updateSwipe();
|
||||||
bool oldUseUSSensorBottom = useUSSensorBottom;
|
}
|
||||||
int8_t oldTopAPin = topPIRorTriggerPin;
|
|
||||||
int8_t oldTopBPin = topEchoPin;
|
|
||||||
int8_t oldBottomAPin = bottomPIRorTriggerPin;
|
|
||||||
int8_t oldBottomBPin = bottomEchoPin;
|
|
||||||
|
|
||||||
JsonObject staircase = root[FPSTR(_name)];
|
uint16_t getId() { return USERMOD_ID_ANIMATED_STAIRCASE; }
|
||||||
if (!staircase.isNull()) {
|
|
||||||
if (staircase[FPSTR(_enabled)].is<bool>()) {
|
/**
|
||||||
enabled = staircase[FPSTR(_enabled)].as<bool>();
|
* handling of MQTT message
|
||||||
|
* topic only contains stripped topic (part after /wled/MAC)
|
||||||
|
* topic should look like: /swipe with amessage of [up|down]
|
||||||
|
*/
|
||||||
|
bool onMqttMessage(char* topic, char* payload) {
|
||||||
|
if (strlen(topic) == 6 && strncmp_P(topic, PSTR("/swipe"), 6) == 0) {
|
||||||
|
String action = payload;
|
||||||
|
if (action == "up") {
|
||||||
|
bottomSensorWrite = true;
|
||||||
|
return true;
|
||||||
|
} else if (action == "down") {
|
||||||
|
topSensorWrite = true;
|
||||||
|
return true;
|
||||||
|
} else if (action == "on") {
|
||||||
|
enable(true);
|
||||||
|
return true;
|
||||||
|
} else if (action == "off") {
|
||||||
|
enable(false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* subscribe to MQTT topic for controlling usermod
|
||||||
|
*/
|
||||||
|
void onMqttConnect(bool sessionPresent) {
|
||||||
|
//(re)subscribe to required topics
|
||||||
|
char subuf[64];
|
||||||
|
if (mqttDeviceTopic[0] != 0) {
|
||||||
|
strcpy(subuf, mqttDeviceTopic);
|
||||||
|
strcat_P(subuf, PSTR("/swipe"));
|
||||||
|
mqtt->subscribe(subuf, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void addToJsonState(JsonObject& root) {
|
||||||
|
JsonObject staircase = root[FPSTR(_name)];
|
||||||
|
if (staircase.isNull()) {
|
||||||
|
staircase = root.createNestedObject(FPSTR(_name));
|
||||||
|
}
|
||||||
|
writeSensorsToJson(staircase);
|
||||||
|
DEBUG_PRINTLN(F("Staircase sensor state exposed in API."));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Reads configuration settings from the json API.
|
||||||
|
* See void addToJsonState(JsonObject& root)
|
||||||
|
*/
|
||||||
|
void readFromJsonState(JsonObject& root) {
|
||||||
|
if (!initDone) return; // prevent crash on boot applyPreset()
|
||||||
|
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
|
||||||
|
}
|
||||||
|
readSensorsFromJson(staircase);
|
||||||
|
DEBUG_PRINTLN(F("Staircase sensor state read from API."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Writes the configuration to internal flash memory.
|
||||||
|
*/
|
||||||
|
void addToConfig(JsonObject& root) {
|
||||||
|
JsonObject staircase = root[FPSTR(_name)];
|
||||||
|
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(_topEchoCm)] = topMaxDist;
|
||||||
|
staircase[FPSTR(_bottomEchoCm)] = bottomMaxDist;
|
||||||
|
DEBUG_PRINTLN(F("Staircase config saved."));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Reads the configuration to internal flash memory before setup() is called.
|
||||||
|
*/
|
||||||
|
void readFromConfig(JsonObject& root) {
|
||||||
|
bool oldUseUSSensorTop = useUSSensorTop;
|
||||||
|
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>()));
|
||||||
|
topMaxDist = min(150,max(30,staircase[FPSTR(_topEchoCm)].as<int>())); // max distnace ~1.5m (a lag of 9ms may be expected)
|
||||||
|
bottomMaxDist = min(150,max(30,staircase[FPSTR(_bottomEchoCm)].as<int>())); // max distance ~1.5m (a lag of 9ms may be expected)
|
||||||
} else {
|
} else {
|
||||||
String str = staircase[FPSTR(_enabled)]; // checkbox -> off or on
|
DEBUG_PRINTLN(F("No config found. (Using defaults.)"));
|
||||||
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
|
if (!initDone) {
|
||||||
on_time_ms = min(900,max(10,staircase[FPSTR(_onTime)].as<int>())) * 1000; // min 10s, max 15min
|
// first run: reading from cfg.json
|
||||||
|
DEBUG_PRINTLN(F("Staircase config loaded."));
|
||||||
if (staircase[FPSTR(_useTopUltrasoundSensor)].is<bool>()) {
|
|
||||||
useUSSensorTop = staircase[FPSTR(_useTopUltrasoundSensor)].as<bool>();
|
|
||||||
} else {
|
} else {
|
||||||
String str = staircase[FPSTR(_useTopUltrasoundSensor)]; // checkbox -> off or on
|
// changing paramters from settings page
|
||||||
useUSSensorTop = (bool)(str!="off"); // off is guaranteed to be present
|
DEBUG_PRINTLN(F("Staircase config (re)loaded."));
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Shows the delay between steps and power-off time in the "info"
|
||||||
|
* tab of the web-UI.
|
||||||
|
*/
|
||||||
|
void addToJsonInfo(JsonObject& root) {
|
||||||
|
JsonObject staircase = root["u"];
|
||||||
|
if (staircase.isNull()) {
|
||||||
|
staircase = root.createNestedObject("u");
|
||||||
}
|
}
|
||||||
|
|
||||||
topPIRorTriggerPin = min(39,max(-1,staircase[FPSTR(_topPIRorTrigger_pin)].as<int>()));
|
JsonArray usermodEnabled = staircase.createNestedArray(F("Staircase")); // name
|
||||||
topEchoPin = min(39,max(-1,staircase[FPSTR(_topEcho_pin)].as<int>()));
|
String btn = F("<button class=\"btn infobtn\" onclick=\"requestJson({staircase:{enabled:");
|
||||||
|
if (enabled) {
|
||||||
if (staircase[FPSTR(_useBottomUltrasoundSensor)].is<bool>()) {
|
btn += F("false}},false,false);loadInfo();\">");
|
||||||
useUSSensorBottom = staircase[FPSTR(_useBottomUltrasoundSensor)].as<bool>();
|
btn += F("enabled");
|
||||||
} else {
|
} else {
|
||||||
String str = staircase[FPSTR(_useBottomUltrasoundSensor)]; // checkbox -> off or on
|
btn += F("true}},false,false);loadInfo();\">");
|
||||||
useUSSensorBottom = (bool)(str!="off"); // off is guaranteed to be present
|
btn += F("disabled");
|
||||||
}
|
}
|
||||||
bottomPIRorTriggerPin = min(39,max(-1,staircase[FPSTR(_bottomPIRorTrigger_pin)].as<int>()));
|
btn += F("</button>");
|
||||||
bottomEchoPin = min(39,max(-1,staircase[FPSTR(_bottomEcho_pin)].as<int>()));
|
usermodEnabled.add(btn); // value
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Shows the delay between steps and power-off time in the "info"
|
|
||||||
* tab of the web-UI.
|
|
||||||
*/
|
|
||||||
void addToJsonInfo(JsonObject& root) {
|
|
||||||
JsonObject staircase = root["u"];
|
|
||||||
if (staircase.isNull()) {
|
|
||||||
staircase = root.createNestedObject("u");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (enabled) {
|
|
||||||
JsonArray usermodEnabled = staircase.createNestedArray(F("Staircase enabled")); // name
|
|
||||||
usermodEnabled.add("yes"); // value
|
|
||||||
|
|
||||||
JsonArray segmentDelay = staircase.createNestedArray(F("Delay between stairs")); // name
|
|
||||||
segmentDelay.add(segment_delay_ms); // value
|
|
||||||
segmentDelay.add("ms"); // unit
|
|
||||||
|
|
||||||
JsonArray onTime = staircase.createNestedArray(F("Power-off stairs after")); // name
|
|
||||||
onTime.add(on_time_ms / 1000); // value
|
|
||||||
onTime.add("s"); // unit
|
|
||||||
} else {
|
|
||||||
JsonArray usermodEnabled = staircase.createNestedArray(F("Staircase enabled")); // name
|
|
||||||
usermodEnabled.add("no"); // value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// strings to reduce flash memory usage (used more than twice)
|
// strings to reduce flash memory usage (used more than twice)
|
||||||
@ -482,5 +527,5 @@ const char Animated_Staircase::_topEcho_pin[] PROGMEM = "topEcho_p
|
|||||||
const char Animated_Staircase::_useBottomUltrasoundSensor[] PROGMEM = "useBottomUltrasoundSensor";
|
const char Animated_Staircase::_useBottomUltrasoundSensor[] PROGMEM = "useBottomUltrasoundSensor";
|
||||||
const char Animated_Staircase::_bottomPIRorTrigger_pin[] PROGMEM = "bottomPIRorTrigger_pin";
|
const char Animated_Staircase::_bottomPIRorTrigger_pin[] PROGMEM = "bottomPIRorTrigger_pin";
|
||||||
const char Animated_Staircase::_bottomEcho_pin[] PROGMEM = "bottomEcho_pin";
|
const char Animated_Staircase::_bottomEcho_pin[] PROGMEM = "bottomEcho_pin";
|
||||||
const char Animated_Staircase::_topEchoTime[] PROGMEM = "top-echo-us";
|
const char Animated_Staircase::_topEchoCm[] PROGMEM = "top-dist-cm";
|
||||||
const char Animated_Staircase::_bottomEchoTime[] PROGMEM = "bottom-echo-us";
|
const char Animated_Staircase::_bottomEchoCm[] PROGMEM = "bottom-dist-cm";
|
||||||
|
@ -56,9 +56,6 @@ or remove them and put everything on one line.
|
|||||||
| Setting | Description | Default |
|
| Setting | Description | Default |
|
||||||
|------------------|---------------------------------------------------------------|---------|
|
|------------------|---------------------------------------------------------------|---------|
|
||||||
| enabled | Enable or disable the usermod | true |
|
| enabled | Enable or disable the usermod | true |
|
||||||
| segment-delay-ms | Delay (milliseconds) between switching on/off each step | 150 |
|
|
||||||
| on-time-s | Time (seconds) the stairs stay lit after last detection | 5 |
|
|
||||||
| bottom-echo-us | Detection range of ultrasonic sensor | 1749 |
|
|
||||||
| bottom-sensor | Manually trigger a down to up animation via API | false |
|
| bottom-sensor | Manually trigger a down to up animation via API | false |
|
||||||
| top-sensor | Manually trigger an up to down animation via API | false |
|
| top-sensor | Manually trigger an up to down animation via API | false |
|
||||||
|
|
||||||
@ -72,8 +69,6 @@ The staircase settings and sensor states are inside the WLED status element:
|
|||||||
"state": {
|
"state": {
|
||||||
"staircase": {
|
"staircase": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"segment-delay-ms": 150,
|
|
||||||
"on-time-s": 5,
|
|
||||||
"bottom-sensor": false,
|
"bottom-sensor": false,
|
||||||
"tops-ensor": false
|
"tops-ensor": false
|
||||||
},
|
},
|
||||||
@ -94,58 +89,16 @@ curl -X POST -H "Content-Type: application/json" \
|
|||||||
|
|
||||||
To enable the usermod again, use `"enabled":true`.
|
To enable the usermod again, use `"enabled":true`.
|
||||||
|
|
||||||
### Changing animation parameters
|
Alternatively you can use _Usermod_ Settings page where you can change other parameters as well.
|
||||||
To change the delay between the steps to (for example) 100 milliseconds and the on-time to
|
|
||||||
10 seconds:
|
|
||||||
|
|
||||||
```bash
|
### Changing animation parameters and detection range of the ultrasonic HC-SR04 sensor
|
||||||
curl -X POST -H "Content-Type: application/json" \
|
Using _Usermod_ Settings page you can define different usermod parameters, includng sensor pins, delay between segment activation and so on.
|
||||||
-d '{"staircase":{"segment-delay-ms":100,"on-time-s":10}}' \
|
|
||||||
xxx.xxx.xxx.xxx/json/state
|
|
||||||
```
|
|
||||||
|
|
||||||
### Changing detection range of the ultrasonic HC-SR04 sensor
|
When an ultrasonic sensor is enabled you can enter maximum detection distance in centimeters separately for top and bottom sensors.
|
||||||
When an ultrasonic sensor is enabled in `Animated_Staircase_config.h`, you'll see a
|
|
||||||
`bottom-echo-us` setting appear in the json api:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"state": {
|
|
||||||
"staircase": {
|
|
||||||
"enabled": true,
|
|
||||||
"segment-delay-ms": 150,
|
|
||||||
"on-time-s": 5,
|
|
||||||
"bottom-echo-us": 1749
|
|
||||||
},
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
If the HC-SR04 sensor detects an echo within 1749 microseconds (corresponding to ~30 cm
|
|
||||||
detection range from the sensor), it will trigger switching on the staircase. This setting
|
|
||||||
can be changed through the API with an HTTP POST:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -X POST -H "Content-Type: application/json" \
|
|
||||||
-d '{"staircase":{"bottom-echo-us":1166}}' \
|
|
||||||
xxx.xxx.xxx.xxx/json/state
|
|
||||||
```
|
|
||||||
|
|
||||||
Calculating the detection range can be performed as follows: The speed of sound is 343m/s at 20
|
|
||||||
degrees Centigrade. Since the sound has to travel back and forth, the detection range for the
|
|
||||||
sensor in cm is (0.0343 * maxTimeUs) / 2. To get you started, please find delays and distances below:
|
|
||||||
|
|
||||||
| Distance | Detection time |
|
|
||||||
|---------:|----------------:|
|
|
||||||
| 5 cm | 292 uS |
|
|
||||||
| 10 cm | 583 uS |
|
|
||||||
| 20 cm | 1166 uS |
|
|
||||||
| 30 cm | 1749 uS |
|
|
||||||
| 50 cm | 2915 uS |
|
|
||||||
| 100 cm | 5831 uS |
|
|
||||||
|
|
||||||
**Please note:** that using an HC-SR04 sensor, particularly when detecting echos at longer
|
**Please note:** that using an HC-SR04 sensor, particularly when detecting echos at longer
|
||||||
distances creates delays in the WLED software, and _might_ introduce timing hickups in your animations or
|
distances creates delays in the WLED software, and _might_ introduce timing hickups in your animations or
|
||||||
a less responsive web interface. It is therefore advised to keep the detection time as short as possible.
|
a less responsive web interface. It is therefore advised to keep the detection distance as short as possible.
|
||||||
|
|
||||||
### Animation triggering through the API
|
### Animation triggering through the API
|
||||||
Instead of stairs activation by one of the sensors, you can also trigger the animation through
|
Instead of stairs activation by one of the sensors, you can also trigger the animation through
|
||||||
@ -164,10 +117,15 @@ curl -X POST -H "Content-Type: application/json" \
|
|||||||
-d '{"staircase":{"top-sensor":true}}' \
|
-d '{"staircase":{"top-sensor":true}}' \
|
||||||
xxx.xxx.xxx.xxx/json/state
|
xxx.xxx.xxx.xxx/json/state
|
||||||
```
|
```
|
||||||
|
**MQTT**
|
||||||
|
You can publish a message with either `up` or `down` on topic `/swipe` to trigger animation.
|
||||||
|
You can also use `on` or `off` for enabling or disabling usermod.
|
||||||
|
|
||||||
Have fun with this usermod.<br/>
|
Have fun with this usermod.<br/>
|
||||||
www.rolfje.com
|
www.rolfje.com
|
||||||
|
|
||||||
|
Modifications @blazoncek
|
||||||
|
|
||||||
## Change log
|
## Change log
|
||||||
2021-04
|
2021-04
|
||||||
* Adaptation for runtime configuration.
|
* Adaptation for runtime configuration.
|
@ -9,9 +9,7 @@ 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 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**.
|
|
||||||
|
|
||||||
## Sensor connection
|
## Sensor connection
|
||||||
|
|
||||||
@ -63,7 +61,10 @@ 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.
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
|
When the PIR sensor state changes an MQTT message is broadcasted with topic `wled/deviceMAC/motion` and message `on` or `off`.
|
||||||
|
Usermod can also be configured to just send MQTT message and not change WLED state using settings page as well as responding to motion only during nighttime (assuming NTP and lattitude/longitude are set to determine sunrise/sunset times).
|
||||||
|
|
||||||
### There are two options to get access to the usermod instance:
|
### There are two options to get access to the usermod instance:
|
||||||
|
|
||||||
|
@ -48,11 +48,11 @@ public:
|
|||||||
/**
|
/**
|
||||||
* Enable/Disable the PIR sensor
|
* Enable/Disable the PIR sensor
|
||||||
*/
|
*/
|
||||||
void EnablePIRsensor(bool enable) { m_PIRenabled = enable; }
|
void EnablePIRsensor(bool en) { enabled = en; }
|
||||||
/**
|
/**
|
||||||
* Get PIR sensor enabled/disabled state
|
* Get PIR sensor enabled/disabled state
|
||||||
*/
|
*/
|
||||||
bool PIRsensorEnabled() { return m_PIRenabled; }
|
bool PIRsensorEnabled() { return enabled; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// PIR sensor pin
|
// PIR sensor pin
|
||||||
@ -64,39 +64,68 @@ private:
|
|||||||
// 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 sensorPinState = LOW;
|
||||||
// PIR sensor enabled - ISR attached
|
// PIR sensor enabled
|
||||||
bool m_PIRenabled = true;
|
bool enabled = true;
|
||||||
// status of initialisation
|
// status of initialisation
|
||||||
bool initDone = false;
|
bool initDone = false;
|
||||||
|
// on and off presets
|
||||||
|
uint8_t m_onPreset = 0;
|
||||||
|
uint8_t m_offPreset = 0;
|
||||||
|
// flag to indicate that PIR sensor should activate WLED during nighttime only
|
||||||
|
bool m_nightTimeOnly = false;
|
||||||
|
// flag to send MQTT message only (assuming it is enabled)
|
||||||
|
bool m_mqttOnly = false;
|
||||||
|
|
||||||
|
unsigned long lastLoop = 0;
|
||||||
|
|
||||||
// strings to reduce flash memory usage (used more than twice)
|
// strings to reduce flash memory usage (used more than twice)
|
||||||
static const char _name[];
|
static const char _name[];
|
||||||
static const char _switchOffDelay[];
|
static const char _switchOffDelay[];
|
||||||
static const char _enabled[];
|
static const char _enabled[];
|
||||||
|
static const char _onPreset[];
|
||||||
|
static const char _offPreset[];
|
||||||
|
static const char _nightTime[];
|
||||||
|
static const char _mqttOnly[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* return or change if new PIR sensor state is available
|
* check if it is daytime
|
||||||
|
* if sunrise/sunset is not defined (no NTP or lat/lon) default to nighttime
|
||||||
*/
|
*/
|
||||||
static volatile bool newPIRsensorState(bool changeState = false, bool newState = false);
|
bool isDayTime() {
|
||||||
|
bool isDayTime = false;
|
||||||
|
updateLocalTime();
|
||||||
|
uint8_t hr = hour(localTime);
|
||||||
|
uint8_t mi = minute(localTime);
|
||||||
|
|
||||||
/**
|
if (sunrise && sunset) {
|
||||||
* PIR sensor state has changed
|
if (hour(sunrise)<hr && hour(sunset)>hr) {
|
||||||
*/
|
isDayTime = true;
|
||||||
static void IRAM_ATTR ISR_PIRstateChange();
|
} else {
|
||||||
|
if (hour(sunrise)==hr && minute(sunrise)<mi) {
|
||||||
|
isDayTime = true;
|
||||||
|
}
|
||||||
|
if (hour(sunset)==hr && minute(sunset)>mi) {
|
||||||
|
isDayTime = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return isDayTime;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* switch strip on/off
|
* switch strip on/off
|
||||||
*/
|
*/
|
||||||
void switchStrip(bool switchOn)
|
void switchStrip(bool switchOn)
|
||||||
{
|
{
|
||||||
if (switchOn && bri == 0)
|
if (switchOn && m_onPreset) {
|
||||||
{
|
applyPreset(m_onPreset);
|
||||||
|
} else if (!switchOn && m_offPreset) {
|
||||||
|
applyPreset(m_offPreset);
|
||||||
|
} else if (switchOn && bri == 0) {
|
||||||
bri = briLast;
|
bri = briLast;
|
||||||
colorUpdated(NotifyUpdateMode);
|
colorUpdated(NotifyUpdateMode);
|
||||||
}
|
} else if (!switchOn && bri != 0) {
|
||||||
else if (!switchOn && bri != 0)
|
|
||||||
{
|
|
||||||
briLast = bri;
|
briLast = bri;
|
||||||
bri = 0;
|
bri = 0;
|
||||||
colorUpdated(NotifyUpdateMode);
|
colorUpdated(NotifyUpdateMode);
|
||||||
@ -110,7 +139,7 @@ private:
|
|||||||
char subuf[64];
|
char subuf[64];
|
||||||
strcpy(subuf, mqttDeviceTopic);
|
strcpy(subuf, mqttDeviceTopic);
|
||||||
strcat_P(subuf, PSTR("/motion"));
|
strcat_P(subuf, PSTR("/motion"));
|
||||||
mqtt->publish(subuf, 0, true, state);
|
mqtt->publish(subuf, 0, false, state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,22 +149,18 @@ private:
|
|||||||
*/
|
*/
|
||||||
bool updatePIRsensorState()
|
bool updatePIRsensorState()
|
||||||
{
|
{
|
||||||
if (newPIRsensorState())
|
bool pinState = digitalRead(PIRsensorPin);
|
||||||
{
|
if (pinState != sensorPinState) {
|
||||||
m_PIRsensorPinState = digitalRead(PIRsensorPin);
|
sensorPinState = pinState; // change previous state
|
||||||
|
|
||||||
if (m_PIRsensorPinState == HIGH)
|
if (sensorPinState == HIGH) {
|
||||||
{
|
|
||||||
m_offTimerStart = 0;
|
m_offTimerStart = 0;
|
||||||
switchStrip(true);
|
if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()))) switchStrip(true);
|
||||||
publishMqtt("on");
|
publishMqtt("on");
|
||||||
}
|
} else /*if (bri != 0)*/ {
|
||||||
else if (bri != 0)
|
|
||||||
{
|
|
||||||
// start switch off timer
|
// start switch off timer
|
||||||
m_offTimerStart = millis();
|
m_offTimerStart = millis();
|
||||||
}
|
}
|
||||||
newPIRsensorState(true, false);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -148,9 +173,9 @@ private:
|
|||||||
{
|
{
|
||||||
if (m_offTimerStart > 0 && millis() - m_offTimerStart > m_switchOffDelay)
|
if (m_offTimerStart > 0 && millis() - m_offTimerStart > m_switchOffDelay)
|
||||||
{
|
{
|
||||||
if (m_PIRenabled == true)
|
if (enabled == true)
|
||||||
{
|
{
|
||||||
switchStrip(false);
|
if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()))) switchStrip(false);
|
||||||
publishMqtt("off");
|
publishMqtt("off");
|
||||||
}
|
}
|
||||||
m_offTimerStart = 0;
|
m_offTimerStart = 0;
|
||||||
@ -171,14 +196,13 @@ public:
|
|||||||
// pin retrieved from cfg.json (readFromConfig()) prior to running setup()
|
// pin retrieved from cfg.json (readFromConfig()) prior to running setup()
|
||||||
if (!pinManager.allocatePin(PIRsensorPin,false)) {
|
if (!pinManager.allocatePin(PIRsensorPin,false)) {
|
||||||
PIRsensorPin = -1; // allocation failed
|
PIRsensorPin = -1; // allocation failed
|
||||||
m_PIRenabled = false;
|
enabled = false;
|
||||||
DEBUG_PRINTLN(F("PIRSensorSwitch pin allocation failed."));
|
DEBUG_PRINTLN(F("PIRSensorSwitch pin allocation failed."));
|
||||||
} else {
|
} else {
|
||||||
// PIR Sensor mode INPUT_PULLUP
|
// PIR Sensor mode INPUT_PULLUP
|
||||||
pinMode(PIRsensorPin, INPUT_PULLUP);
|
pinMode(PIRsensorPin, INPUT_PULLUP);
|
||||||
if (m_PIRenabled) {
|
if (enabled) {
|
||||||
// assign interrupt function and set CHANGE mode
|
sensorPinState = digitalRead(PIRsensorPin);
|
||||||
attachInterrupt(digitalPinToInterrupt(PIRsensorPin), ISR_PIRstateChange, CHANGE);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
initDone = true;
|
initDone = true;
|
||||||
@ -197,6 +221,11 @@ public:
|
|||||||
*/
|
*/
|
||||||
void loop()
|
void loop()
|
||||||
{
|
{
|
||||||
|
// only check sensors 10x/s
|
||||||
|
unsigned long now = millis();
|
||||||
|
if (now - lastLoop < 100) return;
|
||||||
|
lastLoop = now;
|
||||||
|
|
||||||
if (!updatePIRsensorState()) {
|
if (!updatePIRsensorState()) {
|
||||||
handleOffTimer();
|
handleOffTimer();
|
||||||
}
|
}
|
||||||
@ -210,38 +239,10 @@ public:
|
|||||||
void addToJsonInfo(JsonObject &root)
|
void addToJsonInfo(JsonObject &root)
|
||||||
{
|
{
|
||||||
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(F("<i class=\"icons\"></i> PIR sensor state")); //name
|
|
||||||
String uiDomString = F("<button class=\"btn infobtn\" onclick=\"requestJson({PIRenabled:");
|
|
||||||
String sensorStateInfo;
|
|
||||||
|
|
||||||
// PIR sensor state
|
if (enabled)
|
||||||
if (m_PIRenabled)
|
|
||||||
{
|
{
|
||||||
uiDomString += "false";
|
|
||||||
sensorStateInfo = (m_PIRsensorPinState != LOW ? FPSTR(F("active")) : FPSTR(F("inactive"))); //value
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
uiDomString += "true";
|
|
||||||
sensorStateInfo = F("Disabled!");
|
|
||||||
}
|
|
||||||
uiDomString += F("});return false;\">");
|
|
||||||
uiDomString += sensorStateInfo;
|
|
||||||
uiDomString += F("</button>");
|
|
||||||
infoArr.add(uiDomString); //value
|
|
||||||
*/
|
|
||||||
if (m_PIRenabled)
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
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 += F("\" onchange=\"requestJson({PIRoffSec:parseInt(this.value)*60});\">min");
|
|
||||||
infoArr.add(uiDomString);
|
|
||||||
*/
|
|
||||||
// off timer
|
// off timer
|
||||||
String uiDomString = F("PIR <i class=\"icons\"></i>");
|
String uiDomString = F("PIR <i class=\"icons\"></i>");
|
||||||
JsonArray infoArr = user.createNestedArray(uiDomString); // timer value
|
JsonArray infoArr = user.createNestedArray(uiDomString); // timer value
|
||||||
@ -271,66 +272,34 @@ public:
|
|||||||
uiDomString += (offSeconds);
|
uiDomString += (offSeconds);
|
||||||
infoArr.add(uiDomString + F("s"));
|
infoArr.add(uiDomString + F("s"));
|
||||||
} else {
|
} else {
|
||||||
infoArr.add(F("inactive"));
|
infoArr.add(sensorPinState ? F("sensor on") : F("inactive"));
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
String uiDomString = F("PIR sensor");
|
||||||
|
JsonArray infoArr = user.createNestedArray(uiDomString);
|
||||||
|
infoArr.add(F("disabled"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
* Add "PIRenabled" to json state. This can be used to disable/enable the sensor.
|
|
||||||
* Add "PIRoffSec" to json state. This can be used to adjust <m_switchOffDelay> milliseconds.
|
|
||||||
*/
|
*/
|
||||||
|
/*
|
||||||
void addToJsonState(JsonObject &root)
|
void addToJsonState(JsonObject &root)
|
||||||
{
|
{
|
||||||
root[FPSTR(_enabled)] = m_PIRenabled;
|
|
||||||
root[FPSTR(_switchOffDelay)] = (m_switchOffDelay / 1000);
|
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
* Read "PIRenabled" from json state and switch enable/disable the PIR sensor.
|
|
||||||
* Read "PIRoffSec" from json state and adjust <m_switchOffDelay> milliseconds.
|
|
||||||
*/
|
*/
|
||||||
|
/*
|
||||||
void readFromJsonState(JsonObject &root)
|
void readFromJsonState(JsonObject &root)
|
||||||
{
|
{
|
||||||
if (root[FPSTR(_switchOffDelay)] != nullptr) {
|
|
||||||
m_switchOffDelay = (1000 * max(60UL, min(43200UL, root[FPSTR(_switchOffDelay)].as<unsigned long>())));
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
// remove old ISR
|
|
||||||
detachInterrupt(PIRsensorPin);
|
|
||||||
// assign interrupt function and set CHANGE mode
|
|
||||||
attachInterrupt(digitalPinToInterrupt(pin), ISR_PIRstateChange, CHANGE);
|
|
||||||
newPIRsensorState(true, true);
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
m_PIRenabled = root[FPSTR(_enabled)];
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* provide the changeable values
|
* provide the changeable values
|
||||||
@ -338,9 +307,13 @@ public:
|
|||||||
void addToConfig(JsonObject &root)
|
void addToConfig(JsonObject &root)
|
||||||
{
|
{
|
||||||
JsonObject top = root.createNestedObject(FPSTR(_name));
|
JsonObject top = root.createNestedObject(FPSTR(_name));
|
||||||
top[FPSTR(_enabled)] = m_PIRenabled;
|
top[FPSTR(_enabled)] = enabled;
|
||||||
top[FPSTR(_switchOffDelay)] = m_switchOffDelay / 1000;
|
top[FPSTR(_switchOffDelay)] = m_switchOffDelay / 1000;
|
||||||
top["pin"] = PIRsensorPin;
|
top["pin"] = PIRsensorPin;
|
||||||
|
top[FPSTR(_onPreset)] = m_onPreset;
|
||||||
|
top[FPSTR(_offPreset)] = m_offPreset;
|
||||||
|
top[FPSTR(_nightTime)] = m_nightTimeOnly;
|
||||||
|
top[FPSTR(_mqttOnly)] = m_mqttOnly;
|
||||||
DEBUG_PRINTLN(F("PIR config saved."));
|
DEBUG_PRINTLN(F("PIR config saved."));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -350,7 +323,7 @@ public:
|
|||||||
*/
|
*/
|
||||||
void readFromConfig(JsonObject &root)
|
void readFromConfig(JsonObject &root)
|
||||||
{
|
{
|
||||||
bool oldEnabled = m_PIRenabled;
|
bool oldEnabled = enabled;
|
||||||
int8_t oldPin = PIRsensorPin;
|
int8_t oldPin = PIRsensorPin;
|
||||||
|
|
||||||
JsonObject top = root[FPSTR(_name)];
|
JsonObject top = root[FPSTR(_name)];
|
||||||
@ -362,11 +335,11 @@ public:
|
|||||||
|
|
||||||
if (top[FPSTR(_enabled)] != nullptr) {
|
if (top[FPSTR(_enabled)] != nullptr) {
|
||||||
if (top[FPSTR(_enabled)].is<bool>()) {
|
if (top[FPSTR(_enabled)].is<bool>()) {
|
||||||
m_PIRenabled = top[FPSTR(_enabled)].as<bool>(); // reading from cfg.json
|
enabled = top[FPSTR(_enabled)].as<bool>(); // reading from cfg.json
|
||||||
} else {
|
} else {
|
||||||
// change from settings page
|
// change from settings page
|
||||||
String str = top[FPSTR(_enabled)]; // checkbox -> off or on
|
String str = top[FPSTR(_enabled)]; // checkbox -> off or on
|
||||||
m_PIRenabled = (bool)(str!="off"); // off is guaranteed to be present
|
enabled = (bool)(str!="off"); // off is guaranteed to be present
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -374,15 +347,39 @@ public:
|
|||||||
m_switchOffDelay = (top[FPSTR(_switchOffDelay)].as<int>() * 1000);
|
m_switchOffDelay = (top[FPSTR(_switchOffDelay)].as<int>() * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (top[FPSTR(_onPreset)] != nullptr) {
|
||||||
|
m_onPreset = max(0,min(250,top[FPSTR(_onPreset)].as<int>()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (top[FPSTR(_offPreset)] != nullptr) {
|
||||||
|
m_offPreset = max(0,min(250,top[FPSTR(_offPreset)].as<int>()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (top[FPSTR(_nightTime)] != nullptr) {
|
||||||
|
if (top[FPSTR(_nightTime)].is<bool>()) {
|
||||||
|
m_nightTimeOnly = top[FPSTR(_nightTime)].as<bool>(); // reading from cfg.json
|
||||||
|
} else {
|
||||||
|
// change from settings page
|
||||||
|
String str = top[FPSTR(_nightTime)]; // checkbox -> off or on
|
||||||
|
m_nightTimeOnly = (bool)(str!="off"); // off is guaranteed to be present
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (top[FPSTR(_mqttOnly)] != nullptr) {
|
||||||
|
if (top[FPSTR(_mqttOnly)].is<bool>()) {
|
||||||
|
m_mqttOnly = top[FPSTR(_mqttOnly)].as<bool>(); // reading from cfg.json
|
||||||
|
} else {
|
||||||
|
// change from settings page
|
||||||
|
String str = top[FPSTR(_mqttOnly)]; // checkbox -> off or on
|
||||||
|
m_mqttOnly = (bool)(str!="off"); // off is guaranteed to be present
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!initDone) {
|
if (!initDone) {
|
||||||
// reading config prior to setup()
|
// reading config prior to setup()
|
||||||
DEBUG_PRINTLN(F("PIR config loaded."));
|
DEBUG_PRINTLN(F("PIR config loaded."));
|
||||||
} else {
|
} else {
|
||||||
if (oldPin != PIRsensorPin || oldEnabled != m_PIRenabled) {
|
if (oldPin != PIRsensorPin || oldEnabled != enabled) {
|
||||||
if (oldEnabled) {
|
|
||||||
// remove old ISR if disabling usermod
|
|
||||||
detachInterrupt(oldPin);
|
|
||||||
}
|
|
||||||
// check if pin is OK
|
// check if pin is OK
|
||||||
if (oldPin != PIRsensorPin && oldPin >= 0) {
|
if (oldPin != PIRsensorPin && oldPin >= 0) {
|
||||||
// if we are changing pin in settings page
|
// if we are changing pin in settings page
|
||||||
@ -393,12 +390,11 @@ public:
|
|||||||
} else {
|
} else {
|
||||||
// allocation failed
|
// allocation failed
|
||||||
PIRsensorPin = -1;
|
PIRsensorPin = -1;
|
||||||
m_PIRenabled = false;
|
enabled = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (m_PIRenabled) {
|
if (enabled) {
|
||||||
attachInterrupt(digitalPinToInterrupt(PIRsensorPin), ISR_PIRstateChange, CHANGE);
|
sensorPinState = digitalRead(PIRsensorPin);
|
||||||
newPIRsensorState(true, true);
|
|
||||||
}
|
}
|
||||||
DEBUG_PRINTLN(F("PIR config (re)loaded."));
|
DEBUG_PRINTLN(F("PIR config (re)loaded."));
|
||||||
}
|
}
|
||||||
@ -415,25 +411,11 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
//////////////////////////////////////////////////////
|
|
||||||
// PIRsensorSwitch static method implementations
|
|
||||||
|
|
||||||
volatile bool PIRsensorSwitch::newPIRsensorState(bool changeState, bool newState)
|
|
||||||
{
|
|
||||||
static volatile bool s_PIRsensorState = false;
|
|
||||||
if (changeState)
|
|
||||||
{
|
|
||||||
s_PIRsensorState = newState;
|
|
||||||
}
|
|
||||||
return s_PIRsensorState;
|
|
||||||
}
|
|
||||||
|
|
||||||
void IRAM_ATTR PIRsensorSwitch::ISR_PIRstateChange()
|
|
||||||
{
|
|
||||||
newPIRsensorState(true, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// strings to reduce flash memory usage (used more than twice)
|
// strings to reduce flash memory usage (used more than twice)
|
||||||
const char PIRsensorSwitch::_name[] PROGMEM = "PIRsensorSwitch";
|
const char PIRsensorSwitch::_name[] PROGMEM = "PIRsensorSwitch";
|
||||||
const char PIRsensorSwitch::_enabled[] PROGMEM = "PIRenabled";
|
const char PIRsensorSwitch::_enabled[] PROGMEM = "PIRenabled";
|
||||||
const char PIRsensorSwitch::_switchOffDelay[] PROGMEM = "PIRoffSec";
|
const char PIRsensorSwitch::_switchOffDelay[] PROGMEM = "PIRoffSec";
|
||||||
|
const char PIRsensorSwitch::_onPreset[] PROGMEM = "on-preset";
|
||||||
|
const char PIRsensorSwitch::_offPreset[] PROGMEM = "off-preset";
|
||||||
|
const char PIRsensorSwitch::_nightTime[] PROGMEM = "nighttime-only";
|
||||||
|
const char PIRsensorSwitch::_mqttOnly[] PROGMEM = "mqtt-only";
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
Based on the excellent `QuinLED_Dig_Uno_Temp_MQTT` by srg74 and 400killer!
|
Based on the excellent `QuinLED_Dig_Uno_Temp_MQTT` by srg74 and 400killer!
|
||||||
This usermod will read from an attached DS18B20 temperature sensor (as available on the QuinLED Dig-Uno)
|
This usermod will read from an attached DS18B20 temperature sensor (as available on the QuinLED Dig-Uno)
|
||||||
The temperature is displayed both in the Info section of the web UI as well as published to the `/temperature` MQTT topic if enabled.
|
The temperature is displayed both in the Info section of the web UI as well as published to the `/temperature` MQTT topic if enabled.
|
||||||
This usermod will be expanded with support for different sensor types in the future.
|
This usermod may be expanded with support for different sensor types in the future.
|
||||||
|
|
||||||
If temperature sensor is not detected during boot, this usermod will be disabled.
|
If temperature sensor is not detected during boot, this usermod will be disabled.
|
||||||
|
|
||||||
@ -16,18 +16,19 @@ Copy the example `platformio_override.ini` to the root directory. This file sho
|
|||||||
* `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_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.
|
All parameters can be configured at runtime using Usermods settings page, including pin, selection to display temerature in degrees Celsius or Farenheit mand measurement interval.
|
||||||
|
|
||||||
## 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
|
||||||
|
* [Srg74-WLED-Wemos-shield](https://github.com/srg74/WLED-wemos-shield) - another great DIY WLED board
|
||||||
|
|
||||||
### PlatformIO requirements
|
### PlatformIO requirements
|
||||||
|
|
||||||
If you are using `platformio_override.ini`, you should be able to refresh the task list and see your custom task, for example `env:d1_mini_usermod_dallas_temperature_C`.
|
If you are using `platformio_override.ini`, you should be able to refresh the task list and see your custom task, for example `env:d1_mini_usermod_dallas_temperature_C`.
|
||||||
|
|
||||||
|
|
||||||
If you are not using `platformio_override.ini`, you might have to uncomment `DallasTemperature@~3.8.0`,`OneWire@~2.3.5 under` `[common]` section in `platformio.ini`:
|
If you are not using `platformio_override.ini`, you might have to uncomment `OneWire@~2.3.5 under` `[common]` section in `platformio.ini`:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# platformio.ini
|
# platformio.ini
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "wled.h"
|
#include "wled.h"
|
||||||
|
|
||||||
//#include <DallasTemperature.h> //DS18B20
|
|
||||||
#include "OneWire.h"
|
#include "OneWire.h"
|
||||||
|
|
||||||
//Pin defaults for QuinLed Dig-Uno if not overriden
|
//Pin defaults for QuinLed Dig-Uno if not overriden
|
||||||
@ -164,9 +162,9 @@ class UsermodTemperature : public Usermod {
|
|||||||
// the DallasTemperature library returns -127C or -196.6F when problem
|
// the DallasTemperature library returns -127C or -196.6F when problem
|
||||||
// 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, false, String(temperature).c_str());
|
||||||
strcat_P(subuf, PSTR("_f"));
|
strcat_P(subuf, PSTR("_f"));
|
||||||
mqtt->publish(subuf, 0, true, String((float)temperature * 1.8f + 32).c_str());
|
mqtt->publish(subuf, 0, false, String((float)temperature * 1.8f + 32).c_str());
|
||||||
} else {
|
} else {
|
||||||
// publish something else to indicate status?
|
// publish something else to indicate status?
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,30 @@
|
|||||||
|
|
||||||
This usermod-v2 modification allows the connection of multiple relays each with individual delay and on/off mode.
|
This usermod-v2 modification allows the connection of multiple relays each with individual delay and on/off mode.
|
||||||
|
|
||||||
|
## HTTP API
|
||||||
|
All responses are returned as JSON.
|
||||||
|
|
||||||
|
Status Request: `http://[device-ip]/relays`
|
||||||
|
Switch Command: `http://[device-ip]/relays?switch=1,0,1,1`
|
||||||
|
The number of numbers behind the switch parameter must correspond to the number of relays. The number 1 switches the relay on. The number 0 switches the relay off.
|
||||||
|
|
||||||
|
Toggle Command: `http://[device-ip]/relays?toggle=1,0,1,1`
|
||||||
|
The number of numbers behind the parameter switch must correspond to the number of relays. The number 1 causes a toggling of the relay. The number 0 leaves the state of the device.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
1. 4 relays at all, relay 2 will be toggled: `http://[device-ip]/relays?toggle=0,1,0,0`
|
||||||
|
2. 3 relays at all, relay 1&3 will be switched on: `http://[device-ip]/relays?switch=1,0,1`
|
||||||
|
|
||||||
|
## MQTT API
|
||||||
|
|
||||||
|
wled/deviceMAC/relay/0/command on|off|toggle
|
||||||
|
wled/deviceMAC/relay/1/command on|off|toggle
|
||||||
|
|
||||||
|
When relay is switched it will publish a message:
|
||||||
|
|
||||||
|
wled/deviceMAC/relay/0 on|off
|
||||||
|
|
||||||
|
|
||||||
## Usermod installation
|
## 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`.
|
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`.
|
||||||
|
@ -56,7 +56,7 @@ class MultiRelay : public Usermod {
|
|||||||
if (WLED_MQTT_CONNECTED){
|
if (WLED_MQTT_CONNECTED){
|
||||||
char subuf[64];
|
char subuf[64];
|
||||||
sprintf_P(subuf, PSTR("%s/relay/%d"), mqttDeviceTopic, relay);
|
sprintf_P(subuf, PSTR("%s/relay/%d"), mqttDeviceTopic, relay);
|
||||||
mqtt->publish(subuf, 0, true, state);
|
mqtt->publish(subuf, 0, false, state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,7 +88,7 @@ class MultiRelay : public Usermod {
|
|||||||
DEBUG_PRINTLN("Relays: HTML API");
|
DEBUG_PRINTLN("Relays: HTML API");
|
||||||
String janswer;
|
String janswer;
|
||||||
String error = "";
|
String error = "";
|
||||||
int params = request->params();
|
//int params = request->params();
|
||||||
janswer = F("{\"NoOfRelays\":");
|
janswer = F("{\"NoOfRelays\":");
|
||||||
janswer += String(MULTI_RELAY_MAX_RELAYS) + ",";
|
janswer += String(MULTI_RELAY_MAX_RELAYS) + ",";
|
||||||
|
|
||||||
@ -281,7 +281,7 @@ class MultiRelay : public Usermod {
|
|||||||
* loop() is called continuously. Here you can check for events, read sensors, etc.
|
* loop() is called continuously. Here you can check for events, read sensors, etc.
|
||||||
*/
|
*/
|
||||||
void loop() {
|
void loop() {
|
||||||
if (!enabled) return;
|
if (!enabled || strip.isUpdating()) return;
|
||||||
|
|
||||||
static unsigned long lastUpdate = 0;
|
static unsigned long lastUpdate = 0;
|
||||||
if (millis() - lastUpdate < 200) return; // update only 5 times/s
|
if (millis() - lastUpdate < 200) return; // update only 5 times/s
|
||||||
|
@ -313,11 +313,7 @@ class FourLineDisplayUsermod : public Usermod {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update last known values.
|
// Update last known values.
|
||||||
#if defined(ESP8266)
|
|
||||||
knownSsid = apActive ? WiFi.softAPSSID() : WiFi.SSID();
|
knownSsid = apActive ? WiFi.softAPSSID() : WiFi.SSID();
|
||||||
#else
|
|
||||||
knownSsid = WiFi.SSID();
|
|
||||||
#endif
|
|
||||||
knownIp = apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP();
|
knownIp = apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP();
|
||||||
knownBrightness = bri;
|
knownBrightness = bri;
|
||||||
knownMode = strip.getMode();
|
knownMode = strip.getMode();
|
||||||
@ -409,9 +405,9 @@ class FourLineDisplayUsermod : public Usermod {
|
|||||||
if (!insideQuotes || (qComma != knownMode)) break;
|
if (!insideQuotes || (qComma != knownMode)) break;
|
||||||
lineBuffer[printedChars++] = singleJsonSymbol;
|
lineBuffer[printedChars++] = singleJsonSymbol;
|
||||||
}
|
}
|
||||||
if ((qComma > knownMode) || (printedChars > getCols()-2) || printedChars > sizeof(lineBuffer)-2) break;
|
if ((qComma > knownMode) || (printedChars >= getCols()-2) || printedChars >= sizeof(lineBuffer)-2) break;
|
||||||
}
|
}
|
||||||
for (;printedChars < getCols()-2 || printedChars > sizeof(lineBuffer)-2; printedChars++) lineBuffer[printedChars]=' ';
|
for (;printedChars < getCols()-2 && printedChars < sizeof(lineBuffer)-2; printedChars++) lineBuffer[printedChars]=' ';
|
||||||
lineBuffer[printedChars] = 0;
|
lineBuffer[printedChars] = 0;
|
||||||
drawString(2, row*lineHeight, lineBuffer);
|
drawString(2, row*lineHeight, lineBuffer);
|
||||||
}
|
}
|
||||||
|
@ -46,6 +46,7 @@
|
|||||||
#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"
|
#define USERMOD_ID_MULTI_RELAY 101 //Usermod "usermod_multi_relay.h"
|
||||||
|
#define USERMOD_ID_ANIMATED_STAIRCASE 102 //Usermod "Animated_Staircase.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
|
||||||
|
Loading…
Reference in New Issue
Block a user