MultiRelay usermod.

beta 2
This commit is contained in:
Blaz Kristan 2021-05-01 19:38:13 +02:00
parent 1a520f8782
commit 757172934e
10 changed files with 353 additions and 10 deletions

View File

@ -1,6 +1,6 @@
{
"name": "wled",
"version": "0.12.2-bl1",
"version": "0.12.2-bl2",
"description": "Tools for WLED project",
"main": "tools/cdata.js",
"directories": {

View File

@ -0,0 +1,51 @@
# Multi Relay
This usermod-v2 modification allows the connection of multiple relays each with individual delay and on/off mode.
## Usermod installation
1. Register the usermod by adding `#include "../usermods/multi_relay/usermod_multi_relay.h"` at the top and `usermods.add(new MultiRelay());` at the bottom of `usermods_list.cpp`.
or
2. Use `#define USERMOD_MULTI_RELAY` in wled.h or `-D USERMOD_MULTI_RELAY`in your platformio.ini
You can override the default maximum number (4) of relays by defining MULTI_RELAY_MAX_RELAYS.
Example **usermods_list.cpp**:
```cpp
#include "wled.h"
/*
* Register your v2 usermods here!
* (for v1 usermods using just usermod.cpp, you can ignore this file)
*/
/*
* Add/uncomment your usermod filename here (and once more below)
* || || ||
* \/ \/ \/
*/
//#include "usermod_v2_example.h"
//#include "usermod_temperature.h"
#include "../usermods/usermod_multi_relay.h"
void registerUsermods()
{
/*
* Add your usermod class name here
* || || ||
* \/ \/ \/
*/
//usermods.add(new MyExampleUsermod());
//usermods.add(new UsermodTemperature());
usermods.add(new MultiRelay());
}
```
## Configuration
Usermod can be configured in Usermods settings page.
If there is no MultiRelay section, just save current configuration and re-open Usermods settings page.
Have fun - @blazoncek

View File

@ -0,0 +1,278 @@
#pragma once
#include "wled.h"
#ifndef MULTI_RELAY_MAX_RELAYS
#define MULTI_RELAY_MAX_RELAYS 4
#endif
/*
* This usermod handles multiple relay outputs.
* These outputs complement built-in relay output in a way that the activation can be delayed.
* They can also activate/deactivate in reverse logic independently.
*/
class MultiRelay : public Usermod {
private:
// pins
int8_t _relayPin[MULTI_RELAY_MAX_RELAYS];
// delay (in seconds) before relay state changes
uint16_t _relayDelay[MULTI_RELAY_MAX_RELAYS];
// activation mode (high/low)
bool _relayMode[MULTI_RELAY_MAX_RELAYS];
// relay active state
bool _relayActive[MULTI_RELAY_MAX_RELAYS];
// switch timer start time
uint32_t _switchTimerStart = 0;
// old brightness
uint8_t _oldBrightness = 0;
// usermod enabled
bool enabled = false; // needs to be configured (no default config)
// status of initialisation
bool initDone = false;
// strings to reduce flash memory usage (used more than twice)
static const char _name[];
static const char _enabled[];
static const char _relay[];
static const char _delay[];
static const char _activeHigh[];
/**
* switch relay on/off
*/
void switchRelay(uint8_t relay) {
if (relay>=MULTI_RELAY_MAX_RELAYS || _relayPin[relay]<0) return;
pinMode(_relayPin[relay], OUTPUT);
bool mode = bri ? _relayMode[relay] : !_relayMode[relay];
digitalWrite(_relayPin[relay], mode);
publishMqtt(mode ? "on" : "off", relay);
}
void publishMqtt(const char* state, int relay) {
//Check if MQTT Connected, otherwise it will crash the 8266
if (WLED_MQTT_CONNECTED){
char subuf[64];
sprintf_P(subuf, PSTR("%s/relay/%d"), mqttDeviceTopic, relay);
mqtt->publish(subuf, 0, true, state);
}
}
/**
* switch off the strip if the delay has elapsed
*/
void handleOffTimer() {
bool activeRelays = false;
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
if (_relayActive[i] && _switchTimerStart > 0 && millis() - _switchTimerStart > (_relayDelay[i]*1000)) {
switchRelay(i); // toggle relay
_relayActive[i] = false;
}
activeRelays = activeRelays || _relayActive[i];
}
if (!activeRelays) _switchTimerStart = 0;
}
public:
/**
* constructor
*/
MultiRelay() {
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
_relayPin[i] = -1;
_relayMode[i] = false;
_relayDelay[i] = 0;
_relayActive[i] = false;
}
}
/**
* desctructor
*/
~MultiRelay() {}
/**
* Enable/Disable the usermod
*/
void enable(bool enable) { enabled = enable; }
/**
* Get usermod enabled/disabled state
*/
bool isEnabled() { return enabled; }
//Functions called by WLED
/**
* setup() is called once at boot. WiFi is not yet connected at this point.
* You can use it to initialize variables, sensors or similar.
*/
void setup() {
// pins retrieved from cfg.json (readFromConfig()) prior to running setup()
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
if (_relayPin[i]<0) continue;
if (!pinManager.allocatePin(_relayPin[i],true)) {
_relayPin[i] = -1; // allocation failed
} else {
switchRelay(i);
_relayActive[i] = false;
}
}
_oldBrightness = bri;
initDone = true;
}
/**
* connected() is called every time the WiFi is (re)connected
* Use it to initialize network interfaces
*/
void connected() {}
/**
* loop() is called continuously. Here you can check for events, read sensors, etc.
*/
void loop() {
if (!enabled) return;
static unsigned long lastUpdate = 0;
if (millis() - lastUpdate < 200) return; // update only 5 times/s
lastUpdate = millis();
//set relay when LEDs turn on
if (_oldBrightness != bri) {
_oldBrightness = bri;
_switchTimerStart = millis();
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
if (_relayPin[i]>=0) _relayActive[i] = true;
}
}
handleOffTimer();
}
/**
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
*/
void addToJsonInfo(JsonObject &root) {
if (enabled) {
uint8_t count = 0;
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) if (_relayPin[i]>=0) count++;
JsonObject user = root["u"];
if (user.isNull())
user = root.createNestedObject("u");
JsonArray infoArr = user.createNestedArray(F("Number of relays")); //name
infoArr.add(String(count));
}
}
/**
* addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object).
* Values in the state object may be modified by connected clients
*/
void addToJsonState(JsonObject &root) {
}
/**
* readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object).
* Values in the state object may be modified by connected clients
*/
void readFromJsonState(JsonObject &root) {
}
/**
* provide the changeable values
*/
void addToConfig(JsonObject &root) {
JsonObject top = root.createNestedObject(FPSTR(_name));
top[FPSTR(_enabled)] = enabled;
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
String parName = FPSTR(_relay); parName += "-"; parName += i; parName += "-";
top[parName+"pin"] = _relayPin[i];
top[parName+FPSTR(_activeHigh)] = _relayMode[i];
top[parName+FPSTR(_delay)] = _relayDelay[i];
}
DEBUG_PRINTLN(F("MultiRelay config saved."));
}
/**
* restore the changeable values
* readFromConfig() is called before setup() to populate properties from values stored in cfg.json
*/
void readFromConfig(JsonObject &root) {
int8_t oldPin[MULTI_RELAY_MAX_RELAYS];
JsonObject top = root[FPSTR(_name)];
if (top.isNull()) return;
if (top[FPSTR(_enabled)] != nullptr) {
if (top[FPSTR(_enabled)].is<bool>()) {
enabled = top[FPSTR(_enabled)].as<bool>(); // reading from cfg.json
} else {
// change from settings page
String str = top[FPSTR(_enabled)]; // checkbox -> off or on
enabled = (bool)(str!="off"); // off is guaranteed to be present
}
}
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
String parName = FPSTR(_relay); parName += "-"; parName += i; parName += "-";
oldPin[i] = _relayPin[i];
if (top[parName+"pin"] != nullptr) _relayPin[i] = min(39,max(-1,top[parName+"pin"].as<int>()));
if (top[parName+FPSTR(_activeHigh)] != nullptr) {
if (top[parName+FPSTR(_activeHigh)].is<bool>()) {
_relayMode[i] = top[parName+FPSTR(_activeHigh)].as<bool>(); // reading from cfg.json
} else {
// change from settings page
String str = top[parName+FPSTR(_activeHigh)]; // checkbox -> off or on
_relayMode[i] = (bool)(str!="off"); // off is guaranteed to be present
}
}
_relayDelay[i] = min(600,max(0,abs(top[parName+FPSTR(_delay)].as<int>())));
}
if (!initDone) {
// reading config prior to setup()
DEBUG_PRINTLN(F("MultiRelay config loaded."));
} else {
// deallocate all pins 1st
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++)
if (oldPin[i]>=0) {
pinManager.deallocatePin(oldPin[i]);
}
// allocate new pins
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
if (_relayPin[i]>=0 && pinManager.allocatePin(_relayPin[i],true)) {
switchRelay(i);
} else {
_relayPin[i] = -1;
}
_relayActive[i] = false;
}
DEBUG_PRINTLN(F("MultiRelay config (re)loaded."));
}
}
/**
* getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).
* This could be used in the future for the system to determine whether your usermod is installed.
*/
uint16_t getId()
{
return USERMOD_ID_MULTI_RELAY;
}
};
// strings to reduce flash memory usage (used more than twice)
const char MultiRelay::_name[] PROGMEM = "MultiRelay";
const char MultiRelay::_enabled[] PROGMEM = "enabled";
const char MultiRelay::_relay[] PROGMEM = "relay";
const char MultiRelay::_delay[] PROGMEM = "delay-s";
const char MultiRelay::_activeHigh[] PROGMEM = "active-high";

