#pragma once #include "wled.h" #ifndef MULTI_RELAY_MAX_RELAYS #define MULTI_RELAY_MAX_RELAYS 4 #endif #define ON true #define OFF false /* * 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. */ typedef struct relay_t { int8_t pin; bool active; bool mode; bool state; bool external; uint16_t delay; } Relay; class MultiRelay : public Usermod { private: // array of relays Relay _relay[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_str[]; static const char _delay_str[]; static const char _activeHigh[]; static const char _external[]; 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, false, state); } } /** * switch off the strip if the delay has elapsed */ void handleOffTimer() { bool activeRelays = false; for (uint8_t i=0; i 0 && millis() - _switchTimerStart > (_relay[i].delay*1000)) { if (!_relay[i].external) toggleRelay(i); _relay[i].active = false; } activeRelays = activeRelays || _relay[i].active; } if (!activeRelays) _switchTimerStart = 0; } /** * HTTP API handler * borrowed from: * https://github.com/gsieben/WLED/blob/master/usermods/GeoGab-Relays/usermod_GeoGab.h */ #define GEOGABVERSION "0.1.3" void InitHtmlAPIHandle() { // https://github.com/me-no-dev/ESPAsyncWebServer DEBUG_PRINTLN(F("Relays: Initialize HTML API")); server.on("/relays", HTTP_GET, [this](AsyncWebServerRequest *request) { DEBUG_PRINTLN("Relays: HTML API"); String janswer; String error = ""; //int params = request->params(); janswer = F("{\"NoOfRelays\":"); janswer += String(MULTI_RELAY_MAX_RELAYS) + ","; if (getActiveRelayCount()) { // Commands if(request->hasParam("switch")) { /**** Switch ****/ AsyncWebParameter* p = request->getParam("switch"); // Get Values for (int i=0; ivalue(), ',', i); if (value==-1) { error = F("There must be as much arugments as relays"); } else { // Switch if (_relay[i].external) switchRelay(i, (bool)value); } } } else if(request->hasParam("toggle")) { /**** Toggle ****/ AsyncWebParameter* p = request->getParam("toggle"); // Get Values for (int i=0;ivalue(), ',', i); if (value==-1) { error = F("There must be as mutch arugments as relays"); } else { // Toggle if (value && _relay[i].external) toggleRelay(i); } } } else { error = F("No valid command found"); } } else { error = F("No active relays"); } // Status response char sbuf[16]; for (int i=0; isend(200, "application/json", janswer); }); } int getValue(String data, char separator, int index) { int found = 0; int strIndex[] = {0, -1}; int maxIndex = data.length()-1; for(int i=0; i<=maxIndex && found<=index; i++){ if(data.charAt(i)==separator || i==maxIndex){ found++; strIndex[0] = strIndex[1]+1; strIndex[1] = (i == maxIndex) ? i+1 : i; } } return found>index ? data.substring(strIndex[0], strIndex[1]).toInt() : -1; } public: /** * constructor */ MultiRelay() { for (uint8_t i=0; i=MULTI_RELAY_MAX_RELAYS || _relay[relay].pin<0) return; _relay[relay].state = mode; pinMode(_relay[relay].pin, OUTPUT); digitalWrite(_relay[relay].pin, mode ? !_relay[relay].mode : _relay[relay].mode); publishMqtt(mode ? "on" : "off", relay); } /** * toggle relay */ inline void toggleRelay(uint8_t relay) { switchRelay(relay, !_relay[relay].state); } uint8_t getActiveRelayCount() { uint8_t count = 0; for (uint8_t i=0; i=0) count++; return count; } //Functions called by WLED /** * handling of MQTT message * topic only contains stripped topic (part after /wled/MAC) * topic should look like: /relay/X/command; where X is relay number, 0 based */ bool onMqttMessage(char* topic, char* payload) { if (strlen(topic) > 8 && strncmp_P(topic, PSTR("/relay/"), 7) == 0 && strncmp_P(topic+8, PSTR("/command"), 8) == 0) { uint8_t relay = strtoul(topic+7, NULL, 10); if (relaysubscribe(subuf, 0); } } /** * 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=0) _relay[i].active = 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) { JsonObject user = root["u"]; if (user.isNull()) user = root.createNestedObject("u"); JsonArray infoArr = user.createNestedArray(F("Number of relays")); //name infoArr.add(String(getActiveRelayCount())); } } /** * 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()) { enabled = top[FPSTR(_enabled)].as(); // 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())); if (top[parName+FPSTR(_activeHigh)] != nullptr) { if (top[parName+FPSTR(_activeHigh)].is()) { _relay[i].mode = top[parName+FPSTR(_activeHigh)].as(); // reading from cfg.json } else { // change from settings page String str = top[parName+FPSTR(_activeHigh)]; // checkbox -> off or on _relay[i].mode = (bool)(str!="off"); // off is guaranteed to be present } } if (top[parName+FPSTR(_external)] != nullptr) { if (top[parName+FPSTR(_external)].is()) { _relay[i].external = top[parName+FPSTR(_external)].as(); // reading from cfg.json } else { // change from settings page String str = top[parName+FPSTR(_external)]; // checkbox -> off or on _relay[i].external = (bool)(str!="off"); // off is guaranteed to be present } } _relay[i].delay = min(600,max(0,abs(top[parName+FPSTR(_delay_str)].as()))); } if (!initDone) { // reading config prior to setup() DEBUG_PRINTLN(F("MultiRelay config loaded.")); } else { // deallocate all pins 1st for (uint8_t i=0; i=0) { pinManager.deallocatePin(oldPin[i]); } // allocate new pins for (uint8_t i=0; i=0 && pinManager.allocatePin(_relay[i].pin,true)) { if (!_relay[i].external) switchRelay(i, _relay[i].state = (bool)bri); } else { _relay[i].pin = -1; } _relay[i].active = false; } DEBUG_PRINTLN(F("MultiRelay config (re)loaded.")); } return true; } /** * 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_str[] PROGMEM = "relay"; const char MultiRelay::_delay_str[] PROGMEM = "delay-s"; const char MultiRelay::_activeHigh[] PROGMEM = "active-high"; const char MultiRelay::_external[] PROGMEM = "external";