2020-03-25 09:00:55 +01:00
|
|
|
#include "wled.h"
|
2020-03-25 10:14:23 +01:00
|
|
|
|
2016-12-31 00:38:51 +01:00
|
|
|
/*
|
|
|
|
* Physical IO
|
|
|
|
*/
|
|
|
|
|
2022-01-24 07:41:35 +01:00
|
|
|
#define WLED_DEBOUNCE_THRESHOLD 50 //only consider button input of at least 50ms as valid (debouncing)
|
|
|
|
#define WLED_LONG_PRESS 600 //long press if button is released after held for at least 600ms
|
|
|
|
#define WLED_DOUBLE_PRESS 350 //double press if another press within 350ms after a short press
|
|
|
|
#define WLED_LONG_REPEATED_ACTION 333 //how often a repeated action (e.g. dimming) is fired on long press on button IDs >0
|
|
|
|
#define WLED_LONG_AP 6000 //how long the button needs to be held to activate WLED-AP
|
2021-04-12 00:45:33 +02:00
|
|
|
|
2021-06-03 12:18:11 +02:00
|
|
|
static const char _mqtt_topic_button[] PROGMEM = "%s/button/%d"; // optimize flash usage
|
|
|
|
|
2021-05-20 19:54:07 +02:00
|
|
|
void shortPressAction(uint8_t b)
|
2019-03-13 11:13:03 +01:00
|
|
|
{
|
2021-10-31 11:57:03 +01:00
|
|
|
if (!macroButton[b]) {
|
|
|
|
switch (b) {
|
2022-02-20 22:24:11 +01:00
|
|
|
case 0: toggleOnOff(); stateUpdated(CALL_MODE_BUTTON); break;
|
2022-01-24 07:41:35 +01:00
|
|
|
case 1: ++effectCurrent %= strip.getModeCount(); effectChanged = true; colorUpdated(CALL_MODE_BUTTON); break;
|
2021-10-31 11:57:03 +01:00
|
|
|
}
|
2019-03-13 11:13:03 +01:00
|
|
|
} else {
|
2021-12-11 23:44:21 +01:00
|
|
|
applyPreset(macroButton[b], CALL_MODE_BUTTON_PRESET);
|
2019-03-13 11:13:03 +01:00
|
|
|
}
|
2021-06-03 12:18:11 +02:00
|
|
|
|
|
|
|
// publish MQTT message
|
2021-07-01 20:51:52 +02:00
|
|
|
if (buttonPublishMqtt && WLED_MQTT_CONNECTED) {
|
2021-06-03 12:18:11 +02:00
|
|
|
char subuf[64];
|
|
|
|
sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b);
|
|
|
|
mqtt->publish(subuf, 0, false, "short");
|
|
|
|
}
|
2019-03-13 11:13:03 +01:00
|
|
|
}
|
|
|
|
|
2021-10-31 11:57:03 +01:00
|
|
|
void longPressAction(uint8_t b)
|
|
|
|
{
|
|
|
|
if (!macroLongPress[b]) {
|
|
|
|
switch (b) {
|
2022-02-20 22:24:11 +01:00
|
|
|
case 0: setRandomColor(col); colorUpdated(CALL_MODE_BUTTON); break;
|
|
|
|
case 1: bri += 8; stateUpdated(CALL_MODE_BUTTON); buttonPressedTime[b] = millis(); break; // repeatable action
|
2021-10-31 11:57:03 +01:00
|
|
|
}
|
|
|
|
} else {
|
2021-12-11 23:44:21 +01:00
|
|
|
applyPreset(macroLongPress[b], CALL_MODE_BUTTON_PRESET);
|
2021-10-31 11:57:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// publish MQTT message
|
|
|
|
if (buttonPublishMqtt && WLED_MQTT_CONNECTED) {
|
|
|
|
char subuf[64];
|
|
|
|
sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b);
|
|
|
|
mqtt->publish(subuf, 0, false, "long");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void doublePressAction(uint8_t b)
|
|
|
|
{
|
|
|
|
if (!macroDoublePress[b]) {
|
|
|
|
switch (b) {
|
|
|
|
//case 0: toggleOnOff(); colorUpdated(CALL_MODE_BUTTON); break; //instant short press on button 0 if no macro set
|
2022-02-20 22:24:11 +01:00
|
|
|
case 1: ++effectPalette %= strip.getPaletteCount(); colorUpdated(CALL_MODE_BUTTON); break;
|
2021-10-31 11:57:03 +01:00
|
|
|
}
|
|
|
|
} else {
|
2021-12-11 23:44:21 +01:00
|
|
|
applyPreset(macroDoublePress[b], CALL_MODE_BUTTON_PRESET);
|
2021-10-31 11:57:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// publish MQTT message
|
|
|
|
if (buttonPublishMqtt && WLED_MQTT_CONNECTED) {
|
|
|
|
char subuf[64];
|
|
|
|
sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b);
|
|
|
|
mqtt->publish(subuf, 0, false, "double");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-20 19:54:07 +02:00
|
|
|
bool isButtonPressed(uint8_t i)
|
2020-09-20 16:12:46 +02:00
|
|
|
{
|
2021-05-20 19:54:07 +02:00
|
|
|
if (btnPin[i]<0) return false;
|
2022-02-09 19:59:17 +01:00
|
|
|
uint8_t pin = btnPin[i];
|
|
|
|
|
2021-05-20 19:54:07 +02:00
|
|
|
switch (buttonType[i]) {
|
|
|
|
case BTN_TYPE_NONE:
|
|
|
|
case BTN_TYPE_RESERVED:
|
|
|
|
break;
|
|
|
|
case BTN_TYPE_PUSH:
|
|
|
|
case BTN_TYPE_SWITCH:
|
2022-02-09 19:59:17 +01:00
|
|
|
if (digitalRead(pin) == LOW) return true;
|
2021-05-20 19:54:07 +02:00
|
|
|
break;
|
|
|
|
case BTN_TYPE_PUSH_ACT_HIGH:
|
2021-06-03 12:18:11 +02:00
|
|
|
case BTN_TYPE_PIR_SENSOR:
|
2022-02-09 19:59:17 +01:00
|
|
|
if (digitalRead(pin) == HIGH) return true;
|
2021-05-20 19:54:07 +02:00
|
|
|
break;
|
|
|
|
case BTN_TYPE_TOUCH:
|
2022-01-24 11:34:02 +01:00
|
|
|
#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3)
|
2022-02-09 19:59:17 +01:00
|
|
|
if (touchRead(pin) <= touchThreshold) return true;
|
2021-05-20 19:54:07 +02:00
|
|
|
#endif
|
|
|
|
break;
|
|
|
|
}
|
2020-09-20 16:12:46 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-05-20 19:54:07 +02:00
|
|
|
void handleSwitch(uint8_t b)
|
2021-04-12 00:45:33 +02:00
|
|
|
{
|
2021-06-03 12:18:11 +02:00
|
|
|
// isButtonPressed() handles inverted/noninverted logic
|
2021-05-20 19:54:07 +02:00
|
|
|
if (buttonPressedBefore[b] != isButtonPressed(b)) {
|
|
|
|
buttonPressedTime[b] = millis();
|
|
|
|
buttonPressedBefore[b] = !buttonPressedBefore[b];
|
2021-04-12 00:45:33 +02:00
|
|
|
}
|
|
|
|
|
2021-05-20 19:54:07 +02:00
|
|
|
if (buttonLongPressed[b] == buttonPressedBefore[b]) return;
|
2021-04-12 00:45:33 +02:00
|
|
|
|
2021-05-20 19:54:07 +02:00
|
|
|
if (millis() - buttonPressedTime[b] > WLED_DEBOUNCE_THRESHOLD) { //fire edge event only after 50ms without change (debounce)
|
2021-06-03 12:18:11 +02:00
|
|
|
if (!buttonPressedBefore[b]) { // on -> off
|
2021-12-11 23:44:21 +01:00
|
|
|
if (macroButton[b]) applyPreset(macroButton[b], CALL_MODE_BUTTON_PRESET);
|
2021-04-12 00:45:33 +02:00
|
|
|
else { //turn on
|
2022-02-20 22:24:11 +01:00
|
|
|
if (!bri) {toggleOnOff(); stateUpdated(CALL_MODE_BUTTON);}
|
2021-04-12 00:45:33 +02:00
|
|
|
}
|
2021-06-03 12:18:11 +02:00
|
|
|
} else { // off -> on
|
2021-12-11 23:44:21 +01:00
|
|
|
if (macroLongPress[b]) applyPreset(macroLongPress[b], CALL_MODE_BUTTON_PRESET);
|
2021-04-12 00:45:33 +02:00
|
|
|
else { //turn off
|
2022-02-20 22:24:11 +01:00
|
|
|
if (bri) {toggleOnOff(); stateUpdated(CALL_MODE_BUTTON);}
|
2021-04-12 00:45:33 +02:00
|
|
|
}
|
|
|
|
}
|
2021-06-03 12:18:11 +02:00
|
|
|
|
|
|
|
// publish MQTT message
|
2021-07-01 20:51:52 +02:00
|
|
|
if (buttonPublishMqtt && WLED_MQTT_CONNECTED) {
|
2021-06-03 12:18:11 +02:00
|
|
|
char subuf[64];
|
|
|
|
if (buttonType[b] == BTN_TYPE_PIR_SENSOR) sprintf_P(subuf, PSTR("%s/motion/%d"), mqttDeviceTopic, (int)b);
|
|
|
|
else sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b);
|
|
|
|
mqtt->publish(subuf, 0, false, !buttonPressedBefore[b] ? "off" : "on");
|
|
|
|
}
|
|
|
|
|
2021-05-20 19:54:07 +02:00
|
|
|
buttonLongPressed[b] = buttonPressedBefore[b]; //save the last "long term" switch state
|
2021-04-12 00:45:33 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-21 13:33:22 +02:00
|
|
|
void handleAnalog(uint8_t b)
|
|
|
|
{
|
|
|
|
static uint8_t oldRead[WLED_MAX_BUTTONS];
|
|
|
|
#ifdef ESP8266
|
2021-05-28 14:14:50 +02:00
|
|
|
uint16_t aRead = analogRead(A0) >> 2; // convert 10bit read to 8bit
|
2021-05-21 13:33:22 +02:00
|
|
|
#else
|
2021-05-28 14:14:50 +02:00
|
|
|
uint16_t aRead = analogRead(btnPin[b]) >> 4; // convert 12bit read to 8bit
|
2021-05-21 13:33:22 +02:00
|
|
|
#endif
|
2021-06-03 12:18:11 +02:00
|
|
|
|
|
|
|
if (buttonType[b] == BTN_TYPE_ANALOG_INVERTED) aRead = 255 - aRead;
|
|
|
|
|
2021-05-28 14:14:50 +02:00
|
|
|
// remove noise & reduce frequency of UI updates
|
|
|
|
aRead &= 0xFC;
|
2021-05-21 13:33:22 +02:00
|
|
|
|
|
|
|
if (oldRead[b] == aRead) return; // no change in reading
|
2021-05-25 23:59:43 +02:00
|
|
|
oldRead[b] = aRead;
|
2021-05-21 13:33:22 +02:00
|
|
|
|
|
|
|
// if no macro for "short press" and "long press" is defined use brightness control
|
|
|
|
if (!macroButton[b] && !macroLongPress[b]) {
|
2021-05-28 08:47:15 +02:00
|
|
|
// if "double press" macro defines which option to change
|
2021-05-25 23:59:43 +02:00
|
|
|
if (macroDoublePress[b] >= 250) {
|
2021-05-28 08:47:15 +02:00
|
|
|
// global brightness
|
2021-05-25 23:59:43 +02:00
|
|
|
if (aRead == 0) {
|
|
|
|
briLast = bri;
|
|
|
|
bri = 0;
|
|
|
|
} else{
|
2021-05-28 14:14:50 +02:00
|
|
|
bri = aRead;
|
2021-05-28 08:47:15 +02:00
|
|
|
}
|
|
|
|
} else if (macroDoublePress[b] == 249) {
|
|
|
|
// effect speed
|
2021-05-28 14:14:50 +02:00
|
|
|
effectSpeed = aRead;
|
2021-05-28 08:47:15 +02:00
|
|
|
} else if (macroDoublePress[b] == 248) {
|
|
|
|
// effect intensity
|
2021-05-28 14:14:50 +02:00
|
|
|
effectIntensity = aRead;
|
2021-05-28 08:47:15 +02:00
|
|
|
} else if (macroDoublePress[b] == 247) {
|
|
|
|
// selected palette
|
2021-05-28 14:14:50 +02:00
|
|
|
effectPalette = map(aRead, 0, 252, 0, strip.getPaletteCount()-1);
|
2021-05-30 02:03:32 +02:00
|
|
|
} else if (macroDoublePress[b] == 200) {
|
|
|
|
// primary color, hue, full saturation
|
|
|
|
colorHStoRGB(aRead*256,255,col);
|
2021-05-21 13:33:22 +02:00
|
|
|
} else {
|
|
|
|
// otherwise use "double press" for segment selection
|
|
|
|
WS2812FX::Segment& seg = strip.getSegment(macroDoublePress[b]);
|
|
|
|
if (aRead == 0) {
|
2021-05-25 23:59:43 +02:00
|
|
|
seg.setOption(SEG_OPTION_ON, 0); // off
|
2021-05-21 13:33:22 +02:00
|
|
|
} else {
|
2021-05-28 14:14:50 +02:00
|
|
|
seg.setOpacity(aRead, macroDoublePress[b]);
|
2021-05-25 23:59:43 +02:00
|
|
|
seg.setOption(SEG_OPTION_ON, 1);
|
2021-05-21 13:33:22 +02:00
|
|
|
}
|
2021-05-25 23:59:43 +02:00
|
|
|
// this will notify clients of update (websockets,mqtt,etc)
|
2021-07-09 18:54:28 +02:00
|
|
|
updateInterfaces(CALL_MODE_BUTTON);
|
2021-05-21 13:33:22 +02:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
//TODO:
|
|
|
|
// we can either trigger a preset depending on the level (between short and long entries)
|
|
|
|
// or use it for RGBW direct control
|
|
|
|
}
|
2021-07-09 18:54:28 +02:00
|
|
|
colorUpdated(CALL_MODE_BUTTON);
|
2021-05-21 13:33:22 +02:00
|
|
|
}
|
|
|
|
|
2016-11-19 19:39:17 +01:00
|
|
|
void handleButton()
|
|
|
|
{
|
2021-05-25 23:59:43 +02:00
|
|
|
static unsigned long lastRead = 0UL;
|
2022-01-09 15:13:33 +01:00
|
|
|
bool analog = false;
|
|
|
|
unsigned long now = millis();
|
2021-05-25 23:59:43 +02:00
|
|
|
|
2021-05-20 19:54:07 +02:00
|
|
|
for (uint8_t b=0; b<WLED_MAX_BUTTONS; b++) {
|
2021-05-28 17:45:14 +02:00
|
|
|
#ifdef ESP8266
|
2021-06-03 12:18:11 +02:00
|
|
|
if ((btnPin[b]<0 && !(buttonType[b] == BTN_TYPE_ANALOG || buttonType[b] == BTN_TYPE_ANALOG_INVERTED)) || buttonType[b] == BTN_TYPE_NONE) continue;
|
2021-05-28 17:45:14 +02:00
|
|
|
#else
|
2021-05-25 23:59:43 +02:00
|
|
|
if (btnPin[b]<0 || buttonType[b] == BTN_TYPE_NONE) continue;
|
2021-05-28 17:45:14 +02:00
|
|
|
#endif
|
2021-04-12 00:45:33 +02:00
|
|
|
|
2021-10-31 11:57:03 +01:00
|
|
|
if (usermods.handleButton(b)) continue; // did usermod handle buttons
|
|
|
|
|
2022-01-09 15:13:33 +01:00
|
|
|
if ((buttonType[b] == BTN_TYPE_ANALOG || buttonType[b] == BTN_TYPE_ANALOG_INVERTED) && now - lastRead > 250) { // button is not a button but a potentiometer
|
|
|
|
analog = true;
|
2021-05-21 13:33:22 +02:00
|
|
|
handleAnalog(b); continue;
|
|
|
|
}
|
2021-04-12 00:45:33 +02:00
|
|
|
|
2021-06-03 12:18:11 +02:00
|
|
|
//button is not momentary, but switch. This is only suitable on pins whose on-boot state does not matter (NOT gpio0)
|
|
|
|
if (buttonType[b] == BTN_TYPE_SWITCH || buttonType[b] == BTN_TYPE_PIR_SENSOR) {
|
2021-05-20 19:54:07 +02:00
|
|
|
handleSwitch(b); continue;
|
|
|
|
}
|
2019-10-18 12:19:52 +02:00
|
|
|
|
2021-05-20 19:54:07 +02:00
|
|
|
//momentary button logic
|
2021-10-31 11:57:03 +01:00
|
|
|
if (isButtonPressed(b)) { //pressed
|
|
|
|
|
2022-01-09 15:13:33 +01:00
|
|
|
if (!buttonPressedBefore[b]) buttonPressedTime[b] = now;
|
2021-05-20 19:54:07 +02:00
|
|
|
buttonPressedBefore[b] = true;
|
|
|
|
|
2022-01-09 15:13:33 +01:00
|
|
|
if (now - buttonPressedTime[b] > WLED_LONG_PRESS) { //long press
|
2021-10-31 11:57:03 +01:00
|
|
|
if (!buttonLongPressed[b]) longPressAction(b);
|
|
|
|
else if (b) { //repeatable action (~3 times per s) on button > 0
|
|
|
|
longPressAction(b);
|
2022-01-24 07:41:35 +01:00
|
|
|
buttonPressedTime[b] = now - WLED_LONG_REPEATED_ACTION; //333ms
|
2021-05-20 19:54:07 +02:00
|
|
|
}
|
2021-10-31 11:57:03 +01:00
|
|
|
buttonLongPressed[b] = true;
|
2019-10-18 12:19:52 +02:00
|
|
|
}
|
2021-10-31 11:57:03 +01:00
|
|
|
|
|
|
|
} else if (!isButtonPressed(b) && buttonPressedBefore[b]) { //released
|
|
|
|
|
2022-01-09 15:13:33 +01:00
|
|
|
long dur = now - buttonPressedTime[b];
|
2021-05-20 19:54:07 +02:00
|
|
|
if (dur < WLED_DEBOUNCE_THRESHOLD) {buttonPressedBefore[b] = false; continue;} //too short "press", debounce
|
2021-10-31 11:57:03 +01:00
|
|
|
bool doublePress = buttonWaitTime[b]; //did we have a short press before?
|
2021-05-20 19:54:07 +02:00
|
|
|
buttonWaitTime[b] = 0;
|
|
|
|
|
2022-01-24 14:20:21 +01:00
|
|
|
if (b == 0 && dur > 2*WLED_LONG_AP) { //very long press on button 0 (when released)
|
|
|
|
WLED_FS.format();
|
|
|
|
clearEEPROM();
|
|
|
|
doReboot = true;
|
|
|
|
} else if (b == 0 && dur > WLED_LONG_AP) { //long press on button 0 (when released)
|
2021-05-20 19:54:07 +02:00
|
|
|
WLED::instance().initAP(true);
|
2021-10-31 11:57:03 +01:00
|
|
|
} else if (!buttonLongPressed[b]) { //short press
|
2022-02-20 22:24:11 +01:00
|
|
|
//NOTE: this interferes with double click handling in usermods so usermod needs to implement full button handling
|
|
|
|
if (b != 1 && !macroDoublePress[b]) { //don't wait for double press on buttons without a default action if no double press macro set
|
2022-01-21 21:24:49 +01:00
|
|
|
shortPressAction(b);
|
|
|
|
} else { //double press if less than 350 ms between current press and previous short press release (buttonWaitTime!=0)
|
2021-06-03 12:18:11 +02:00
|
|
|
if (doublePress) {
|
2021-10-31 11:57:03 +01:00
|
|
|
doublePressAction(b);
|
|
|
|
} else {
|
2022-01-09 15:13:33 +01:00
|
|
|
buttonWaitTime[b] = now;
|
2021-10-31 11:57:03 +01:00
|
|
|
}
|
2022-01-21 21:24:49 +01:00
|
|
|
}
|
2021-05-20 19:54:07 +02:00
|
|
|
}
|
|
|
|
buttonPressedBefore[b] = false;
|
|
|
|
buttonLongPressed[b] = false;
|
2016-11-19 19:39:17 +01:00
|
|
|
}
|
2019-03-13 11:13:03 +01:00
|
|
|
|
2021-10-31 11:57:03 +01:00
|
|
|
//if 350ms elapsed since last short press release it is a short press
|
2022-01-09 15:13:33 +01:00
|
|
|
if (buttonWaitTime[b] && now - buttonWaitTime[b] > WLED_DOUBLE_PRESS && !buttonPressedBefore[b]) {
|
2021-05-20 19:54:07 +02:00
|
|
|
buttonWaitTime[b] = 0;
|
|
|
|
shortPressAction(b);
|
|
|
|
}
|
2019-03-13 11:13:03 +01:00
|
|
|
}
|
2022-01-09 15:13:33 +01:00
|
|
|
if (analog) lastRead = now;
|
2019-03-13 11:13:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void handleIO()
|
|
|
|
{
|
|
|
|
handleButton();
|
|
|
|
|
|
|
|
//set relay when LEDs turn on
|
|
|
|
if (strip.getBrightness())
|
|
|
|
{
|
|
|
|
lastOnTime = millis();
|
|
|
|
if (offMode)
|
2020-10-12 20:13:13 +02:00
|
|
|
{
|
2021-01-17 00:20:31 +01:00
|
|
|
if (rlyPin>=0) {
|
|
|
|
pinMode(rlyPin, OUTPUT);
|
|
|
|
digitalWrite(rlyPin, rlyMde);
|
|
|
|
}
|
2019-03-13 11:13:03 +01:00
|
|
|
offMode = false;
|
|
|
|
}
|
|
|
|
} else if (millis() - lastOnTime > 600)
|
|
|
|
{
|
2021-01-17 00:20:31 +01:00
|
|
|
if (!offMode) {
|
|
|
|
#ifdef ESP8266
|
2021-05-20 19:54:07 +02:00
|
|
|
// turn off built-in LED if strip is turned off
|
|
|
|
// this will break digital bus so will need to be reinitialised on On
|
2021-11-17 11:13:07 +01:00
|
|
|
PinOwner ledPinOwner = pinManager.getPinOwner(LED_BUILTIN);
|
2022-02-04 13:28:00 +01:00
|
|
|
if (!strip.isOffRefreshRequired() && (ledPinOwner == PinOwner::None || ledPinOwner == PinOwner::BusDigital)) {
|
2021-11-17 11:13:07 +01:00
|
|
|
pinMode(LED_BUILTIN, OUTPUT);
|
|
|
|
digitalWrite(LED_BUILTIN, HIGH);
|
|
|
|
}
|
2020-10-12 20:13:13 +02:00
|
|
|
#endif
|
2021-01-17 00:20:31 +01:00
|
|
|
if (rlyPin>=0) {
|
|
|
|
pinMode(rlyPin, OUTPUT);
|
|
|
|
digitalWrite(rlyPin, !rlyMde);
|
|
|
|
}
|
|
|
|
}
|
2019-03-13 11:13:03 +01:00
|
|
|
offMode = true;
|
2016-11-19 19:39:17 +01:00
|
|
|
}
|
2022-02-09 19:59:17 +01:00
|
|
|
}
|