Browse Source

LED Updates (#2170)

* support GPIO setting in WebUI
* experimental led pattern support
* support loading settings in setup() (not yet fully working)
mcspr-patch-1
Max Prokhorov 4 years ago
committed by GitHub
parent
commit
80825590a5
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 20167 additions and 19664 deletions
  1. +2
    -2
      code/espurna/config/types.h
  2. BIN
      code/espurna/data/index.all.html.gz
  3. BIN
      code/espurna/data/index.light.html.gz
  4. BIN
      code/espurna/data/index.lightfox.html.gz
  5. BIN
      code/espurna/data/index.rfbridge.html.gz
  6. BIN
      code/espurna/data/index.rfm69.html.gz
  7. BIN
      code/espurna/data/index.sensor.html.gz
  8. BIN
      code/espurna/data/index.small.html.gz
  9. BIN
      code/espurna/data/index.thermostat.html.gz
  10. +4
    -2
      code/espurna/homeassistant.ino
  11. +54
    -18
      code/espurna/led.h
  12. +251
    -116
      code/espurna/led.ino
  13. +166
    -0
      code/espurna/led_pattern.h
  14. +71
    -0
      code/espurna/led_pattern.h.in
  15. +3
    -9
      code/espurna/relay.h
  16. +59
    -76
      code/espurna/relay.ino
  17. +13
    -0
      code/espurna/rpc.h
  18. +41
    -0
      code/espurna/rpc.ino
  19. +2534
    -2528
      code/espurna/static/index.all.html.gz.h
  20. +2331
    -2325
      code/espurna/static/index.light.html.gz.h
  21. +2434
    -2429
      code/espurna/static/index.lightfox.html.gz.h
  22. +2457
    -2452
      code/espurna/static/index.rfbridge.html.gz.h
  23. +3316
    -3311
      code/espurna/static/index.rfm69.html.gz.h
  24. +2023
    -2017
      code/espurna/static/index.sensor.html.gz.h
  25. +2378
    -2374
      code/espurna/static/index.small.html.gz.h
  26. +1953
    -1948
      code/espurna/static/index.thermostat.html.gz.h
  27. +31
    -26
      code/html/custom.js
  28. +46
    -31
      code/html/index.html

+ 2
- 2
code/espurna/config/types.h View File

@ -169,8 +169,8 @@
#define LED_MODE_MANUAL 0 // LED will be managed manually (OFF by default) #define LED_MODE_MANUAL 0 // LED will be managed manually (OFF by default)
#define LED_MODE_WIFI 1 // LED will blink according to the WIFI status #define LED_MODE_WIFI 1 // LED will blink according to the WIFI status
#define LED_MODE_FOLLOW 2 // LED will follow state of linked relay (check RELAY#_LED)
#define LED_MODE_FOLLOW_INVERSE 3 // LED will follow the opposite state of linked relay (check RELAY#_LED)
#define LED_MODE_FOLLOW 2 // LED will follow state of linked LED#_RELAY relay ID
#define LED_MODE_FOLLOW_INVERSE 3 // LED will follow the opposite state of linked LED#_RELAY relay ID
#define LED_MODE_FINDME 4 // LED will be ON if all relays are OFF #define LED_MODE_FINDME 4 // LED will be ON if all relays are OFF
#define LED_MODE_FINDME_WIFI 5 // A mixture between WIFI and FINDME #define LED_MODE_FINDME_WIFI 5 // A mixture between WIFI and FINDME
#define LED_MODE_ON 6 // LED always ON #define LED_MODE_ON 6 // LED always ON


BIN
code/espurna/data/index.all.html.gz View File


BIN
code/espurna/data/index.light.html.gz View File


BIN
code/espurna/data/index.lightfox.html.gz View File


BIN
code/espurna/data/index.rfbridge.html.gz View File


BIN
code/espurna/data/index.rfm69.html.gz View File


BIN
code/espurna/data/index.sensor.html.gz View File


BIN
code/espurna/data/index.small.html.gz View File


BIN
code/espurna/data/index.thermostat.html.gz View File


+ 4
- 2
code/espurna/homeassistant.ino View File

@ -13,6 +13,8 @@ Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
#include "homeassistant.h" #include "homeassistant.h"
#include "mqtt.h" #include "mqtt.h"
#include "relay.h"
#include "rpc.h"
#include "ws.h" #include "ws.h"
bool _ha_enabled = false; bool _ha_enabled = false;
@ -253,8 +255,8 @@ void _haSendSwitch(unsigned char i, JsonObject& config) {
if (relayCount()) { if (relayCount()) {
config["state_topic"] = mqttTopic(MQTT_TOPIC_RELAY, i, false); config["state_topic"] = mqttTopic(MQTT_TOPIC_RELAY, i, false);
config["command_topic"] = mqttTopic(MQTT_TOPIC_RELAY, i, true); config["command_topic"] = mqttTopic(MQTT_TOPIC_RELAY, i, true);
config["payload_on"] = relayPayload(RelayStatus::ON);
config["payload_off"] = relayPayload(RelayStatus::OFF);
config["payload_on"] = relayPayload(PayloadStatus::On);
config["payload_off"] = relayPayload(PayloadStatus::Off);
config["availability_topic"] = mqttTopic(MQTT_TOPIC_STATUS, false); config["availability_topic"] = mqttTopic(MQTT_TOPIC_STATUS, false);
config["payload_available"] = mqttPayloadStatus(true); config["payload_available"] = mqttPayloadStatus(true);
config["payload_not_available"] = mqttPayloadStatus(false); config["payload_not_available"] = mqttPayloadStatus(false);


+ 54
- 18
code/espurna/led.h View File

@ -8,9 +8,57 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
#pragma once #pragma once
#include <vector>
#include <memory>
constexpr const size_t LedsMax = 8;
enum class LedMode {
NetworkAutoconfig,
NetworkConnected,
NetworkConnectedInverse,
NetworkConfig,
NetworkConfigInverse,
NetworkIdle,
None
};
enum class led_delay_mode_t {
Finite,
Infinite,
None
};
struct led_delay_t {
led_delay_t() = delete;
led_delay_t(unsigned long on_ms, unsigned long off_ms);
led_delay_t(unsigned long on_ms, unsigned long off_ms, unsigned char repeats);
led_delay_mode_t type;
unsigned long on;
unsigned long off;
unsigned char repeats;
};
struct led_pattern_t {
led_pattern_t() = default;
led_pattern_t(const std::vector<led_delay_t>& delays);
void start();
void stop();
bool started();
bool ready();
std::vector<led_delay_t> delays;
std::vector<led_delay_t> queue;
unsigned long clock_last;
unsigned long clock_delay;
};
struct led_t { struct led_t {
led_t();
led_t(unsigned char id);
led_t() = delete;
led_t(unsigned char pin, bool inverse, unsigned char mode, unsigned char relayID);
bool status(); bool status();
bool status(bool new_status); bool status(bool new_status);
@ -21,25 +69,13 @@ struct led_t {
bool inverse; bool inverse;
unsigned char mode; unsigned char mode;
unsigned char relayID; unsigned char relayID;
};
struct led_delay_t {
led_delay_t(unsigned long on_ms, unsigned long off_ms);
const unsigned long on;
const unsigned long off;
};
enum class LedMode {
NetworkAutoconfig,
NetworkConnected,
NetworkConnectedInverse,
NetworkConfig,
NetworkConfigInverse,
NetworkIdle,
None
led_pattern_t pattern;
}; };
const led_delay_t& _ledGetDelay(LedMode mode);
void ledUpdate(bool do_update); void ledUpdate(bool do_update);
unsigned char ledCount();
bool ledStatus(unsigned char id, bool status);
bool ledStatus(unsigned char id);
void ledSetup(); void ledSetup();

+ 251
- 116
code/espurna/led.ino View File

@ -8,29 +8,27 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
#if LED_SUPPORT #if LED_SUPPORT
#include <algorithm>
#include "broker.h" #include "broker.h"
#include "relay.h" #include "relay.h"
#include "rpc.h"
#include "led.h" #include "led.h"
#include "led_pattern.h"
#include "led_config.h" #include "led_config.h"
// LED helper class // LED helper class
led_t::led_t() :
pin(GPIO_NONE),
inverse(false),
mode(LED_MODE_MANUAL),
relayID(0)
{}
led_t::led_t(unsigned char id) :
pin(_ledPin(id)),
inverse(_ledInverse(id)),
mode(_ledMode(id)),
relayID(_ledRelay(id))
led_t::led_t(unsigned char pin, bool inverse, unsigned char mode, unsigned char relayID) :
pin(pin),
inverse(inverse),
mode(mode),
relayID(relayID)
{ {
if (pin != GPIO_NONE) { if (pin != GPIO_NONE) {
pinMode(pin, OUTPUT); pinMode(pin, OUTPUT);
status(false);
} }
} }
@ -48,11 +46,44 @@ bool led_t::toggle() {
return status(!status()); return status(!status());
} }
led_delay_t::led_delay_t(unsigned long on_ms, unsigned long off_ms) :
led_delay_t::led_delay_t(unsigned long on_ms, unsigned long off_ms, unsigned char repeats) :
type(repeats ? led_delay_mode_t::Finite : led_delay_mode_t::Infinite),
on(microsecondsToClockCycles(on_ms * 1000)), on(microsecondsToClockCycles(on_ms * 1000)),
off(microsecondsToClockCycles(off_ms * 1000))
off(microsecondsToClockCycles(off_ms * 1000)),
repeats(repeats ? repeats : 0)
{}
led_delay_t::led_delay_t(unsigned long on_ms, unsigned long off_ms) :
led_delay_t(on_ms, off_ms, 0)
{} {}
led_pattern_t::led_pattern_t(const std::vector<led_delay_t>& delays) :
delays(delays),
queue(),
clock_last(ESP.getCycleCount()),
clock_delay(delays.size() ? delays.back().on : 0)
{}
bool led_pattern_t::started() {
return queue.size() > 0;
}
bool led_pattern_t::ready() {
return delays.size() > 0;
}
void led_pattern_t::start() {
clock_last = ESP.getCycleCount();
clock_delay = 0;
queue = {
delays.rbegin(), delays.rend()
};
}
void led_pattern_t::stop() {
queue.clear();
}
// For relay-based modes // For relay-based modes
bool _led_update = false; bool _led_update = false;
@ -71,10 +102,51 @@ std::vector<led_t> _leds;
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
unsigned char _ledCount() {
unsigned char ledCount() {
return _leds.size(); return _leds.size();
} }
bool _ledStatus(led_t& led) {
return led.pattern.started() || led.status();
}
bool _ledStatus(led_t& led, bool status) {
bool result = false;
// when led has pattern, status depends on whether it's running
if (led.pattern.ready()) {
if (status) {
if (!led.pattern.started()) {
led.pattern.start();
}
result = true;
} else {
led.pattern.stop();
led.status(false);
result = false;
}
// if not, simply proxy status directly to the led pin
} else {
result = led.status(status);
}
return result;
}
bool _ledToggle(led_t& led) {
return _ledStatus(led, !_ledStatus(led));
}
bool ledStatus(unsigned char id, bool status) {
if (id >= ledCount()) return false;
return _ledStatus(_leds[id], status);
}
bool ledStatus(unsigned char id) {
if (id >= ledCount()) return false;
return _ledStatus(_leds[id]);
}
const led_delay_t& _ledModeToDelay(LedMode mode) { const led_delay_t& _ledModeToDelay(LedMode mode) {
static_assert( static_assert(
(sizeof(_ledDelays) / sizeof(_ledDelays[0])) <= static_cast<int>(LedMode::None), (sizeof(_ledDelays) / sizeof(_ledDelays[0])) <= static_cast<int>(LedMode::None),
@ -83,6 +155,32 @@ const led_delay_t& _ledModeToDelay(LedMode mode) {
return _ledDelays[static_cast<int>(mode)]; return _ledDelays[static_cast<int>(mode)];
} }
void _ledPattern(led_t& led) {
const auto clock_current = ESP.getCycleCount();
if (clock_current - led.pattern.clock_last >= led.pattern.clock_delay) {
const bool status = led.toggle();
auto& current = led.pattern.queue.back();
switch (current.type) {
case led_delay_mode_t::Finite:
if (status && !--current.repeats) {
led.pattern.queue.pop_back();
if (!led.pattern.queue.size()) {
led.status(false);
return;
}
}
break;
case led_delay_mode_t::Infinite:
case led_delay_mode_t::None:
default:
break;
}
led.pattern.clock_delay = status ? current.on : current.off;
led.pattern.clock_last = ESP.getCycleCount();
}
}
void _ledBlink(led_t& led, const led_delay_t& delays) { void _ledBlink(led_t& led, const led_delay_t& delays) {
static auto clock_last = ESP.getCycleCount(); static auto clock_last = ESP.getCycleCount();
static auto delay_for = delays.on; static auto delay_for = delays.on;
@ -105,18 +203,29 @@ bool _ledWebSocketOnKeyCheck(const char * key, JsonVariant& value) {
} }
void _ledWebSocketOnVisible(JsonObject& root) { void _ledWebSocketOnVisible(JsonObject& root) {
if (_ledCount() > 0) {
if (ledCount() > 0) {
root["ledVisible"] = 1; root["ledVisible"] = 1;
} }
} }
void _ledWebSocketOnConnected(JsonObject& root) { void _ledWebSocketOnConnected(JsonObject& root) {
if (!_ledCount()) return;
JsonArray& leds = root.createNestedArray("ledConfig");
for (unsigned char id = 0; id < _ledCount(); ++id) {
JsonObject& led = leds.createNestedObject();
led["mode"] = getSetting({"ledMode", id}, _leds[id].mode);
led["relay"] = getSetting<unsigned char>({"ledRelay", id}, _leds[id].relayID);
if (!ledCount()) return;
JsonObject& module = root.createNestedObject("led");
JsonArray& schema = module.createNestedArray("schema");
schema.add("GPIO");
schema.add("Inv");
schema.add("Mode");
schema.add("Relay");
JsonArray& leds = module.createNestedArray("list");
for (unsigned char index = 0; index < ledCount(); ++index) {
JsonArray& led = leds.createNestedArray();
led.add(getSetting({"ledGPIO", index}, _ledPin(index)));
led.add(static_cast<int>(getSetting({"ledInv", index}, _ledInverse(index))));
led.add(getSetting({"ledMode", index}, _ledMode(index)));
led.add(getSetting({"ledRelay", index}, _ledRelay(index)));
} }
} }
@ -150,7 +259,7 @@ void _ledMQTTCallback(unsigned int type, const char * topic, const char * payloa
// Get led ID from after the slash when t is `led/<LED_ID>` // Get led ID from after the slash when t is `led/<LED_ID>`
unsigned int ledID = magnitude.substring(strlen(MQTT_TOPIC_LED) + 1).toInt(); unsigned int ledID = magnitude.substring(strlen(MQTT_TOPIC_LED) + 1).toInt();
if (ledID >= _ledCount()) {
if (ledID >= ledCount()) {
DEBUG_MSG_P(PSTR("[LED] Wrong ledID (%d)\n"), ledID); DEBUG_MSG_P(PSTR("[LED] Wrong ledID (%d)\n"), ledID);
return; return;
} }
@ -158,14 +267,21 @@ void _ledMQTTCallback(unsigned int type, const char * topic, const char * payloa
// Check if LED is managed // Check if LED is managed
if (_leds[ledID].mode != LED_MODE_MANUAL) return; if (_leds[ledID].mode != LED_MODE_MANUAL) return;
// Get value based on relays payload logic (0 / off, 1 / on, 2 / toggle)
const auto value = relayParsePayload(payload);
// Action to perform is also based on relay constants ... TODO generic enum?
if (value == RelayStatus::TOGGLE) {
_leds[ledID].toggle();
} else {
_leds[ledID].status(value == RelayStatus::ON);
// Get value based on rpc payload logic (see rpc.ino)
const auto value = rpcParsePayload(payload);
switch (value) {
case PayloadStatus::On:
case PayloadStatus::Off:
_ledStatus(_leds[ledID], (value == PayloadStatus::On));
break;
case PayloadStatus::Toggle:
_ledToggle(_leds[ledID]);
break;
case PayloadStatus::Unknown:
default:
_ledLoadPattern(_leds[ledID], payload);
_ledStatus(_leds[ledID], true);
break;
} }
} }
@ -176,7 +292,9 @@ void _ledMQTTCallback(unsigned int type, const char * topic, const char * payloa
void _ledConfigure() { void _ledConfigure() {
for (unsigned char id = 0; id < _leds.size(); ++id) { for (unsigned char id = 0; id < _leds.size(); ++id) {
_leds[id].mode = getSetting({"ledMode", id}, _ledMode(id)); _leds[id].mode = getSetting({"ledMode", id}, _ledMode(id));
_leds[id].relayID = getSetting<unsigned char>({"ledRelay", id}, _ledRelay(id));
_leds[id].relayID = getSetting({"ledRelay", id}, _ledRelay(id));
_leds[id].pattern.stop();
_ledLoadPattern(_leds[id], getSetting({"ledPattern", id}).c_str());
} }
_led_update = true; _led_update = true;
} }
@ -218,11 +336,20 @@ void ledSetup() {
_leds.reserve(leds); _leds.reserve(leds);
for (unsigned char id=0; id < leds; ++id) {
_leds.emplace_back(id);
for (unsigned char index=0; index < leds; ++index) {
const auto pin = getSetting({"ledGPIO", index}, _ledPin(index));
if (!gpioValid(pin)) {
break;
}
_leds.emplace_back(
pin,
getSetting({"ledInv", index}, _ledInverse(index)),
getSetting({"ledMode", index}, _ledMode(index)),
getSetting({"ledRelay", index}, _ledRelay(index))
);
} }
_ledConfigure();
_led_update = true;
#if MQTT_SUPPORT #if MQTT_SUPPORT
mqttRegister(_ledMQTTCallback); mqttRegister(_ledMQTTCallback);
@ -254,103 +381,111 @@ void ledLoop() {
for (auto& led : _leds) { for (auto& led : _leds) {
if (led.mode == LED_MODE_WIFI) {
if ((wifi_state & WIFI_STATE_WPS) || (wifi_state & WIFI_STATE_SMARTCONFIG)) {
_ledBlink(led, LedMode::NetworkAutoconfig);
} else if (wifi_state & WIFI_STATE_STA) {
_ledBlink(led, LedMode::NetworkConnected);
} else if (wifi_state & WIFI_STATE_AP) {
_ledBlink(led, LedMode::NetworkConfig);
} else {
_ledBlink(led, LedMode::NetworkIdle);
}
}
if (led.mode == LED_MODE_FINDME_WIFI) {
if ((wifi_state & WIFI_STATE_WPS) || (wifi_state & WIFI_STATE_SMARTCONFIG)) {
_ledBlink(led, LedMode::NetworkAutoconfig);
} else if (wifi_state & WIFI_STATE_STA) {
if (relayStatus(led.relayID)) {
switch (led.mode) {
case LED_MODE_WIFI:
if ((wifi_state & WIFI_STATE_WPS) || (wifi_state & WIFI_STATE_SMARTCONFIG)) {
_ledBlink(led, LedMode::NetworkAutoconfig);
} else if (wifi_state & WIFI_STATE_STA) {
_ledBlink(led, LedMode::NetworkConnected); _ledBlink(led, LedMode::NetworkConnected);
} else {
_ledBlink(led, LedMode::NetworkConnectedInverse);
}
} else if (wifi_state & WIFI_STATE_AP) {
if (relayStatus(led.relayID)) {
} else if (wifi_state & WIFI_STATE_AP) {
_ledBlink(led, LedMode::NetworkConfig); _ledBlink(led, LedMode::NetworkConfig);
} else { } else {
_ledBlink(led, LedMode::NetworkConfigInverse);
_ledBlink(led, LedMode::NetworkIdle);
} }
} else {
_ledBlink(led, LedMode::NetworkIdle);
}
}
if (led.mode == LED_MODE_RELAY_WIFI) {
if ((wifi_state & WIFI_STATE_WPS) || (wifi_state & WIFI_STATE_SMARTCONFIG)) {
_ledBlink(led, LedMode::NetworkAutoconfig);
} else if (wifi_state & WIFI_STATE_STA) {
if (relayStatus(led.relayID)) {
_ledBlink(led, LedMode::NetworkConnected);
break;
case LED_MODE_FINDME_WIFI:
if ((wifi_state & WIFI_STATE_WPS) || (wifi_state & WIFI_STATE_SMARTCONFIG)) {
_ledBlink(led, LedMode::NetworkAutoconfig);
} else if (wifi_state & WIFI_STATE_STA) {
if (relayStatus(led.relayID)) {
_ledBlink(led, LedMode::NetworkConnected);
} else {
_ledBlink(led, LedMode::NetworkConnectedInverse);
}
} else if (wifi_state & WIFI_STATE_AP) {
if (relayStatus(led.relayID)) {
_ledBlink(led, LedMode::NetworkConfig);
} else {
_ledBlink(led, LedMode::NetworkConfigInverse);
}
} else { } else {
_ledBlink(led, LedMode::NetworkConnectedInverse);
_ledBlink(led, LedMode::NetworkIdle);
} }
} else if (wifi_state & WIFI_STATE_AP) {
if (relayStatus(led.relayID)) {
_ledBlink(led, LedMode::NetworkConfig);
break;
case LED_MODE_RELAY_WIFI:
if ((wifi_state & WIFI_STATE_WPS) || (wifi_state & WIFI_STATE_SMARTCONFIG)) {
_ledBlink(led, LedMode::NetworkAutoconfig);
} else if (wifi_state & WIFI_STATE_STA) {
if (relayStatus(led.relayID)) {
_ledBlink(led, LedMode::NetworkConnected);
} else {
_ledBlink(led, LedMode::NetworkConnectedInverse);
}
} else if (wifi_state & WIFI_STATE_AP) {
if (relayStatus(led.relayID)) {
_ledBlink(led, LedMode::NetworkConfig);
} else {
_ledBlink(led, LedMode::NetworkConfigInverse);
}
} else { } else {
_ledBlink(led, LedMode::NetworkConfigInverse);
_ledBlink(led, LedMode::NetworkIdle);
} }
} else {
_ledBlink(led, LedMode::NetworkIdle);
}
}
// Relay-based modes, update only if relays have been updated
if (!_led_update) continue;
if (led.mode == LED_MODE_FOLLOW) {
led.status(relayStatus(led.relayID));
}
if (led.mode == LED_MODE_FOLLOW_INVERSE) {
led.status(!relayStatus(led.relayID));
}
if (led.mode == LED_MODE_FINDME) {
bool status = true;
for (unsigned char relayID = 0; relayID < relayCount(); ++relayID) {
if (relayStatus(relayID)) {
status = false;
break;
break;
case LED_MODE_FOLLOW:
if (!_led_update) break;
_ledStatus(led, relayStatus(led.relayID));
break;
case LED_MODE_FOLLOW_INVERSE:
if (!_led_update) break;
led.status(!relayStatus(led.relayID));
_ledStatus(led, !relayStatus(led.relayID));
break;
case LED_MODE_FINDME: {
if (!_led_update) break;
bool status = true;
for (unsigned char relayID = 0; relayID < relayCount(); ++relayID) {
if (relayStatus(relayID)) {
status = false;
break;
}
} }
_ledStatus(led, status);
break;
} }
led.status(status);
}
if (led.mode == LED_MODE_RELAY) {
bool status = false;
for (unsigned char relayID = 0; relayID < relayCount(); ++relayID) {
if (relayStatus(relayID)) {
status = true;
break;
case LED_MODE_RELAY: {
if (!_led_update) break;
bool status = false;
for (unsigned char relayID = 0; relayID < relayCount(); ++relayID) {
if (relayStatus(relayID)) {
status = true;
break;
}
} }
_ledStatus(led, status);
break;
} }
led.status(status);
}
if (led.mode == LED_MODE_ON) {
led.status(true);
case LED_MODE_ON:
if (!_led_update) break;
_ledStatus(led, true);
break;
case LED_MODE_OFF:
if (!_led_update) break;
_ledStatus(led, false);
break;
} }
if (led.mode == LED_MODE_OFF) {
led.status(false);
if (led.pattern.started()) {
_ledPattern(led);
continue;
} }
} }


+ 166
- 0
code/espurna/led_pattern.h View File

@ -0,0 +1,166 @@
/* Generated by re2c 1.3 on Wed Mar 4 13:32:28 2020 */
#line 1 "led_pattern.h.in"
/*
LED MODULE
Copyright (C) 2020 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
*/
#pragma once
#include <cstring>
#include "led.h"
// Scans input string with format
// '<on1>,<off1>,<repeats1> <on2>,<off2>,<repeats2> ...'
// Directly changing `led.pattern.delays` contents
void _ledLoadPattern(led_t& led, const char* input) {
char buffer[16];
const char* d1;
const char* d2;
const char* d3;
const char* p = input;
const char* marker;
led.pattern.delays.clear();
loop:
const char *yyt1;const char *yyt2;const char *yyt3;
#line 37 "led_pattern.h"
{
char yych;
static const unsigned char yybm[] = {
0, 0, 0, 0, 0, 0, 0, 0,
0, 64, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
64, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
128, 128, 128, 128, 128, 128, 128, 128,
128, 128, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
};
yych = (char)*p;
if (yybm[0+yych] & 64) {
goto yy4;
}
if (yych <= '/') goto yy2;
if (yych <= '9') {
yyt1 = p;
goto yy7;
}
yy2:
++p;
yy3:
#line 45 "led_pattern.h.in"
{ return; }
#line 88 "led_pattern.h"
yy4:
yych = (char)*++p;
if (yybm[0+yych] & 64) {
goto yy4;
}
#line 46 "led_pattern.h.in"
{ goto loop; }
#line 96 "led_pattern.h"
yy7:
yych = (char)*(marker = ++p);
if (yybm[0+yych] & 128) {
goto yy10;
}
if (yych != ',') goto yy3;
yy8:
yych = (char)*++p;
if (yych <= '/') goto yy9;
if (yych <= '9') {
yyt2 = p;
goto yy12;
}
yy9:
p = marker;
goto yy3;
yy10:
yych = (char)*++p;
if (yybm[0+yych] & 128) {
goto yy10;
}
if (yych == ',') goto yy8;
goto yy9;
yy12:
yych = (char)*++p;
if (yych == ',') goto yy14;
if (yych <= '/') goto yy9;
if (yych <= '9') goto yy12;
goto yy9;
yy14:
yych = (char)*++p;
if (yych <= '/') goto yy9;
if (yych >= ':') goto yy9;
yyt3 = p;
yy15:
yych = (char)*++p;
if (yych <= '/') goto yy17;
if (yych <= '9') goto yy15;
yy17:
d1 = yyt1;
d2 = yyt2;
d3 = yyt3;
#line 47 "led_pattern.h.in"
{
unsigned long on;
unsigned long off;
unsigned char repeats;
memcpy(buffer, d1, int(d2 - d1));
buffer[int(d2 - d1 - 1)] = '\0';
on = strtoul(buffer, nullptr, 10);
memcpy(buffer, d2, int(d3 - d2));
buffer[int(d3 - d2 - 1)] = '\0';
off = strtoul(buffer, nullptr, 10);
memcpy(buffer, d3, int(p - d3));
buffer[int(p - d3)] = '\0';
repeats = strtoul(buffer, nullptr, 10);
led.pattern.delays.emplace_back(
on, off, repeats
);
goto loop;
}
#line 163 "led_pattern.h"
}
#line 70 "led_pattern.h.in"
}

+ 71
- 0
code/espurna/led_pattern.h.in View File

@ -0,0 +1,71 @@
/*
LED MODULE
Copyright (C) 2020 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
*/
#pragma once
#include <cstring>
#include "led.h"
// Scans input string with format
// '<on1>,<off1>,<repeats1> <on2>,<off2>,<repeats2> ...'
// Directly changing `led.pattern.delays` contents
void _ledLoadPattern(led_t& led, const char* input) {
char buffer[16];
const char* d1;
const char* d2;
const char* d3;
const char* p = input;
const char* marker;
led.pattern.delays.clear();
loop:
/*!stags:re2c format = 'const char *@@;'; */
/*!re2c
re2c:define:YYCTYPE = char;
re2c:define:YYCURSOR = p;
re2c:define:YYMARKER = marker;
re2c:yyfill:enable = 0;
re2c:yych:conversion = 1;
re2c:indent:top = 1;
end = "\x00";
wsp = [ \t]+;
num = [0-9]+;
* { return; }
wsp { goto loop; }
@d1 num [,] @d2 num [,] @d3 num {
unsigned long on;
unsigned long off;
unsigned char repeats;
memcpy(buffer, d1, int(d2 - d1));
buffer[int(d2 - d1 - 1)] = '\0';
on = strtoul(buffer, nullptr, 10);
memcpy(buffer, d2, int(d3 - d2));
buffer[int(d3 - d2 - 1)] = '\0';
off = strtoul(buffer, nullptr, 10);
memcpy(buffer, d3, int(p - d3));
buffer[int(p - d3)] = '\0';
repeats = strtoul(buffer, nullptr, 10);
led.pattern.delays.emplace_back(
on, off, repeats
);
goto loop;
}
*/
}

+ 3
- 9
code/espurna/relay.h View File

@ -9,17 +9,11 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
#pragma once #pragma once
#include <bitset> #include <bitset>
#include "rpc.h"
#include "utils.h" #include "utils.h"
constexpr size_t RELAYS_MAX = 32; constexpr size_t RELAYS_MAX = 32;
enum class RelayStatus : unsigned char {
OFF = 0,
ON = 1,
TOGGLE = 2,
UNKNOWN = 0xFF
};
struct RelayMask { struct RelayMask {
explicit RelayMask(const String& string) : explicit RelayMask(const String& string) :
@ -51,7 +45,7 @@ struct RelayMask {
}; };
RelayStatus relayParsePayload(const char * payload);
PayloadStatus relayParsePayload(const char * payload);
bool relayStatus(unsigned char id, bool status, bool report, bool group_report); bool relayStatus(unsigned char id, bool status, bool report, bool group_report);
bool relayStatus(unsigned char id, bool status); bool relayStatus(unsigned char id, bool status);
@ -66,7 +60,7 @@ const String& relayPayloadOn();
const String& relayPayloadOff(); const String& relayPayloadOff();
const String& relayPayloadToggle(); const String& relayPayloadToggle();
const char* relayPayload(RelayStatus status);
const char* relayPayload(PayloadStatus status);
void relaySetupDummy(size_t size, bool reconfigure = false); void relaySetupDummy(size_t size, bool reconfigure = false);

+ 59
- 76
code/espurna/relay.ino View File

@ -17,6 +17,7 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
#include "settings.h" #include "settings.h"
#include "mqtt.h" #include "mqtt.h"
#include "relay.h" #include "relay.h"
#include "rpc.h"
#include "tuya.h" #include "tuya.h"
#include "ws.h" #include "ws.h"
@ -103,13 +104,13 @@ bool _relay_report_ws = false;
#endif // WEB_SUPPORT #endif // WEB_SUPPORT
#if MQTT_SUPPORT
#if MQTT_SUPPORT || API_SUPPORT
String _relay_mqtt_payload_on;
String _relay_mqtt_payload_off;
String _relay_mqtt_payload_toggle;
String _relay_rpc_payload_on;
String _relay_rpc_payload_off;
String _relay_rpc_payload_toggle;
#endif // MQTT_SUPPORT
#endif // MQTT_SUPPORT || API_SUPPORT
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// UTILITY // UTILITY
@ -117,28 +118,28 @@ String _relay_mqtt_payload_toggle;
bool _relayHandlePayload(unsigned char relayID, const char* payload) { bool _relayHandlePayload(unsigned char relayID, const char* payload) {
auto value = relayParsePayload(payload); auto value = relayParsePayload(payload);
if (value == RelayStatus::UNKNOWN) return false;
if (value == PayloadStatus::Unknown) return false;
if (value == RelayStatus::OFF) {
if (value == PayloadStatus::Off) {
relayStatus(relayID, false); relayStatus(relayID, false);
} else if (value == RelayStatus::ON) {
} else if (value == PayloadStatus::On) {
relayStatus(relayID, true); relayStatus(relayID, true);
} else if (value == RelayStatus::TOGGLE) {
} else if (value == PayloadStatus::Toggle) {
relayToggle(relayID); relayToggle(relayID);
} }
return true; return true;
} }
RelayStatus _relayStatusInvert(RelayStatus status) {
return (status == RelayStatus::ON) ? RelayStatus::OFF : status;
PayloadStatus _relayStatusInvert(PayloadStatus status) {
return (status == PayloadStatus::On) ? PayloadStatus::Off : status;
} }
RelayStatus _relayStatusTyped(unsigned char id) {
if (id >= _relays.size()) return RelayStatus::OFF;
PayloadStatus _relayStatusTyped(unsigned char id) {
if (id >= _relays.size()) return PayloadStatus::Off;
const bool status = _relays[id].current_status; const bool status = _relays[id].current_status;
return (status) ? RelayStatus::ON : RelayStatus::OFF;
return (status) ? PayloadStatus::On : PayloadStatus::Off;
} }
void _relayLockAll() { void _relayLockAll() {
@ -676,41 +677,17 @@ unsigned char relayCount() {
return _relays.size(); return _relays.size();
} }
RelayStatus relayParsePayload(const char * payload) {
// Don't parse empty strings
const auto len = strlen(payload);
if (!len) return RelayStatus::UNKNOWN;
// Check most commonly used payloads
if (len == 1) {
if (payload[0] == '0') return RelayStatus::OFF;
if (payload[0] == '1') return RelayStatus::ON;
if (payload[0] == '2') return RelayStatus::TOGGLE;
return RelayStatus::UNKNOWN;
}
// If possible, compare to locally configured payload strings
#if MQTT_SUPPORT
if (_relay_mqtt_payload_off.equals(payload)) return RelayStatus::OFF;
if (_relay_mqtt_payload_on.equals(payload)) return RelayStatus::ON;
if (_relay_mqtt_payload_toggle.equals(payload)) return RelayStatus::TOGGLE;
#endif // MQTT_SUPPORT
// Finally, check for "OFF", "ON", "TOGGLE" (both lower and upper cases)
String temp(payload);
temp.trim();
if (temp.equalsIgnoreCase("off")) {
return RelayStatus::OFF;
} else if (temp.equalsIgnoreCase("on")) {
return RelayStatus::ON;
} else if (temp.equalsIgnoreCase("toggle")) {
return RelayStatus::TOGGLE;
}
return RelayStatus::UNKNOWN;
PayloadStatus relayParsePayload(const char * payload) {
#if MQTT_SUPPORT || API_SUPPORT
return rpcParsePayload(payload, [](const char* payload) {
if (_relay_rpc_payload_off.equals(payload)) return PayloadStatus::Off;
if (_relay_rpc_payload_on.equals(payload)) return PayloadStatus::On;
if (_relay_rpc_payload_toggle.equals(payload)) return PayloadStatus::Toggle;
return PayloadStatus::Unknown;
});
#else
return rpcParsePayload(payload);
#endif
} }
// BACKWARDS COMPATIBILITY // BACKWARDS COMPATIBILITY
@ -833,11 +810,11 @@ void _relayConfigure() {
_relay_delay_interlock = getSetting("relayDelayInterlock", RELAY_DELAY_INTERLOCK); _relay_delay_interlock = getSetting("relayDelayInterlock", RELAY_DELAY_INTERLOCK);
_relay_sync_mode = getSetting("relaySync", RELAY_SYNC); _relay_sync_mode = getSetting("relaySync", RELAY_SYNC);
#if MQTT_SUPPORT
#if MQTT_SUPPORT || API_SUPPORT
settingsProcessConfig({ settingsProcessConfig({
{_relay_mqtt_payload_on, "relayPayloadOn", RELAY_MQTT_ON},
{_relay_mqtt_payload_off, "relayPayloadOff", RELAY_MQTT_OFF},
{_relay_mqtt_payload_toggle, "relayPayloadToggle", RELAY_MQTT_TOGGLE},
{_relay_rpc_payload_on, "relayPayloadOn", RELAY_MQTT_ON},
{_relay_rpc_payload_off, "relayPayloadOff", RELAY_MQTT_OFF},
{_relay_rpc_payload_toggle, "relayPayloadToggle", RELAY_MQTT_TOGGLE},
}); });
#endif // MQTT_SUPPORT #endif // MQTT_SUPPORT
} }
@ -1059,33 +1036,38 @@ void relaySetupAPI() {
// MQTT // MQTT
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
#if MQTT_SUPPORT
#if MQTT_SUPPORT || API_SUPPORT
const String& relayPayloadOn() { const String& relayPayloadOn() {
return _relay_mqtt_payload_on;
return _relay_rpc_payload_on;
} }
const String& relayPayloadOff() { const String& relayPayloadOff() {
return _relay_mqtt_payload_off;
return _relay_rpc_payload_off;
} }
const String& relayPayloadToggle() { const String& relayPayloadToggle() {
return _relay_mqtt_payload_toggle;
return _relay_rpc_payload_toggle;
} }
const char* relayPayload(RelayStatus status) {
if (status == RelayStatus::OFF) {
return _relay_mqtt_payload_off.c_str();
} else if (status == RelayStatus::ON) {
return _relay_mqtt_payload_on.c_str();
} else if (status == RelayStatus::TOGGLE) {
return _relay_mqtt_payload_toggle.c_str();
const char* relayPayload(PayloadStatus status) {
switch (status) {
case PayloadStatus::Off:
return _relay_rpc_payload_off.c_str();
case PayloadStatus::On:
return _relay_rpc_payload_on.c_str();
case PayloadStatus::Toggle:
return _relay_rpc_payload_toggle.c_str();
case PayloadStatus::Unknown:
default:
return "";
} }
return "";
} }
#endif // MQTT_SUPPORT || API_SUPPORT
#if MQTT_SUPPORT
void _relayMQTTGroup(unsigned char id) { void _relayMQTTGroup(unsigned char id) {
const String topic = getSetting({"mqttGroup", id}); const String topic = getSetting({"mqttGroup", id});
if (!topic.length()) return; if (!topic.length()) return;
@ -1129,17 +1111,18 @@ void relayMQTT() {
} }
} }
void relayStatusWrap(unsigned char id, RelayStatus value, bool is_group_topic) {
void relayStatusWrap(unsigned char id, PayloadStatus value, bool is_group_topic) {
switch (value) { switch (value) {
case RelayStatus::OFF:
case PayloadStatus::Off:
relayStatus(id, false, mqttForward(), !is_group_topic); relayStatus(id, false, mqttForward(), !is_group_topic);
break; break;
case RelayStatus::ON:
case PayloadStatus::On:
relayStatus(id, true, mqttForward(), !is_group_topic); relayStatus(id, true, mqttForward(), !is_group_topic);
break; break;
case RelayStatus::TOGGLE:
case PayloadStatus::Toggle:
relayToggle(id, true, true); relayToggle(id, true, true);
break; break;
case PayloadStatus::Unknown:
default: default:
_relays[id].report = true; _relays[id].report = true;
relayMQTT(id); relayMQTT(id);
@ -1219,7 +1202,7 @@ void relayMQTTCallback(unsigned int type, const char * topic, const char * paylo
// Get value // Get value
auto value = relayParsePayload(payload); auto value = relayParsePayload(payload);
if (value == RelayStatus::UNKNOWN) return;
if (value == PayloadStatus::Unknown) return;
relayStatusWrap(id, value, false); relayStatusWrap(id, value, false);
@ -1235,9 +1218,9 @@ void relayMQTTCallback(unsigned int type, const char * topic, const char * paylo
if ((t.length() > 0) && t.equals(topic)) { if ((t.length() > 0) && t.equals(topic)) {
auto value = relayParsePayload(payload); auto value = relayParsePayload(payload);
if (value == RelayStatus::UNKNOWN) return;
if (value == PayloadStatus::Unknown) return;
if ((value == RelayStatus::ON) || (value == RelayStatus::OFF)) {
if ((value == PayloadStatus::On) || (value == PayloadStatus::Off)) {
if (getSetting({"mqttGroupSync", i}, RELAY_GROUP_SYNC_NORMAL) == RELAY_GROUP_SYNC_INVERSE) { if (getSetting({"mqttGroupSync", i}, RELAY_GROUP_SYNC_NORMAL) == RELAY_GROUP_SYNC_INVERSE) {
value = _relayStatusInvert(value); value = _relayStatusInvert(value);
} }
@ -1263,10 +1246,10 @@ void relayMQTTCallback(unsigned int type, const char * topic, const char * paylo
const auto reaction = getSetting({"relayOnDisc", i}, 0); const auto reaction = getSetting({"relayOnDisc", i}, 0);
if (1 == reaction) { // switch relay OFF if (1 == reaction) { // switch relay OFF
DEBUG_MSG_P(PSTR("[RELAY] Reset relay (%d) due to MQTT disconnection\n"), i); DEBUG_MSG_P(PSTR("[RELAY] Reset relay (%d) due to MQTT disconnection\n"), i);
relayStatusWrap(i, RelayStatus::OFF, false);
relayStatusWrap(i, PayloadStatus::Off, false);
} else if(2 == reaction) { // switch relay ON } else if(2 == reaction) { // switch relay ON
DEBUG_MSG_P(PSTR("[RELAY] Set relay (%d) due to MQTT disconnection\n"), i); DEBUG_MSG_P(PSTR("[RELAY] Set relay (%d) due to MQTT disconnection\n"), i);
relayStatusWrap(i, RelayStatus::ON, false);
relayStatusWrap(i, PayloadStatus::On, false);
} }
} }


+ 13
- 0
code/espurna/rpc.h View File

@ -6,4 +6,17 @@ Part of MQTT and API modules
#pragma once #pragma once
#include <vector>
#include <utility>
enum class PayloadStatus {
Off = 0,
On = 1,
Toggle = 2,
Unknown = 0xFF
};
using rpc_payload_check_t = PayloadStatus(*)(const char*);
bool rpcHandleAction(const String& action); bool rpcHandleAction(const String& action);
PayloadStatus rpcParsePayload(const char* payload, const rpc_payload_check_t ext_check = nullptr);

+ 41
- 0
code/espurna/rpc.ino View File

@ -2,11 +2,14 @@
Part of MQTT and API modules Part of MQTT and API modules
Copyright (C) 2020 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
*/ */
#if MQTT_SUPPORT || API_SUPPORT #if MQTT_SUPPORT || API_SUPPORT
#include <Schedule.h> #include <Schedule.h>
#include <cstring>
#include "system.h" #include "system.h"
#include "utils.h" #include "utils.h"
@ -26,4 +29,42 @@ bool rpcHandleAction(const String& action) {
return result; return result;
} }
PayloadStatus rpcParsePayload(const char* payload, const rpc_payload_check_t ext_check) {
// Don't parse empty strings
const auto len = strlen(payload);
if (!len) return PayloadStatus::Unknown;
// Check most commonly used payloads
if (len == 1) {
if (payload[0] == '0') return PayloadStatus::Off;
if (payload[0] == '1') return PayloadStatus::On;
if (payload[0] == '2') return PayloadStatus::Toggle;
return PayloadStatus::Unknown;
}
// If possible, use externally provided payload checker
if (ext_check) {
const PayloadStatus result = ext_check(payload);
if (result != PayloadStatus::Unknown) {
return result;
}
}
// Finally, check for "OFF", "ON", "TOGGLE" (both lower and upper cases)
String temp(payload);
temp.trim();
if (temp.equalsIgnoreCase("off")) {
return PayloadStatus::Off;
} else if (temp.equalsIgnoreCase("on")) {
return PayloadStatus::On;
} else if (temp.equalsIgnoreCase("toggle")) {
return PayloadStatus::Toggle;
}
return PayloadStatus::Unknown;
}
#endif // MQTT_SUPPORT || API_SUPPORT #endif // MQTT_SUPPORT || API_SUPPORT

+ 2534
- 2528
code/espurna/static/index.all.html.gz.h
File diff suppressed because it is too large
View File


+ 2331
- 2325
code/espurna/static/index.light.html.gz.h
File diff suppressed because it is too large
View File


+ 2434
- 2429
code/espurna/static/index.lightfox.html.gz.h
File diff suppressed because it is too large
View File


+ 2457
- 2452
code/espurna/static/index.rfbridge.html.gz.h
File diff suppressed because it is too large
View File


+ 3316
- 3311
code/espurna/static/index.rfm69.html.gz.h
File diff suppressed because it is too large
View File


+ 2023
- 2017
code/espurna/static/index.sensor.html.gz.h
File diff suppressed because it is too large
View File


+ 2378
- 2374
code/espurna/static/index.small.html.gz.h
File diff suppressed because it is too large
View File


+ 1953
- 1948
code/espurna/static/index.thermostat.html.gz.h
File diff suppressed because it is too large
View File


+ 31
- 26
code/html/custom.js View File

@ -1237,23 +1237,6 @@ function initRelayConfig(data) {
} }
function initLeds(data) {
var current = $("#ledConfig > div").length;
if (current > 0) { return; }
var size = data.length;
var template = $("#ledConfigTemplate").children();
for (var i=0; i<size; ++i) {
var line = $(template).clone();
$("span.id", line).html(i);
$("select", line).attr("data", i);
$("input", line).attr("data", i);
line.appendTo("#ledConfig");
}
}
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Sensors & Magnitudes // Sensors & Magnitudes
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -1863,15 +1846,37 @@ function processData(data) {
// LEDs // LEDs
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
if ("ledConfig" === key) {
initLeds(value);
for (var i=0; i<value.length; ++i) {
var mode = $("select[name='ledMode'][data='" + i + "']");
var relay = $("select[name='ledRelay'][data='" + i + "']");
mode.val(value[i].mode);
relay.val(value[i].relay);
setOriginalsFromValues($([mode,relay]));
}
if ("led" === key) {
if($("#ledConfig > div").length > 0) return;
var schema = value["schema"];
value["list"].forEach(function(led_data, index) {
if (schema.length !== led_data.length) {
throw "LED schema mismatch!";
}
var led = {};
schema.forEach(function(key, index) {
led[key] = led_data[index];
});
var line = $($("#ledConfigTemplate").children()).clone();
$("span.id", line).html(index);
$("select", line).attr("data", index);
$("input", line).attr("data", index);
$("select[name='ledGPIO']", line).val(led.GPIO);
// XXX: checkbox implementation depends on unique id
// $("input[name='ledInv']", line).val(led.Inv);
$("select[name='ledMode']", line).val(led.Mode);
$("input[name='ledRelay']", line).val(led.Relay);
setOriginalsFromValues($("input,select", line));
line.appendTo("#ledConfig");
});
return; return;
} }


+ 46
- 31
code/html/index.html View File

@ -419,22 +419,28 @@
<div class="page"> <div class="page">
<fieldset>
<legend class="module module-led">Modes</legend>
<div class="pure-g module module-led">
<div class="pure-u-1 pure-u-lg-3-4 hint">
<li><strong>WiFi status</strong> will blink at 1Hz when trying to connect. If successfully connected it will briefly blink every 5 seconds if in STA mode or every second if in AP mode.</li>
<li><strong>Follow switch</strong> will force the LED to follow the status of a given switch (you must define which switch to follow in the side field).</li>
<li><strong>Inverse switch</strong> will force the LED to not-follow the status of a given switch (you must define which switch to follow in the side field).</li>
<li><strong>Find me</strong> will turn the LED ON when all switches are OFF. This is meant to locate switches at night.</li>
<li><strong>Find me &amp; WiFi</strong> will follow the WiFi status but will stay mostly on when switches are OFF, and mostly OFF when any of them is ON.</li>
<li><strong>Switches status</strong> will turn the LED ON whenever any switch is ON, and OFF otherwise. This is global status notification.</li>
<li><strong>Switches status &amp; WiFi</strong> will follow the WiFi status but will stay mostly off when switches are OFF, and mostly ON when any of them is ON.</li>
<li><strong>MQTT managed</strong> will let you manage the LED status via MQTT by sending a message to "&lt;base_topic&gt;/led/0/set" with a payload of 0, 1 or 2 (to toggle it).</li>
<li><strong>Always ON</strong> and <strong>Always OFF</strong> modes are self-explanatory.</li>
</div>
<details>
<summary><legend class="module module-led">Available modes</legend></summary>
<br/>
<div class="pure-g module module-led">
<div class="pure-u-1 pure-u-lg-3-4 hint">
<li><strong>WiFi status</strong> will blink at 1Hz when trying to connect. If successfully connected it will briefly blink every 5 seconds if in STA mode or every second if in AP mode.</li>
<li><strong>Follow switch</strong> will force the LED to follow the status of a given switch (you must define which switch to follow in the side field).</li>
<li><strong>Inverse switch</strong> will force the LED to not-follow the status of a given switch (you must define which switch to follow in the side field).</li>
<li><strong>Find me</strong> will turn the LED ON when all switches are OFF. This is meant to locate switches at night.</li>
<li><strong>Find me &amp; WiFi</strong> will follow the WiFi status but will stay mostly on when switches are OFF, and mostly OFF when any of them is ON.</li>
<li><strong>Switches status</strong> will turn the LED ON whenever any switch is ON, and OFF otherwise. This is global status notification.</li>
<li><strong>Switches status &amp; WiFi</strong> will follow the WiFi status but will stay mostly off when switches are OFF, and mostly ON when any of them is ON.</li>
<li><strong>Manually managed</strong> will let you manage the LED status via MQTT by sending a message to "&lt;base_topic&gt;/led/0/set" with a payload of 0, 1 or 2 (to toggle it).</li>
<li><strong>Always ON</strong> and <strong>Always OFF</strong> modes are self-explanatory.</li>
</div> </div>
</div>
</details>
<fieldset>
<div id="ledConfig"></div> <div id="ledConfig"></div>
@ -1775,22 +1781,31 @@
<!-- Templates --> <!-- Templates -->
<div id="ledConfigTemplate" class="template"> <div id="ledConfigTemplate" class="template">
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">LED #<span class="id"></span> mode</label>
<select name="ledMode" class="pure-u-1-4">
<option value="1">WiFi status</option>
<option value="2">Follow switch #</option>
<option value="3">Inverse switch #</option>
<option value="4">Find me</option>
<option value="5">Find me &amp; WiFi</option>
<option value="8">Switches status</option>
<option value="9">Switches &amp; WiFi</option>
<option value="0">MQTT managed</option>
<option value="6">Always ON</option>
<option value="7">Always OFF</option>
</select>
&nbsp;
<div class="pure-u-1-4"><input class="pure-u-23-24" name="ledRelay" type="number" min="0" data="0" /></div>
<div class="pure-form pure-form-aligned">
<legend class="pure-u-1 pure-u-lg-1-4">LED #<span class="id"></span></legend>
<div class="pure-control-group">
<label>GPIO</label>
<select class="pure-input-2-3 gpio-select" name="ledGPIO" action="reboot"></select>
</div>
<div class="pure-control-group">
<label>Mode</label>
<select class="pure-input-2-3" name="ledMode">
<option value="1">WiFi status</option>
<option value="2">Follow switch #</option>
<option value="3">Inverse switch #</option>
<option value="4">Find me</option>
<option value="5">Find me &amp; WiFi</option>
<option value="8">Switches status</option>
<option value="9">Switches &amp; WiFi</option>
<option value="0">Manual</option>
<option value="6">Always ON</option>
<option value="7">Always OFF</option>
</select>
</div>
<div class="pure-control-group">
<label>Relay ID</label>
<input class="pure-input-2-3" name="ledRelay" type="number" min="0" data="0"></input>
</div>
</div> </div>
</div> </div>


Loading…
Cancel
Save