View File

@ -39,6 +39,7 @@
#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"
//Access point behavior
#define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot

View File

@ -27,11 +27,11 @@
ldS();
}
function check(o,k) {
var n = o.name.replace("[]","").substr(-4);
if (o.type=="number" && n.substr(0,4)=="_pin") {
var n = o.name.replace("[]","").substr(-3);
if (o.type=="number" && n.substr(0,3)=="pin") {
for (var i=0; i<pins.length; i++) {
if (k==pinO[i]) continue;
if (o.value==pins[i]) { o.style.color="red"; break; } else o.style.color="#fff";
if (o.value==pins[i] || o.value<-1 || o.value>39) { o.style.color="red"; break; } else o.style.color=o.value>33?"orange":"#fff";
}
}
}
@ -43,7 +43,7 @@
getPins(v);
continue;
}
if (k=="pin") {
if (k.replace("[]","").substr(-3)=="pin") {
if (Array.isArray(v)) {
for (var i=0; i<v.length; i++) if (v[i]>=0) { pins.push(v[i]); pinO.push(owner); }
} else {
@ -54,6 +54,7 @@
}
}
}
console.log(pins);
}
function addField(k,f,o,a=false) {
if (isO(o)) {

View File

@ -42,7 +42,7 @@ function B(){window.history.back()}function U(){document.getElementById("uf").st
.bt{background:#333;color:#fff;font-family:Verdana,sans-serif;border:.3ch solid #333;display:inline-block;font-size:20px;margin:8px;margin-top:12px}input[type=file]{font-size:16px}body{font-family:Verdana,sans-serif;text-align:center;background:#222;color:#fff;line-height:200%}#msg{display:none}
</style></head><body><h2>WLED Software Update</h2><form method="POST"
action="/update" id="uf" enctype="multipart/form-data" onsubmit="U()">
Installed version: 0.12.2-bl1<br>Download the latest binary: <a
Installed version: 0.12.2-bl2<br>Download the latest binary: <a
href="https://github.com/Aircoookie/WLED/releases" target="_blank"><img
src="https://img.shields.io/github/release/Aircoookie/WLED.svg?style=flat-square">
</a><br><input type="file" class="bt" name="update" accept=".bin" required><br>

View File

@ -381,7 +381,7 @@ HTTP traffic is unencrypted. An attacker in the same network can intercept form
<h3>Software Update</h3><button type="button" onclick="U()">Manual OTA Update
</button><br>Enable ArduinoOTA: <input type="checkbox" name="AO"><br><h3>About
</h3><a href="https://github.com/Aircoookie/WLED/" target="_blank">WLED</a>
version 0.12.2-bl1<br><br><a
version 0.12.2-bl2<br><br><a
href="https://github.com/Aircoookie/WLED/wiki/Contributors-and-credits"
target="_blank">Contributors, dependencies and special thanks</a><br>
A huge thank you to everyone who helped me create WLED!<br><br>
@ -395,7 +395,7 @@ type="submit">Save & Reboot</button></form></body></html>)=====";
// Autogenerated from wled00/data/settings_um.htm, do not edit!!
const char PAGE_settings_um[] PROGMEM = R"=====(<!DOCTYPE html><html><head lang="en"><meta charset="utf-8"><meta
name="viewport" content="width=500"><title>UI Settings</title><script>
var owner,locip,urows,d=document,umCfg={},pins=[6,7,8,9,10,11],pinO=["reserved","reserved","reserved","reserved","reserved","reserved"],loc=!1;function gId(e){return d.getElementById(e)}function isO(e){return e&&"object"==typeof e&&!Array.isArray(e)}function H(){window.open("https://github.com/Aircoookie/WLED/wiki/Settings#usermod-settings")}function B(){window.open("/settings","_self")}function S(){"file:"==window.location.protocol&&(loc=!0,(locip=localStorage.getItem("locIp"))||(locip=prompt("File Mode. Please enter WLED IP!"),localStorage.setItem("locIp",locip))),ldS()}function check(e,n){var o=e.name.replace("[]","").substr(-4);if("number"==e.type&&"_pin"==o.substr(0,4))for(var i=0;i<pins.length;i++)if(n!=pinO[i]){if(e.value==pins[i]){e.style.color="red";break}e.style.color="#fff"}}function getPins(e){if(isO(e))for(const[o,i]of Object.entries(e))if(isO(i))owner=o,getPins(i);else if("pin"==o)if(Array.isArray(i))for(var n=0;n<i.length;n++)i[n]>=0&&(pins.push(i[n]),pinO.push(owner));else i>=0&&(pins.push(i),pinO.push(owner));else if(Array.isArray(i))for(n=0;n<i.length;n++)getPins(i[n])}function addField(e,n,o,i=!1){if(isO(o))for(const[n,i]of Object.entries(o))addField(e,n,i);else if(Array.isArray(o))for(var t=0;t<o.length;t++)addField(e,n,o[t],!0);else{var r,s;switch(typeof o){case"boolean":r="checkbox",s=o?'checked value="on"':"";break;case"number":r="number",s=`value="${parseInt(o,10)}"`;break;case"string":default:r="text",s=`value="${o}"`}"checkbox"==r&&(urows+=`<input type="hidden" name="${e}_${n}${i?"[]":""}" value="off">`),urows+=`${n}: <input type="${r}" name="${e}_${n}${i?"[]":""}" ${s} oninput="check(this,'${e}')"><br>`}}function ldS(){fetch((loc?"http://"+locip:"")+"/cfg.json",{method:"get"}).then(e=>(e.ok||(gId("lserr").style.display="inline"),e.json())).then(e=>{if(umCfg=e.um,getPins(e),urows="",isO(umCfg)){for(const[e,n]of Object.entries(umCfg))urows+=`<hr><h3>${e}</h3>`,addField(e,"unknown",n);gId("um").innerHTML=urows}}).catch((function(e){gId("lserr").style.display="inline",console.log(e)}))}function svS(e){e.preventDefault(),console.log(d.Sf),d.Sf.checkValidity()&&d.Sf.submit()}function GetV(){}
var owner,locip,urows,d=document,umCfg={},pins=[6,7,8,9,10,11],pinO=["reserved","reserved","reserved","reserved","reserved","reserved"],loc=!1;function gId(e){return d.getElementById(e)}function isO(e){return e&&"object"==typeof e&&!Array.isArray(e)}function H(){window.open("https://github.com/Aircoookie/WLED/wiki/Settings#usermod-settings")}function B(){window.open("/settings","_self")}function S(){"file:"==window.location.protocol&&(loc=!0,(locip=localStorage.getItem("locIp"))||(locip=prompt("File Mode. Please enter WLED IP!"),localStorage.setItem("locIp",locip))),ldS()}function check(e,n){var o=e.name.replace("[]","").substr(-3);if("number"==e.type&&"pin"==o.substr(0,3))for(var i=0;i<pins.length;i++)if(n!=pinO[i]){if(e.value==pins[i]||e.value<-1||e.value>39){e.style.color="red";break}e.style.color=e.value>33?"orange":"#fff"}}function getPins(e){if(isO(e))for(const[o,i]of Object.entries(e))if(isO(i))owner=o,getPins(i);else if("pin"==o.replace("[]","").substr(-3))if(Array.isArray(i))for(var n=0;n<i.length;n++)i[n]>=0&&(pins.push(i[n]),pinO.push(owner));else i>=0&&(pins.push(i),pinO.push(owner));else if(Array.isArray(i))for(n=0;n<i.length;n++)getPins(i[n]);console.log(pins)}function addField(e,n,o,i=!1){if(isO(o))for(const[n,i]of Object.entries(o))addField(e,n,i);else if(Array.isArray(o))for(var r=0;r<o.length;r++)addField(e,n,o[r],!0);else{var t,s;switch(typeof o){case"boolean":t="checkbox",s=o?'checked value="on"':"";break;case"number":t="number",s=`value="${parseInt(o,10)}"`;break;case"string":default:t="text",s=`value="${o}"`}"checkbox"==t&&(urows+=`<input type="hidden" name="${e}_${n}${i?"[]":""}" value="off">`),urows+=`${n}: <input type="${t}" name="${e}_${n}${i?"[]":""}" ${s} oninput="check(this,'${e}')"><br>`}}function ldS(){fetch((loc?"http://"+locip:"")+"/cfg.json",{method:"get"}).then(e=>(e.ok||(gId("lserr").style.display="inline"),e.json())).then(e=>{if(umCfg=e.um,getPins(e),urows="",isO(umCfg)){for(const[e,n]of Object.entries(umCfg))urows+=`<hr><h3>${e}</h3>`,addField(e,"unknown",n);gId("um").innerHTML=urows}}).catch((function(e){gId("lserr").style.display="inline",console.log(e)}))}function svS(e){e.preventDefault(),console.log(d.Sf),d.Sf.checkValidity()&&d.Sf.submit()}function GetV(){}
</script>%CSS%%SCSS%</head><body onload="S()"><form
id="form_s" name="Sf" method="post" onsubmit="svS(event)"><div class="toprow">
<div class="helpB"><button type="button" onclick="H()">?</button></div><button

View File

@ -57,6 +57,10 @@
#include "../usermods/Animated_Staircase/Animated_Staircase.h"
#endif
#ifdef USERMOD_MULTI_RELAY
#include "../usermods/multi_relay/usermod_multi_relay.h"
#endif
void registerUsermods()
{
/*
@ -110,4 +114,8 @@ void registerUsermods()
#ifdef USERMOD_ANIMATED_STAIRCASE
usermods.add(new Animated_Staircase());
#endif
}
#ifdef USERMOD_MULTI_RELAY
usermods.add(new MultiRelay());
#endif
}

View File

@ -423,6 +423,10 @@ void WLED::setup()
#endif
// HTTP server page init
initServer();
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_DISABLE_BROWNOUT_DET)
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 1); //enable brownout detector
#endif
}
void WLED::beginStrip()

View File

@ -8,7 +8,7 @@
*/
// version code in format yymmddb (b = daily build)
#define VERSION 2104292
#define VERSION 2105011
//uncomment this if you have a "my_config.h" file you'd like to use
//#define WLED_USE_MY_CONFIG