mirror of
https://github.com/wled/WLED.git
synced 2025-07-12 13:26:33 +00:00
Enhanced Animated Staircase usermod.
This commit is contained in:
parent
f6a5bc9b40
commit
3fde7365f9
@ -10,29 +10,27 @@
|
||||
#pragma once
|
||||
#include "wled.h"
|
||||
|
||||
#define USERMOD_ID_ANIMATED_STAIRCASE 1011
|
||||
|
||||
class Animated_Staircase : public Usermod {
|
||||
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
|
||||
unsigned long on_time_ms = 30000; // 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
|
||||
unsigned int topMaxDist = 50; // default maximum measured distance in cm, top
|
||||
unsigned int bottomMaxDist = 50; // default maximum measured distance in cm, bottom
|
||||
|
||||
/* runtime variables */
|
||||
bool initDone = false;
|
||||
|
||||
// Time between checking of the sensors
|
||||
const unsigned int scanDelay = 50;
|
||||
const unsigned int scanDelay = 100;
|
||||
|
||||
// Lights on or off.
|
||||
// Flipping this will start a transition.
|
||||
@ -76,6 +74,8 @@ class Animated_Staircase : public Usermod {
|
||||
bool topSensorWrite = false;
|
||||
bool bottomSensorRead = false;
|
||||
bool bottomSensorWrite = false;
|
||||
bool topSensorState = false;
|
||||
bool bottomSensorState = false;
|
||||
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
static const char _name[];
|
||||
@ -88,13 +88,21 @@ class Animated_Staircase : public Usermod {
|
||||
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 _[];
|
||||
static const char _topEchoCm[];
|
||||
static const char _bottomEchoCm[];
|
||||
|
||||
void publishMqtt(bool bottom, const char* state)
|
||||
{
|
||||
//Check if MQTT Connected, otherwise it will crash the 8266
|
||||
if (WLED_MQTT_CONNECTED){
|
||||
char subuf[64];
|
||||
sprintf_P(subuf, PSTR("%s/motion/%d"), mqttDeviceTopic, (int)bottom);
|
||||
mqtt->publish(subuf, 0, false, state);
|
||||
}
|
||||
}
|
||||
|
||||
void updateSegments() {
|
||||
// mainSegmentId = strip.getMainSegmentId();
|
||||
// WS2812FX::Segment mainsegment = strip.getSegment(mainSegmentId);
|
||||
mainSegmentId = strip.getMainSegmentId();
|
||||
WS2812FX::Segment* segments = strip.getSegments();
|
||||
for (int i = 0; i < MAX_NUM_SEGMENTS; i++, segments++) {
|
||||
if (!segments->isActive()) {
|
||||
@ -140,28 +148,46 @@ class Animated_Staircase : public Usermod {
|
||||
* 50 cm = 2915 uS
|
||||
* 100 cm = 5831 uS
|
||||
*/
|
||||
bool ultrasoundRead(uint8_t signalPin,
|
||||
uint8_t echoPin,
|
||||
unsigned int maxTimeUs) {
|
||||
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;
|
||||
}
|
||||
|
||||
void checkSensors() {
|
||||
bool checkSensors() {
|
||||
bool sensorChanged = false;
|
||||
|
||||
if ((millis() - lastScanTime) > scanDelay) {
|
||||
lastScanTime = millis();
|
||||
|
||||
if (!useUSSensorBottom)
|
||||
bottomSensorRead = bottomSensorWrite || (digitalRead(bottomPIRorTriggerPin) == HIGH);
|
||||
else
|
||||
bottomSensorRead = bottomSensorWrite || ultrasoundRead(bottomPIRorTriggerPin, bottomEchoPin, bottomMaxTimeUs);
|
||||
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 (!useUSSensorTop)
|
||||
topSensorRead = topSensorWrite || (digitalRead(topPIRorTriggerPin) == HIGH);
|
||||
else
|
||||
topSensorRead = topSensorWrite || ultrasoundRead(topPIRorTriggerPin, topEchoPin, topMaxTimeUs);
|
||||
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;
|
||||
@ -176,11 +202,8 @@ class Animated_Staircase : public Usermod {
|
||||
// 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."));
|
||||
}
|
||||
DEBUG_PRINT(F("ON -> Swipe "));
|
||||
DEBUG_PRINTLN(swipe ? F("up.") : F("down."));
|
||||
|
||||
if (onIndex == offIndex) {
|
||||
// Position the indices for a correct on-swipe
|
||||
@ -195,20 +218,20 @@ class Animated_Staircase : public Usermod {
|
||||
}
|
||||
}
|
||||
}
|
||||
return sensorChanged;
|
||||
}
|
||||
|
||||
void autoPowerOff() {
|
||||
// TODO: add logic to wait until PIR sensor deactivates
|
||||
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;
|
||||
|
||||
if (swipe) {
|
||||
DEBUG_PRINTLN(F("OFF -> Swipe up."));
|
||||
} else {
|
||||
DEBUG_PRINTLN(F("OFF -> Swipe down."));
|
||||
}
|
||||
DEBUG_PRINT(F("OFF -> Swipe "));
|
||||
DEBUG_PRINTLN(swipe ? F("up.") : F("down."));
|
||||
}
|
||||
}
|
||||
|
||||
@ -216,9 +239,6 @@ class Animated_Staircase : public Usermod {
|
||||
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);
|
||||
@ -230,7 +250,6 @@ class Animated_Staircase : public Usermod {
|
||||
offIndex = MAX(onIndex, offIndex - 1);
|
||||
}
|
||||
}
|
||||
|
||||
updateSegments();
|
||||
}
|
||||
}
|
||||
@ -243,8 +262,8 @@ class Animated_Staircase : public Usermod {
|
||||
|
||||
// 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>());
|
||||
bottomSensorWrite = bottomSensorState || (staircase[F("bottom-sensor")].as<bool>());
|
||||
topSensorWrite = topSensorState || (staircase[F("top-sensor")].as<bool>());
|
||||
}
|
||||
|
||||
void enable(bool enable) {
|
||||
@ -256,23 +275,21 @@ class Animated_Staircase : public Usermod {
|
||||
DEBUG_PRINT(on_time_ms / 1000);
|
||||
DEBUG_PRINTLN(F(" seconds."));
|
||||
|
||||
// TODO: attach interrupts
|
||||
if (!useUSSensorBottom)
|
||||
pinMode(bottomPIRorTriggerPin, INPUT);
|
||||
pinMode(bottomPIRorTriggerPin, INPUT_PULLUP);
|
||||
else {
|
||||
pinMode(bottomPIRorTriggerPin, OUTPUT);
|
||||
pinMode(bottomEchoPin, INPUT);
|
||||
}
|
||||
|
||||
if (!useUSSensorTop)
|
||||
pinMode(topPIRorTriggerPin, INPUT);
|
||||
pinMode(topPIRorTriggerPin, INPUT_PULLUP);
|
||||
else {
|
||||
pinMode(topPIRorTriggerPin, OUTPUT);
|
||||
pinMode(topEchoPin, INPUT);
|
||||
}
|
||||
} else {
|
||||
// Restore segment options
|
||||
// WS2812FX::Segment mainsegment = strip.getSegment(mainSegmentId);
|
||||
WS2812FX::Segment* segments = strip.getSegments();
|
||||
for (int i = 0; i < MAX_NUM_SEGMENTS; i++, segments++) {
|
||||
if (!segments->isActive()) {
|
||||
@ -303,22 +320,15 @@ class Animated_Staircase : public Usermod {
|
||||
bottomPIRorTriggerPin = -1;
|
||||
}
|
||||
if (bottomEchoPin >= 0) {
|
||||
if (!pinManager.allocatePin(bottomPIRorTriggerPin,false))
|
||||
if (!pinManager.allocatePin(bottomEchoPin,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() {
|
||||
if (!enabled) return;
|
||||
if (!enabled || strip.isUpdating()) return;
|
||||
checkSensors();
|
||||
autoPowerOff();
|
||||
updateSwipe();
|
||||
@ -326,6 +336,38 @@ class Animated_Staircase : public Usermod {
|
||||
|
||||
uint16_t getId() { return USERMOD_ID_ANIMATED_STAIRCASE; }
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
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()) {
|
||||
@ -349,7 +391,7 @@ class Animated_Staircase : public Usermod {
|
||||
String str = staircase[FPSTR(_enabled)]; // checkbox -> off or on
|
||||
enabled = (bool)(str!="off"); // off is guaranteed to be present
|
||||
}
|
||||
readSensorsFromJson(root);
|
||||
readSensorsFromJson(staircase);
|
||||
DEBUG_PRINTLN(F("Staircase sensor state read from API."));
|
||||
}
|
||||
}
|
||||
@ -371,8 +413,8 @@ class Animated_Staircase : public Usermod {
|
||||
staircase[FPSTR(_useBottomUltrasoundSensor)] = useUSSensorBottom;
|
||||
staircase[FPSTR(_bottomPIRorTrigger_pin)] = bottomPIRorTriggerPin;
|
||||
staircase[FPSTR(_bottomEcho_pin)] = useUSSensorBottom ? bottomEchoPin : -1;
|
||||
staircase[FPSTR(_topEchoTime)] = topMaxTimeUs;
|
||||
staircase[FPSTR(_bottomEchoTime)] = bottomMaxTimeUs;
|
||||
staircase[FPSTR(_topEchoCm)] = topMaxDist;
|
||||
staircase[FPSTR(_bottomEchoCm)] = bottomMaxDist;
|
||||
DEBUG_PRINTLN(F("Staircase config saved."));
|
||||
}
|
||||
|
||||
@ -416,16 +458,17 @@ class Animated_Staircase : public Usermod {
|
||||
}
|
||||
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."));
|
||||
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 {
|
||||
DEBUG_PRINTLN(F("No config found. (Using defaults.)"));
|
||||
}
|
||||
if (!initDone) {
|
||||
// first run: reading from cfg.json
|
||||
DEBUG_PRINTLN(F("Staircase config loaded."));
|
||||
} else {
|
||||
// changing paramters from settings page
|
||||
DEBUG_PRINTLN(F("Staircase config (re)loaded."));
|
||||
bool changed = false;
|
||||
if ((oldUseUSSensorTop != useUSSensorTop) ||
|
||||
(oldUseUSSensorBottom != useUSSensorBottom) ||
|
||||
@ -453,10 +496,10 @@ class Animated_Staircase : public Usermod {
|
||||
staircase = root.createNestedObject("u");
|
||||
}
|
||||
|
||||
if (enabled) {
|
||||
JsonArray usermodEnabled = staircase.createNestedArray(F("Staircase enabled")); // name
|
||||
if (enabled) {
|
||||
usermodEnabled.add("yes"); // value
|
||||
|
||||
/*
|
||||
JsonArray segmentDelay = staircase.createNestedArray(F("Delay between stairs")); // name
|
||||
segmentDelay.add(segment_delay_ms); // value
|
||||
segmentDelay.add("ms"); // unit
|
||||
@ -464,8 +507,8 @@ class Animated_Staircase : public Usermod {
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -482,5 +525,5 @@ const char Animated_Staircase::_topEcho_pin[] PROGMEM = "topEcho_p
|
||||
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";
|
||||
const char Animated_Staircase::_topEchoCm[] PROGMEM = "top-dist-cm";
|
||||
const char Animated_Staircase::_bottomEchoCm[] PROGMEM = "bottom-dist-cm";
|
||||
|
@ -45,7 +45,8 @@
|
||||
#define USERMOD_ID_DHT 10 //Usermod "usermod_dht.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_MULTI_RELAY 101 //Usermod "usermod_multi_relay.h"
|
||||
#define USERMOD_ID_MULTI_RELAY 13 //Usermod "usermod_multi_relay.h"
|
||||
#define USERMOD_ID_ANIMATED_STAIRCASE 14 //Usermod "Animated_Staircase.h"
|
||||
|
||||
//Access point behavior
|
||||
#define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot
|
||||
|
@ -8,7 +8,7 @@
|
||||
*/
|
||||
|
||||
// version code in format yymmddb (b = daily build)
|
||||
#define VERSION 2105131
|
||||
#define VERSION 2105151
|
||||
|
||||
//uncomment this if you have a "my_config.h" file you'd like to use
|
||||
//#define WLED_USE_MY_CONFIG
|
||||
|
Loading…
x
Reference in New Issue
Block a user