From 2a992f62332e583d9b09510e38ab7e4af5ad94f3 Mon Sep 17 00:00:00 2001 From: Eugene Schava Date: Sun, 3 Feb 2019 00:31:28 +0200 Subject: [PATCH 01/40] flow: initial commit --- code/espurna/config/general.h | 4 + code/espurna/espurna.ino | 3 + code/espurna/flow.ino | 159 ++++++++++++++++++++++++++++++++++ code/espurna/flow/component.h | 34 ++++++++ code/espurna/flow/generic.h | 32 +++++++ code/espurna/flow/library.h | 135 +++++++++++++++++++++++++++++ code/espurna/flow/mqtt.h | 56 ++++++++++++ code/espurna/web.ino | 92 ++++++++++++++++++++ 8 files changed, 515 insertions(+) create mode 100644 code/espurna/flow.ino create mode 100644 code/espurna/flow/component.h create mode 100644 code/espurna/flow/generic.h create mode 100644 code/espurna/flow/library.h create mode 100644 code/espurna/flow/mqtt.h diff --git a/code/espurna/config/general.h b/code/espurna/config/general.h index a62b0dac27..08bb4c56af 100644 --- a/code/espurna/config/general.h +++ b/code/espurna/config/general.h @@ -1487,3 +1487,7 @@ #ifndef RFM69_IS_RFM69HW #define RFM69_IS_RFM69HW 0 #endif + +#ifndef FLOW_SUPPORT +#define FLOW_SUPPORT 0 +#endif diff --git a/code/espurna/espurna.ino b/code/espurna/espurna.ino index 21a87a88d6..0c28b2a6de 100644 --- a/code/espurna/espurna.ino +++ b/code/espurna/espurna.ino @@ -189,6 +189,9 @@ void setup() { #if UART_MQTT_SUPPORT uartmqttSetup(); #endif + #if FLOW_SUPPORT + flowSetup(); + #endif // 3rd party code hook diff --git a/code/espurna/flow.ino b/code/espurna/flow.ino new file mode 100644 index 0000000000..0aeaccb5fd --- /dev/null +++ b/code/espurna/flow.ino @@ -0,0 +1,159 @@ +/* + +FLOW MODULE + +Copyright (C) 2016-2018 by Xose Pérez + +*/ + +#if FLOW_SUPPORT + +#include +#include + +#include "flow/library.h" +#include "flow/generic.h" + +#if MQTT_SUPPORT + #include "flow/mqtt.h" +#endif + +// ----------------------------------------------------------------------------- +// FLOW +// ----------------------------------------------------------------------------- + +FlowComponentLibrary _library; + +char* flowLoadData() { + File file = SPIFFS.open("/flow.json", "r"); // TODO: file name to constant + if (file) { + size_t size = file.size(); + uint8_t* nbuf = (uint8_t*) malloc(size + 1); + if (nbuf) { + size = file.read(nbuf, size); + file.close(); + nbuf[size] = 0; + return (char*)nbuf; + } + file.close(); + } else { + DEBUG_MSG("[FLOW] Error reading data\n"); + } + return 0; +} + +bool flowSaveData(char* data) { + bool result = false; + File file = SPIFFS.open("/flow.json", "w"); // TODO: file name to constant + if (file) { + result = file.print(data); + file.close(); + } else { + DEBUG_MSG("[FLOW] Error saving data\n"); + } + return result; +} + +void flowOutputLibrary(AsyncResponseStream *response) { + _library.toJSON(response); +} + +void flowStart() { + DEBUG_MSG("[FLOW] Starting\n"); + + File file = SPIFFS.open("/flow.json", "r"); // TODO: file name to constant + if (file) { + size_t size = file.size(); + uint8_t* nbuf = (uint8_t*) malloc(size + 1); + if (nbuf) { + size = file.read(nbuf, size); + file.close(); + nbuf[size] = 0; + + DynamicJsonBuffer jsonBuffer; + JsonObject& root = jsonBuffer.parseObject((char *) nbuf); + if (root.success()) flowStart(root); + else DEBUG_MSG("[FLOW] Error: Flow cannot be parsed as correct JSON\n"); + + free(nbuf); + } + file.close(); + } else { + DEBUG_MSG("[FLOW] No flow found\n"); + } +} + +void flowStart(JsonObject& data) { + std::map componentTypes; + std::map components; + + JsonObject& processes = data["processes"]; + for (auto process_kv: processes) { + String id = process_kv.key; + JsonVariant& value = process_kv.value; + String componentName = value["component"]; + FlowComponentType* componentType = _library.getType(componentName); + + if (componentType != NULL) { + JsonObject& metadata = value["metadata"]; + JsonObject& properties = metadata["properties"]; + FlowComponent* component = componentType->createComponent(properties); + + componentTypes[id] = componentType; + components[id] = component; + } else { + DEBUG_MSG_P(PSTR("[FLOW] Error: component '%s' is not registered\n"), componentName.c_str()); + } + } + + JsonArray& connections = data["connections"]; + for (JsonObject& connection: connections) { + JsonObject& src = connection["src"]; + String srcProcess = src["process"]; + String srcPort = src["port"]; + + JsonObject& tgt = connection["tgt"]; + String tgtProcess = tgt["process"]; + String tgtPort = tgt["port"]; + + FlowComponent* srcComponent = components[srcProcess]; + if (srcComponent == NULL) { + DEBUG_MSG_P(PSTR("[FLOW] Error: component ID='%s' is not registered\n"), srcProcess.c_str()); + continue; + } + + FlowComponent* tgtComponent = components[tgtProcess]; + if (tgtComponent == NULL) { + DEBUG_MSG_P(PSTR("[FLOW] Error: component ID='%s' is not registered\n"), tgtProcess.c_str()); + continue; + } + + FlowComponentType* srcComponentType = componentTypes[srcProcess]; + FlowComponentType* tgtComponentType = componentTypes[tgtProcess]; + + int srcNumber = srcComponentType->getOutputNumber(srcPort); + if (srcNumber < 0) { + DEBUG_MSG_P(PSTR("[FLOW] Error: component '%s' has no output named '%s'\n"), srcComponentType->name().c_str(), srcPort.c_str()); + continue; + } + + int tgtNumber = tgtComponentType->getInputNumber(tgtPort); + if (tgtNumber < 0) { + DEBUG_MSG_P(PSTR("[FLOW] Error: component '%s' has no input named '%s'\n"), tgtComponentType->name().c_str(), tgtPort.c_str()); + continue; + } + + srcComponent->addOutput(srcNumber, tgtComponent, tgtNumber); + } +} + +void flowSetup() { + flowRegisterGeneric(_library); + + #if MQTT_SUPPORT + flowRegisterMqtt(_library); + #endif + + flowStart(); +} +#endif // FLOW_SUPPORT diff --git a/code/espurna/flow/component.h b/code/espurna/flow/component.h new file mode 100644 index 0000000000..81813d2c57 --- /dev/null +++ b/code/espurna/flow/component.h @@ -0,0 +1,34 @@ +#pragma once + +#include + +class FlowComponent { + private: + typedef struct { + FlowComponent* component; + int inputNumber; + } output_t; + + std::vector> _outputs; + + protected: + void processOutput(JsonVariant& data, int outputNumber) { + if (outputNumber < _outputs.size()) { + for (output_t output : _outputs[outputNumber]) + output.component->processInput(data, output.inputNumber); + } + } + + public: + FlowComponent() { + } + + void addOutput(int outputNumber, FlowComponent* component, int inputNumber) { + if (outputNumber >= _outputs.size()) + _outputs.resize(outputNumber + 1); + _outputs[outputNumber].push_back({component, inputNumber}); + } + + virtual void processInput(JsonVariant& data, int inputNumber) { + } +}; \ No newline at end of file diff --git a/code/espurna/flow/generic.h b/code/espurna/flow/generic.h new file mode 100644 index 0000000000..9682a0f2ae --- /dev/null +++ b/code/espurna/flow/generic.h @@ -0,0 +1,32 @@ +class FlowDebugComponent : public FlowComponent { + private: + String _prefix; + public: + FlowDebugComponent(JsonObject& properties) { + const char * prefix = properties["Prefix"]; + _prefix = String(prefix != NULL ? prefix : ""); + } + + virtual void processInput(JsonVariant& data, int inputNumber) { + DEBUG_MSG_P("[FLOW DEBUG] %s%s\n", _prefix.c_str(), data.as()); + } +}; + +class FlowPauseComponent : public FlowComponent { + public: + FlowPauseComponent(JsonObject& properties) { + } +}; + +void flowRegisterGeneric(FlowComponentLibrary& library) { + library.addType("Debug", "eye", (flow_component_factory_f)([] (JsonObject& properties) { return new FlowDebugComponent(properties); })) + ->addInput("Data", ANY) + ->addProperty("Prefix", STRING) + ; + + library.addType("Delay", "pause", (flow_component_factory_f)([] (JsonObject& properties) { return new FlowPauseComponent(properties); })) + ->addInput("Payload", ANY) + ->addOutput("Payload", ANY) + ->addProperty("Time", INT) + ; +} \ No newline at end of file diff --git a/code/espurna/flow/library.h b/code/espurna/flow/library.h new file mode 100644 index 0000000000..2b6af38d47 --- /dev/null +++ b/code/espurna/flow/library.h @@ -0,0 +1,135 @@ +#pragma once + +#include "component.h" +#include + +typedef std::function flow_component_factory_f; + +enum FlowValueType { + ANY, + STRING, + INT, + DOUBLE, + BOOL +}; + +class FlowComponentType { + private: + typedef struct { + String name; + FlowValueType type; + } name_type_t; + + String _name; + String _icon; + flow_component_factory_f _factory; + std::vector _inputs; + std::vector _outputs; + std::vector _properties; + + void vectorToJSON(AsyncResponseStream *response, std::vector &v, const char* name) { + response->printf("\"%s\": [", name); + if (v.size() > 0) { + for (unsigned int i=0; i < v.size(); i++) { + if (i > 0) + response->print(","); + const char *typeName = "unknown"; + switch(v[i].type) { + case ANY: typeName = "any"; break; + case STRING: typeName = "string"; break; + case INT: typeName = "int"; break; + case DOUBLE: typeName = "double"; break; + case BOOL: typeName = "bool"; break; + } + response->printf("\n\t\t\t{\"name\": \"%s\", \"type\": \"%s\"}", v[i].name.c_str(), typeName); + } + } + response->print("]"); + } + + public: + FlowComponentType(String name, String icon, flow_component_factory_f factory) { + _name = name; + _icon = icon; + _factory = factory; + } + + String name() { + return _name; + } + + FlowComponentType* addInput(String name, FlowValueType type) { + _inputs.push_back({name, type}); + return this; + } + + FlowComponentType* addOutput(String name, FlowValueType type) { + _outputs.push_back({name, type}); + return this; + } + + FlowComponentType* addProperty(String name, FlowValueType type) { + _properties.push_back({name, type}); + return this; + } + + int getInputNumber(String& name) { + for (int i = 0; i < _inputs.size(); i++) { + if (_inputs[i].name.equalsIgnoreCase(name)) + return i; + } + return -1; + } + + int getOutputNumber(String& name) { + for (int i = 0; i < _outputs.size(); i++) { + if (_outputs[i].name.equalsIgnoreCase(name)) + return i; + } + return -1; + } + + void toJSON(AsyncResponseStream *response) { + response->print("{\n\t\t"); + response->printf("\"name\": \"%s\",\n\t\t", _name.c_str()); + response->printf("\"icon\": \"%s\",\n\t\t", _icon.c_str()); + vectorToJSON(response, _inputs, "inports"); + response->print(",\n\t\t"); + vectorToJSON(response, _outputs, "outports"); + response->print(",\n\t\t"); + vectorToJSON(response, _properties, "properties"); + response->print("}"); + } + + FlowComponent* createComponent(JsonObject& properties) { + return _factory(properties); + } +}; + +class FlowComponentLibrary { + private: + std::map _types; + + public: + FlowComponentType* addType(String name, String icon, flow_component_factory_f factory) { + FlowComponentType* type = new FlowComponentType(name, icon, factory); + _types[name] = type; + return type; + } + + void toJSON(AsyncResponseStream *response) { + response->print("{"); + bool first = true; + for (auto pair : _types) { + FlowComponentType* type = pair.second; + if (first) first = false; else response->print(","); + response->printf("\n\t\"%s\": ", type->name().c_str()); + type->toJSON(response); + } + response->print("}"); + } + + FlowComponentType* getType(String name) { + return _types[name]; + } +}; diff --git a/code/espurna/flow/mqtt.h b/code/espurna/flow/mqtt.h new file mode 100644 index 0000000000..52d8538d40 --- /dev/null +++ b/code/espurna/flow/mqtt.h @@ -0,0 +1,56 @@ +class FlowMqttSubscribeComponent : public FlowComponent { + private: + String _topic; + + void mqttCallback(unsigned int type, const char * topic, const char * payload) { + if (type == MQTT_CONNECT_EVENT) { + mqttSubscribeRaw(_topic.c_str()); + } + + if (type == MQTT_MESSAGE_EVENT) { + if (strcmp(topic, _topic.c_str()) == 0) { + JsonVariant data(payload); + processOutput(data, 0); + } + } + } + + public: + FlowMqttSubscribeComponent(JsonObject& properties) { + const char * topic = properties["Topic"]; + _topic = String(topic != NULL ? topic : ""); + + mqtt_callback_f callback = [this](unsigned int type, const char * topic, const char * payload){ this->mqttCallback(type, topic, payload); }; + mqttRegister(callback); + } +}; + +class FlowMqttPublishComponent : public FlowComponent { + private: + String _topic; + bool _retain; + + public: + FlowMqttPublishComponent(JsonObject& properties) { + const char * topic = properties["Topic"]; + _topic = String(topic != NULL ? topic : ""); + _retain = properties["Retain"]; + } + + virtual void processInput(JsonVariant& data, int inputNumber) { + mqttSendRaw(_topic.c_str(), data.as(), _retain); + } +}; + +void flowRegisterMqtt(FlowComponentLibrary& library) { + library.addType("MQTT subscribe", "sign-out", (flow_component_factory_f)([] (JsonObject& properties) { return new FlowMqttSubscribeComponent(properties); })) + ->addOutput("Payload", STRING) + ->addProperty("Topic", STRING) + ; + + library.addType("MQTT publish", "sign-in", (flow_component_factory_f)([] (JsonObject& properties) { return new FlowMqttPublishComponent(properties); })) + ->addInput("Payload", STRING) + ->addProperty("Topic", STRING) + ->addProperty("Retain", BOOL) + ; +} \ No newline at end of file diff --git a/code/espurna/web.ino b/code/espurna/web.ino index 30c10a7836..1c90aee125 100644 --- a/code/espurna/web.ino +++ b/code/espurna/web.ino @@ -354,6 +354,92 @@ void _onBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t i } +#if FLOW_SUPPORT +std::vector * _webFlowBuffer; +bool _webFlowSuccess = false; + +void _onGetFlowLibrary(AsyncWebServerRequest *request) { + + webLog(request); + if (!webAuthenticate(request)) { + return request->requestAuthentication(getSetting("hostname").c_str()); + } + + AsyncResponseStream *response = request->beginResponseStream("text/json"); + + response->addHeader("Content-Disposition", "attachment; filename=\"library.json\""); + response->addHeader("X-XSS-Protection", "1; mode=block"); + response->addHeader("X-Content-Type-Options", "nosniff"); + response->addHeader("X-Frame-Options", "deny"); + + flowOutputLibrary(response); + + request->send(response); +} + +void _onGetFlowConfig(AsyncWebServerRequest *request) { + + webLog(request); + if (!webAuthenticate(request)) { + return request->requestAuthentication(getSetting("hostname").c_str()); + } + + AsyncResponseStream *response = request->beginResponseStream("text/json"); + + response->addHeader("Content-Disposition", "attachment; filename=\"flow.json\""); + response->addHeader("X-XSS-Protection", "1; mode=block"); + response->addHeader("X-Content-Type-Options", "nosniff"); + response->addHeader("X-Frame-Options", "deny"); + + char* data = flowLoadData(); + if (data) { + response->print(data); + free(data); + } + + request->send(response); +} + +void _onPostFlowConfig(AsyncWebServerRequest *request) { + webLog(request); + if (!webAuthenticate(request)) { + return request->requestAuthentication(getSetting("hostname").c_str()); + } + request->send(_webFlowSuccess ? 200 : 400); +} + +void _onPostFlowConfigData(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { + // No buffer + if (final && (index == 0)) { + data[len] = 0; + _webFlowSuccess = flowSaveData((char *) data); + return; + } + + // Buffer start => reset + if (index == 0) if (_webFlowBuffer) delete _webFlowBuffer; + + // init buffer if it doesn't exist + if (!_webFlowBuffer) { + _webFlowBuffer = new std::vector(); + _webFlowSuccess = false; + } + + // Copy + if (len > 0) { + _webFlowBuffer->reserve(_webFlowBuffer->size() + len); + _webFlowBuffer->insert(_webFlowBuffer->end(), data, data + len); + } + + // Ending + if (final) { + _webFlowBuffer->push_back(0); + _webFlowSuccess = flowSaveData((char *) _webFlowBuffer->data()); + delete _webFlowBuffer; + } +} +#endif + // ----------------------------------------------------------------------------- @@ -418,6 +504,12 @@ void webSetup() { _server->on("/upgrade", HTTP_POST, _onUpgrade, _onUpgradeData); _server->on("/discover", HTTP_GET, _onDiscover); + #if FLOW_SUPPORT + _server->on("/flow_library", HTTP_GET, _onGetFlowLibrary); + _server->on("/flow", HTTP_GET, _onGetFlowConfig); + _server->on("/flow", HTTP_POST | HTTP_PUT, _onPostFlowConfig, _onPostFlowConfigData); + #endif + // Serve static files #if SPIFFS_SUPPORT _server->serveStatic("/", SPIFFS, "/") From 1c54ac75c53f0f3b72893460c9788ddc8e4837e9 Mon Sep 17 00:00:00 2001 From: Eugene Schava Date: Sun, 3 Feb 2019 12:47:37 +0200 Subject: [PATCH 02/40] flow: major refactoring + button component --- code/espurna/button.ino | 47 ++++++++++++++++++ code/espurna/espurna.ino | 11 ++++- code/espurna/{flow/library.h => flow.h} | 64 ++++++++++++++++++++++--- code/espurna/flow.ino | 55 +++++++++++++++------ code/espurna/flow/component.h | 34 ------------- code/espurna/flow/generic.h | 32 ------------- code/espurna/flow/mqtt.h | 56 ---------------------- code/espurna/mqtt.ino | 63 ++++++++++++++++++++++++ 8 files changed, 219 insertions(+), 143 deletions(-) rename code/espurna/{flow/library.h => flow.h} (67%) delete mode 100644 code/espurna/flow/component.h delete mode 100644 code/espurna/flow/generic.h delete mode 100644 code/espurna/flow/mqtt.h diff --git a/code/espurna/button.ino b/code/espurna/button.ino index 89e048e469..7e44940b1a 100644 --- a/code/espurna/button.ino +++ b/code/espurna/button.ino @@ -15,10 +15,18 @@ Copyright (C) 2016-2018 by Xose Pérez #include #include +#if FLOW_SUPPORT +#include "flow.h" +class FlowButtonComponent; // forward declaration +#endif + typedef struct { DebounceEvent * button; unsigned long actions; unsigned int relayID; + #if FLOW_SUPPORT + std::vector flow_components; + #endif } button_t; std::vector _buttons; @@ -42,6 +50,27 @@ bool _buttonWebSocketOnReceive(const char * key, JsonVariant& value) { #endif +// ----------------------------------------------------------------------------- +// FLOW +// ----------------------------------------------------------------------------- + +#if FLOW_SUPPORT + +class FlowButtonComponent : public FlowComponent { + public: + FlowButtonComponent(JsonObject& properties) { + int button_id = properties["Button"]; + _buttons[button_id].flow_components.push_back(this); + } + + void buttonEvent(unsigned char event) { + JsonVariant data((int)event); + processOutput(data, 0); + } +}; + +#endif // FLOW_SUPPORT + int buttonFromRelay(unsigned int relayID) { for (unsigned int i=0; i < _buttons.size(); i++) { if (_buttons[i].relayID == relayID) return i; @@ -105,6 +134,12 @@ void buttonEvent(unsigned int id, unsigned char event) { } #endif + #if FLOW_SUPPORT + for (FlowButtonComponent* component : _buttons[id].flow_components) { + component->buttonEvent(event); + } + #endif + if (action == BUTTON_MODE_TOGGLE) { if (_buttons[id].relayID > 0) { relayToggle(_buttons[id].relayID - 1); @@ -209,6 +244,18 @@ void buttonSetup() { wsOnReceiveRegister(_buttonWebSocketOnReceive); #endif + #if FLOW_SUPPORT + std::vector* buttons = new std::vector(); + for (unsigned int i=0; i < _buttons.size(); i++) { + buttons->push_back(String(i)); + } + + flowRegisterComponent("Button", "toggle-on", (flow_component_factory_f)([] (JsonObject& properties) { return new FlowButtonComponent(properties); })) + ->addOutput("Event", INT) + ->addProperty("Button", buttons) + ; + #endif + // Register loop espurnaRegisterLoop(buttonLoop); diff --git a/code/espurna/espurna.ino b/code/espurna/espurna.ino index 0c28b2a6de..ff31d81f24 100644 --- a/code/espurna/espurna.ino +++ b/code/espurna/espurna.ino @@ -22,6 +22,10 @@ along with this program. If not, see . #include "config/all.h" #include +#if FLOW_SUPPORT +#include "flow.h" +#endif + std::vector _loop_callbacks; std::vector _reload_callbacks; @@ -114,6 +118,11 @@ void setup() { apiSetup(); #endif + #if FLOW_SUPPORT + // register default flow components first + flowSetup(); + #endif + // lightSetup must be called before relaySetup #if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE lightSetup(); @@ -190,7 +199,7 @@ void setup() { uartmqttSetup(); #endif #if FLOW_SUPPORT - flowSetup(); + flowStart(); #endif diff --git a/code/espurna/flow/library.h b/code/espurna/flow.h similarity index 67% rename from code/espurna/flow/library.h rename to code/espurna/flow.h index 2b6af38d47..c0ffc94ce9 100644 --- a/code/espurna/flow/library.h +++ b/code/espurna/flow.h @@ -1,18 +1,50 @@ #pragma once -#include "component.h" +#include #include -typedef std::function flow_component_factory_f; - enum FlowValueType { ANY, STRING, INT, DOUBLE, - BOOL + BOOL, + LIST }; +class FlowComponent { + private: + typedef struct { + FlowComponent* component; + int inputNumber; + } output_t; + + std::vector> _outputs; + + protected: + void processOutput(JsonVariant& data, int outputNumber) { + if (outputNumber < _outputs.size()) { + for (output_t output : _outputs[outputNumber]) + output.component->processInput(data, output.inputNumber); + } + } + + public: + FlowComponent() { + } + + void addOutput(int outputNumber, FlowComponent* component, int inputNumber) { + if (outputNumber >= _outputs.size()) + _outputs.resize(outputNumber + 1); + _outputs[outputNumber].push_back({component, inputNumber}); + } + + virtual void processInput(JsonVariant& data, int inputNumber) { + } +}; + +typedef std::function flow_component_factory_f; + class FlowComponentType { private: typedef struct { @@ -26,6 +58,7 @@ class FlowComponentType { std::vector _inputs; std::vector _outputs; std::vector _properties; + std::map*> _list_values; void vectorToJSON(AsyncResponseStream *response, std::vector &v, const char* name) { response->printf("\"%s\": [", name); @@ -33,15 +66,28 @@ class FlowComponentType { for (unsigned int i=0; i < v.size(); i++) { if (i > 0) response->print(","); + FlowValueType type = v[i].type; const char *typeName = "unknown"; - switch(v[i].type) { + switch(type) { case ANY: typeName = "any"; break; case STRING: typeName = "string"; break; case INT: typeName = "int"; break; case DOUBLE: typeName = "double"; break; case BOOL: typeName = "bool"; break; + case LIST: typeName = "list"; break; + } + response->printf("\n\t\t\t{\"name\": \"%s\", \"type\": \"%s\"", v[i].name.c_str(), typeName); + if (type == LIST) { + std::vector* values = _list_values[v[i].name]; + response->print(", \"values\": ["); + for (unsigned int j=0; j < values->size(); j++) { + if (j > 0) + response->print(", "); + response->printf("\"%s\"", values->at(j).c_str()); + } + response->print("]"); } - response->printf("\n\t\t\t{\"name\": \"%s\", \"type\": \"%s\"}", v[i].name.c_str(), typeName); + response->print("}"); } } response->print("]"); @@ -73,6 +119,12 @@ class FlowComponentType { return this; } + FlowComponentType* addProperty(String name, std::vector* values) { + _properties.push_back({name, LIST}); + _list_values[name] = values; + return this; + } + int getInputNumber(String& name) { for (int i = 0; i < _inputs.size(); i++) { if (_inputs[i].name.equalsIgnoreCase(name)) diff --git a/code/espurna/flow.ino b/code/espurna/flow.ino index 0aeaccb5fd..32eb40d617 100644 --- a/code/espurna/flow.ino +++ b/code/espurna/flow.ino @@ -11,12 +11,7 @@ Copyright (C) 2016-2018 by Xose P #include #include -#include "flow/library.h" -#include "flow/generic.h" - -#if MQTT_SUPPORT - #include "flow/mqtt.h" -#endif +#include "flow.h" // ----------------------------------------------------------------------------- // FLOW @@ -24,6 +19,10 @@ Copyright (C) 2016-2018 by Xose P FlowComponentLibrary _library; +FlowComponentType* flowRegisterComponent(String name, String icon, flow_component_factory_f factory) { + return _library.addType(name, icon, factory); +} + char* flowLoadData() { File file = SPIFFS.open("/flow.json", "r"); // TODO: file name to constant if (file) { @@ -72,7 +71,7 @@ void flowStart() { DynamicJsonBuffer jsonBuffer; JsonObject& root = jsonBuffer.parseObject((char *) nbuf); - if (root.success()) flowStart(root); + if (root.success()) _flowStart(root); else DEBUG_MSG("[FLOW] Error: Flow cannot be parsed as correct JSON\n"); free(nbuf); @@ -83,7 +82,7 @@ void flowStart() { } } -void flowStart(JsonObject& data) { +void _flowStart(JsonObject& data) { std::map componentTypes; std::map components; @@ -147,13 +146,41 @@ void flowStart(JsonObject& data) { } } -void flowSetup() { - flowRegisterGeneric(_library); +class FlowDebugComponent : public FlowComponent { + private: + String _prefix; + public: + FlowDebugComponent(JsonObject& properties) { + const char * prefix = properties["Prefix"]; + _prefix = String(prefix != NULL ? prefix : ""); + } - #if MQTT_SUPPORT - flowRegisterMqtt(_library); - #endif + virtual void processInput(JsonVariant& data, int inputNumber) { + if (data.is()) { + DEBUG_MSG_P("[FLOW DEBUG] %s%i\n", _prefix.c_str(), data.as()); + } else { + DEBUG_MSG_P("[FLOW DEBUG] %s%s\n", _prefix.c_str(), data.as()); + } + } +}; + +class FlowPauseComponent : public FlowComponent { + public: + FlowPauseComponent(JsonObject& properties) { + } +}; - flowStart(); +void flowSetup() { + flowRegisterComponent("Debug", "eye", (flow_component_factory_f)([] (JsonObject& properties) { return new FlowDebugComponent(properties); })) + ->addInput("Data", ANY) + ->addProperty("Prefix", STRING) + ; + + flowRegisterComponent("Delay", "pause", (flow_component_factory_f)([] (JsonObject& properties) { return new FlowPauseComponent(properties); })) + ->addInput("Payload", ANY) + ->addOutput("Payload", ANY) + ->addProperty("Time", INT) + ; } + #endif // FLOW_SUPPORT diff --git a/code/espurna/flow/component.h b/code/espurna/flow/component.h deleted file mode 100644 index 81813d2c57..0000000000 --- a/code/espurna/flow/component.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#include - -class FlowComponent { - private: - typedef struct { - FlowComponent* component; - int inputNumber; - } output_t; - - std::vector> _outputs; - - protected: - void processOutput(JsonVariant& data, int outputNumber) { - if (outputNumber < _outputs.size()) { - for (output_t output : _outputs[outputNumber]) - output.component->processInput(data, output.inputNumber); - } - } - - public: - FlowComponent() { - } - - void addOutput(int outputNumber, FlowComponent* component, int inputNumber) { - if (outputNumber >= _outputs.size()) - _outputs.resize(outputNumber + 1); - _outputs[outputNumber].push_back({component, inputNumber}); - } - - virtual void processInput(JsonVariant& data, int inputNumber) { - } -}; \ No newline at end of file diff --git a/code/espurna/flow/generic.h b/code/espurna/flow/generic.h deleted file mode 100644 index 9682a0f2ae..0000000000 --- a/code/espurna/flow/generic.h +++ /dev/null @@ -1,32 +0,0 @@ -class FlowDebugComponent : public FlowComponent { - private: - String _prefix; - public: - FlowDebugComponent(JsonObject& properties) { - const char * prefix = properties["Prefix"]; - _prefix = String(prefix != NULL ? prefix : ""); - } - - virtual void processInput(JsonVariant& data, int inputNumber) { - DEBUG_MSG_P("[FLOW DEBUG] %s%s\n", _prefix.c_str(), data.as()); - } -}; - -class FlowPauseComponent : public FlowComponent { - public: - FlowPauseComponent(JsonObject& properties) { - } -}; - -void flowRegisterGeneric(FlowComponentLibrary& library) { - library.addType("Debug", "eye", (flow_component_factory_f)([] (JsonObject& properties) { return new FlowDebugComponent(properties); })) - ->addInput("Data", ANY) - ->addProperty("Prefix", STRING) - ; - - library.addType("Delay", "pause", (flow_component_factory_f)([] (JsonObject& properties) { return new FlowPauseComponent(properties); })) - ->addInput("Payload", ANY) - ->addOutput("Payload", ANY) - ->addProperty("Time", INT) - ; -} \ No newline at end of file diff --git a/code/espurna/flow/mqtt.h b/code/espurna/flow/mqtt.h deleted file mode 100644 index 52d8538d40..0000000000 --- a/code/espurna/flow/mqtt.h +++ /dev/null @@ -1,56 +0,0 @@ -class FlowMqttSubscribeComponent : public FlowComponent { - private: - String _topic; - - void mqttCallback(unsigned int type, const char * topic, const char * payload) { - if (type == MQTT_CONNECT_EVENT) { - mqttSubscribeRaw(_topic.c_str()); - } - - if (type == MQTT_MESSAGE_EVENT) { - if (strcmp(topic, _topic.c_str()) == 0) { - JsonVariant data(payload); - processOutput(data, 0); - } - } - } - - public: - FlowMqttSubscribeComponent(JsonObject& properties) { - const char * topic = properties["Topic"]; - _topic = String(topic != NULL ? topic : ""); - - mqtt_callback_f callback = [this](unsigned int type, const char * topic, const char * payload){ this->mqttCallback(type, topic, payload); }; - mqttRegister(callback); - } -}; - -class FlowMqttPublishComponent : public FlowComponent { - private: - String _topic; - bool _retain; - - public: - FlowMqttPublishComponent(JsonObject& properties) { - const char * topic = properties["Topic"]; - _topic = String(topic != NULL ? topic : ""); - _retain = properties["Retain"]; - } - - virtual void processInput(JsonVariant& data, int inputNumber) { - mqttSendRaw(_topic.c_str(), data.as(), _retain); - } -}; - -void flowRegisterMqtt(FlowComponentLibrary& library) { - library.addType("MQTT subscribe", "sign-out", (flow_component_factory_f)([] (JsonObject& properties) { return new FlowMqttSubscribeComponent(properties); })) - ->addOutput("Payload", STRING) - ->addProperty("Topic", STRING) - ; - - library.addType("MQTT publish", "sign-in", (flow_component_factory_f)([] (JsonObject& properties) { return new FlowMqttPublishComponent(properties); })) - ->addInput("Payload", STRING) - ->addProperty("Topic", STRING) - ->addProperty("Retain", BOOL) - ; -} \ No newline at end of file diff --git a/code/espurna/mqtt.ino b/code/espurna/mqtt.ino index 4c8f3e56e5..2ec187827f 100644 --- a/code/espurna/mqtt.ino +++ b/code/espurna/mqtt.ino @@ -360,6 +360,56 @@ void _mqttInitCommands() { #endif // TERMINAL_SUPPORT +// ----------------------------------------------------------------------------- +// FLOW +// ----------------------------------------------------------------------------- + +#if FLOW_SUPPORT +class FlowMqttSubscribeComponent : public FlowComponent { + private: + String _topic; + + void mqttCallback(unsigned int type, const char * topic, const char * payload) { + if (type == MQTT_CONNECT_EVENT) { + mqttSubscribeRaw(_topic.c_str()); + } + + if (type == MQTT_MESSAGE_EVENT) { + if (strcmp(topic, _topic.c_str()) == 0) { + JsonVariant data(payload); + processOutput(data, 0); + } + } + } + + public: + FlowMqttSubscribeComponent(JsonObject& properties) { + const char * topic = properties["Topic"]; + _topic = String(topic != NULL ? topic : ""); + + mqtt_callback_f callback = [this](unsigned int type, const char * topic, const char * payload){ this->mqttCallback(type, topic, payload); }; + mqttRegister(callback); + } +}; + +class FlowMqttPublishComponent : public FlowComponent { + private: + String _topic; + bool _retain; + + public: + FlowMqttPublishComponent(JsonObject& properties) { + const char * topic = properties["Topic"]; + _topic = String(topic != NULL ? topic : ""); + _retain = properties["Retain"]; + } + + virtual void processInput(JsonVariant& data, int inputNumber) { + mqttSendRaw(_topic.c_str(), data.as(), _retain); + } +}; +#endif //FLOW_SUPPORT + // ----------------------------------------------------------------------------- // MQTT Callbacks // ----------------------------------------------------------------------------- @@ -836,6 +886,19 @@ void mqttSetup() { _mqttInitCommands(); #endif + #if FLOW_SUPPORT + flowRegisterComponent("MQTT subscribe", "sign-out", (flow_component_factory_f)([] (JsonObject& properties) { return new FlowMqttSubscribeComponent(properties); })) + ->addOutput("Payload", STRING) + ->addProperty("Topic", STRING) + ; + + flowRegisterComponent("MQTT publish", "sign-in", (flow_component_factory_f)([] (JsonObject& properties) { return new FlowMqttPublishComponent(properties); })) + ->addInput("Payload", STRING) + ->addProperty("Topic", STRING) + ->addProperty("Retain", BOOL) + ; + #endif + // Main callbacks espurnaRegisterLoop(mqttLoop); espurnaRegisterReload(_mqttConfigure); From be7be8b79dcbf404b245073210b8116d06265172 Mon Sep 17 00:00:00 2001 From: Eugene Schava Date: Mon, 4 Feb 2019 13:08:22 +0200 Subject: [PATCH 03/40] flow: compilation errors --- code/espurna/config/prototypes.h | 13 +++++++++++++ code/espurna/espurna.ino | 4 ---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/code/espurna/config/prototypes.h b/code/espurna/config/prototypes.h index d029ac4684..eddf01f8aa 100644 --- a/code/espurna/config/prototypes.h +++ b/code/espurna/config/prototypes.h @@ -204,3 +204,16 @@ void webRequestRegister(web_request_callback_f callback); typedef std::function wifi_callback_f; void wifiRegister(wifi_callback_f callback); bool wifiConnected(); + +// ----------------------------------------------------------------------------- +// FLOW +// ----------------------------------------------------------------------------- + +#if FLOW_SUPPORT + class FlowComponent; + class FlowComponentType; + typedef std::function flow_component_factory_f; +#else + #define FlowComponentType void + #define flow_component_factory_f void * +#endif diff --git a/code/espurna/espurna.ino b/code/espurna/espurna.ino index ff31d81f24..fa601646d5 100644 --- a/code/espurna/espurna.ino +++ b/code/espurna/espurna.ino @@ -22,10 +22,6 @@ along with this program. If not, see . #include "config/all.h" #include -#if FLOW_SUPPORT -#include "flow.h" -#endif - std::vector _loop_callbacks; std::vector _reload_callbacks; From 05938cf18cded07b94a96a7764795c3525a83d6b Mon Sep 17 00:00:00 2001 From: Eugene Schava Date: Mon, 4 Feb 2019 21:48:58 +0200 Subject: [PATCH 04/40] flow: optimization: serve flow.json file right from the SPIFFS filesystem --- code/espurna/flow.ino | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/code/espurna/flow.ino b/code/espurna/flow.ino index 32eb40d617..1407710ebb 100644 --- a/code/espurna/flow.ino +++ b/code/espurna/flow.ino @@ -23,22 +23,8 @@ FlowComponentType* flowRegisterComponent(String name, String icon, flow_componen return _library.addType(name, icon, factory); } -char* flowLoadData() { - File file = SPIFFS.open("/flow.json", "r"); // TODO: file name to constant - if (file) { - size_t size = file.size(); - uint8_t* nbuf = (uint8_t*) malloc(size + 1); - if (nbuf) { - size = file.read(nbuf, size); - file.close(); - nbuf[size] = 0; - return (char*)nbuf; - } - file.close(); - } else { - DEBUG_MSG("[FLOW] Error reading data\n"); - } - return 0; +String flowGetDataPath() { + return "/flow.json"; // TODO: file name to constant } bool flowSaveData(char* data) { From faf5cce5501c7a563ef2c976560dc3ae3ec3c720 Mon Sep 17 00:00:00 2001 From: Eugene Schava Date: Mon, 4 Feb 2019 22:40:29 +0200 Subject: [PATCH 05/40] flow: optimization: serve flow.json file right from the SPIFFS filesystem --- code/espurna/web.ino | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/code/espurna/web.ino b/code/espurna/web.ino index 1c90aee125..cbc9052d46 100644 --- a/code/espurna/web.ino +++ b/code/espurna/web.ino @@ -384,19 +384,12 @@ void _onGetFlowConfig(AsyncWebServerRequest *request) { return request->requestAuthentication(getSetting("hostname").c_str()); } - AsyncResponseStream *response = request->beginResponseStream("text/json"); + AsyncWebServerResponse *response = request->beginResponse(SPIFFS, flowGetDataPath(), "text/json"); - response->addHeader("Content-Disposition", "attachment; filename=\"flow.json\""); response->addHeader("X-XSS-Protection", "1; mode=block"); response->addHeader("X-Content-Type-Options", "nosniff"); response->addHeader("X-Frame-Options", "deny"); - char* data = flowLoadData(); - if (data) { - response->print(data); - free(data); - } - request->send(response); } From e5795e80e764d6a896de7257bdb22520b82744a2 Mon Sep 17 00:00:00 2001 From: Eugene Schava Date: Mon, 4 Feb 2019 23:03:22 +0200 Subject: [PATCH 06/40] flow: relay component --- code/espurna/relay.ino | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/code/espurna/relay.ino b/code/espurna/relay.ino index 87514dfcd4..af938beda5 100644 --- a/code/espurna/relay.ino +++ b/code/espurna/relay.ino @@ -1016,6 +1016,33 @@ void _relayInitCommands() { #endif // TERMINAL_SUPPORT +// ----------------------------------------------------------------------------- +// FLOW +// ----------------------------------------------------------------------------- + +#if FLOW_SUPPORT + +class FlowRelayComponent : public FlowComponent { + private: + int _relay_id; + public: + FlowRelayComponent(JsonObject& properties) { + _relay_id = properties["Relay"]; + } + + virtual void processInput(JsonVariant& data, int inputNumber) { + if (inputNumber == 0) { // State + bool state = data.as(); + relayStatus(_relay_id, state); + } else { // Toggle + relayToggle(_relay_id); + } + } + +}; + +#endif // FLOW_SUPPORT + //------------------------------------------------------------------------------ // Setup //------------------------------------------------------------------------------ @@ -1077,6 +1104,19 @@ void relaySetup() { #if TERMINAL_SUPPORT _relayInitCommands(); #endif + #if FLOW_SUPPORT + std::vector* relays = new std::vector(); + for (unsigned int i=0; i < _relays.size(); i++) { + relays->push_back(String(i)); + } + + flowRegisterComponent("Relay", "lightbulb-o", (flow_component_factory_f)([] (JsonObject& properties) { return new FlowRelayComponent(properties); })) + ->addInput("State", BOOL) + ->addInput("Toggle", ANY) + ->addProperty("Relay", relays) + ; + #endif + // Main callbacks espurnaRegisterLoop(_relayLoop); From ef966a146c5fec1561012a5214fcef65afff05fb Mon Sep 17 00:00:00 2001 From: Eugene Schava Date: Tue, 5 Feb 2019 22:13:41 +0200 Subject: [PATCH 07/40] flow: topic placeholders in MQTT components --- code/espurna/mqtt.ino | 3 +++ 1 file changed, 3 insertions(+) diff --git a/code/espurna/mqtt.ino b/code/espurna/mqtt.ino index 2ec187827f..628c273b21 100644 --- a/code/espurna/mqtt.ino +++ b/code/espurna/mqtt.ino @@ -386,6 +386,7 @@ class FlowMqttSubscribeComponent : public FlowComponent { FlowMqttSubscribeComponent(JsonObject& properties) { const char * topic = properties["Topic"]; _topic = String(topic != NULL ? topic : ""); + _mqttPlaceholders(&_topic); mqtt_callback_f callback = [this](unsigned int type, const char * topic, const char * payload){ this->mqttCallback(type, topic, payload); }; mqttRegister(callback); @@ -401,6 +402,8 @@ class FlowMqttPublishComponent : public FlowComponent { FlowMqttPublishComponent(JsonObject& properties) { const char * topic = properties["Topic"]; _topic = String(topic != NULL ? topic : ""); + _mqttPlaceholders(&_topic); + _retain = properties["Retain"]; } From d1cebaace18973f66b8e7ad8e25eb40ae79cf7c0 Mon Sep 17 00:00:00 2001 From: Eugene Schava Date: Wed, 6 Feb 2019 00:25:07 +0200 Subject: [PATCH 08/40] flow: delay component --- code/espurna/flow.h | 17 ++++++++++++ code/espurna/flow.ino | 63 ++++++++++++++++++++++++++++++++++++++----- 2 files changed, 74 insertions(+), 6 deletions(-) diff --git a/code/espurna/flow.h b/code/espurna/flow.h index c0ffc94ce9..f27dc368a0 100644 --- a/code/espurna/flow.h +++ b/code/espurna/flow.h @@ -29,6 +29,23 @@ class FlowComponent { } } + JsonVariant* clone(JsonVariant& data) { + if (data.is()) { + char *str = strdup(data.as()); + return new JsonVariant(str); + } else { + return new JsonVariant(data); + } + } + + void release(JsonVariant* data) { + if (data->is()) { + void* str = (void*)data->as(); + free(str); + } + delete data; + } + public: FlowComponent() { } diff --git a/code/espurna/flow.ino b/code/espurna/flow.ino index 1407710ebb..186eff04fc 100644 --- a/code/espurna/flow.ino +++ b/code/espurna/flow.ino @@ -10,6 +10,7 @@ Copyright (C) 2016-2018 by Xose P #include #include +#include #include "flow.h" @@ -150,23 +151,73 @@ class FlowDebugComponent : public FlowComponent { } }; -class FlowPauseComponent : public FlowComponent { +class FlowDelayComponent; + +struct delay_queue_element_t { + JsonVariant *data; + unsigned long time; + FlowDelayComponent *component; + + bool operator() (const delay_queue_element_t& lhs, const delay_queue_element_t& rhs) const { + return lhs.time < rhs.time; + } +}; + +std::set _delay_queue; + +class FlowDelayComponent : public FlowComponent { + private: + long _time; + bool _lastOnly; + int _queueSize = 0; + public: - FlowPauseComponent(JsonObject& properties) { + FlowDelayComponent(JsonObject& properties) { + _time = 1000 * (int)properties["Seconds"]; + _lastOnly = properties["Last only"]; + } + + virtual void processInput(JsonVariant& data, int inputNumber) { + _delay_queue.insert({clone(data), millis() + _time, this}); + _queueSize++; + } + + void processElement(JsonVariant *data) { + if (!_lastOnly || _queueSize == 1) + processOutput(*data, 0); + + _queueSize--; + release(data); } }; +void _delayComponentLoop() { + if (!_delay_queue.empty()) { + auto it = _delay_queue.begin(); + const delay_queue_element_t element = *it; + if (element.time <= millis()) { + element.component->processElement(element.data); + _delay_queue.erase(it); + } + } +} + void flowSetup() { flowRegisterComponent("Debug", "eye", (flow_component_factory_f)([] (JsonObject& properties) { return new FlowDebugComponent(properties); })) ->addInput("Data", ANY) ->addProperty("Prefix", STRING) ; - flowRegisterComponent("Delay", "pause", (flow_component_factory_f)([] (JsonObject& properties) { return new FlowPauseComponent(properties); })) - ->addInput("Payload", ANY) - ->addOutput("Payload", ANY) - ->addProperty("Time", INT) + flowRegisterComponent("Delay", "pause", (flow_component_factory_f)([] (JsonObject& properties) { return new FlowDelayComponent(properties); })) + ->addInput("Data", ANY) + ->addOutput("Data", ANY) + ->addProperty("Seconds", INT) + ->addProperty("Last only", BOOL) ; + + // TODO: each component should have its own loop lambda function, in this case deque instead of set could be used + // for delay elements container + espurnaRegisterLoop(_delayComponentLoop); } #endif // FLOW_SUPPORT From 70f0d05c1ba4cfa76fe6efeabf1f3c6981f2eaa6 Mon Sep 17 00:00:00 2001 From: Eugene Schava Date: Thu, 7 Feb 2019 23:10:04 +0200 Subject: [PATCH 09/40] flow: change component --- code/espurna/flow.h | 13 ++++++++++++- code/espurna/flow.ino | 36 ++++++++++++++++++++++++++++++++++-- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/code/espurna/flow.h b/code/espurna/flow.h index f27dc368a0..73da0fdd95 100644 --- a/code/espurna/flow.h +++ b/code/espurna/flow.h @@ -30,7 +30,15 @@ class FlowComponent { } JsonVariant* clone(JsonVariant& data) { - if (data.is()) { + if (data == NULL) { + return new JsonVariant(false); // workaround for JSON parsing issue + } else if (data.is()) { + return new JsonVariant(data.as()); + } else if (data.is()) { + return new JsonVariant(data.as()); + } else if (data.is()) { + return new JsonVariant(data.as()); + } else if (data.is()) { char *str = strdup(data.as()); return new JsonVariant(str); } else { @@ -39,6 +47,9 @@ class FlowComponent { } void release(JsonVariant* data) { + if (data == NULL) + return; + if (data->is()) { void* str = (void*)data->as(); free(str); diff --git a/code/espurna/flow.ino b/code/espurna/flow.ino index 186eff04fc..f925315013 100644 --- a/code/espurna/flow.ino +++ b/code/espurna/flow.ino @@ -136,6 +136,7 @@ void _flowStart(JsonObject& data) { class FlowDebugComponent : public FlowComponent { private: String _prefix; + public: FlowDebugComponent(JsonObject& properties) { const char * prefix = properties["Prefix"]; @@ -143,14 +144,39 @@ class FlowDebugComponent : public FlowComponent { } virtual void processInput(JsonVariant& data, int inputNumber) { - if (data.is()) { + if (data == NULL) { + DEBUG_MSG_P("[FLOW DEBUG] %sEMPTY\n", _prefix.c_str()); + } else if (data.is()) { DEBUG_MSG_P("[FLOW DEBUG] %s%i\n", _prefix.c_str(), data.as()); - } else { + } else if (data.is()) { + char buffer[64]; + dtostrf(data.as(), 1 - sizeof(buffer), 3, buffer); + DEBUG_MSG_P("[FLOW DEBUG] %s%s\n", _prefix.c_str(), buffer); + } else if (data.is()) { + DEBUG_MSG_P("[FLOW DEBUG] %s%s\n", _prefix.c_str(), data.as() ? "true" : "false"); + } else if (data.is()) { DEBUG_MSG_P("[FLOW DEBUG] %s%s\n", _prefix.c_str(), data.as()); + } else { + DEBUG_MSG_P("[FLOW DEBUG] %sUNKNOWN\n", _prefix.c_str()); } } }; +class FlowChangeComponent : public FlowComponent { + private: + JsonVariant* _value; + + public: + FlowChangeComponent(JsonObject& properties) { + JsonVariant value = properties["Value"]; + _value = clone(value); + } + + virtual void processInput(JsonVariant& data, int inputNumber) { + processOutput(*_value, 0); + } +}; + class FlowDelayComponent; struct delay_queue_element_t { @@ -208,6 +234,12 @@ void flowSetup() { ->addProperty("Prefix", STRING) ; + flowRegisterComponent("Change", "edit", (flow_component_factory_f)([] (JsonObject& properties) { return new FlowChangeComponent(properties); })) + ->addInput("Data", ANY) + ->addOutput("Data", ANY) + ->addProperty("Value", ANY) + ; + flowRegisterComponent("Delay", "pause", (flow_component_factory_f)([] (JsonObject& properties) { return new FlowDelayComponent(properties); })) ->addInput("Data", ANY) ->addOutput("Data", ANY) From 49e4c7556dfe0765c3e0929b2c749dfd5844b371 Mon Sep 17 00:00:00 2001 From: Eugene Schava Date: Fri, 8 Feb 2019 01:32:49 +0200 Subject: [PATCH 10/40] flow: timer and schedule components --- code/espurna/button.ino | 20 +++++++-------- code/espurna/flow.h | 6 ++++- code/espurna/flow.ino | 45 +++++++++++++++++++++++++++++++-- code/espurna/scheduler.ino | 51 ++++++++++++++++++++++++++++++++++++++ code/espurna/web.ino | 2 +- 5 files changed, 110 insertions(+), 14 deletions(-) diff --git a/code/espurna/button.ino b/code/espurna/button.ino index 7e44940b1a..ba3c20014b 100644 --- a/code/espurna/button.ino +++ b/code/espurna/button.ino @@ -57,16 +57,16 @@ bool _buttonWebSocketOnReceive(const char * key, JsonVariant& value) { #if FLOW_SUPPORT class FlowButtonComponent : public FlowComponent { - public: - FlowButtonComponent(JsonObject& properties) { - int button_id = properties["Button"]; - _buttons[button_id].flow_components.push_back(this); - } - - void buttonEvent(unsigned char event) { - JsonVariant data((int)event); - processOutput(data, 0); - } + public: + FlowButtonComponent(JsonObject& properties) { + int button_id = properties["Button"]; + _buttons[button_id].flow_components.push_back(this); + } + + void buttonEvent(unsigned char event) { + JsonVariant data((int)event); + processOutput(data, 0); + } }; #endif // FLOW_SUPPORT diff --git a/code/espurna/flow.h b/code/espurna/flow.h index 73da0fdd95..1fa4a0f516 100644 --- a/code/espurna/flow.h +++ b/code/espurna/flow.h @@ -9,7 +9,9 @@ enum FlowValueType { INT, DOUBLE, BOOL, - LIST + LIST, + TIME, + WEEKDAYS }; class FlowComponent { @@ -103,6 +105,8 @@ class FlowComponentType { case DOUBLE: typeName = "double"; break; case BOOL: typeName = "bool"; break; case LIST: typeName = "list"; break; + case TIME: typeName = "time"; break; + case WEEKDAYS: typeName = "weekdays"; break; } response->printf("\n\t\t\t{\"name\": \"%s\", \"type\": \"%s\"", v[i].name.c_str(), typeName); if (type == LIST) { diff --git a/code/espurna/flow.ino b/code/espurna/flow.ino index f925315013..62bdffa868 100644 --- a/code/espurna/flow.ino +++ b/code/espurna/flow.ino @@ -217,7 +217,37 @@ class FlowDelayComponent : public FlowComponent { } }; -void _delayComponentLoop() { +class FlowTimerComponent; +std::vector _timer_components; + +class FlowTimerComponent : public FlowComponent { + private: + JsonVariant *_data = new JsonVariant(true); + long _period; + long _lastMillis; + + public: + FlowTimerComponent(JsonObject& properties) { + int seconds = properties["Seconds"]; + _period = 1000 * (int)seconds; + _lastMillis = millis(); + + if (_period > 0) + _timer_components.push_back(this); + else + DEBUG_MSG_P("[FLOW] Incorrect timer delay: %i\n", seconds); + } + + void check() { + long now = millis(); + if (now >= _lastMillis + _period) { + processOutput(*_data, 0); + _lastMillis = now; + } + } +}; + +void _flowComponentLoop() { if (!_delay_queue.empty()) { auto it = _delay_queue.begin(); const delay_queue_element_t element = *it; @@ -226,6 +256,12 @@ void _delayComponentLoop() { _delay_queue.erase(it); } } + + if (!_timer_components.empty()) { + for (unsigned int i=0; i < _timer_components.size(); i++) { + _timer_components[i]->check(); + } + } } void flowSetup() { @@ -247,9 +283,14 @@ void flowSetup() { ->addProperty("Last only", BOOL) ; + flowRegisterComponent("Timer", "clock-o", (flow_component_factory_f)([] (JsonObject& properties) { return new FlowTimerComponent(properties); })) + ->addOutput("Data", ANY) + ->addProperty("Seconds", INT) + ; + // TODO: each component should have its own loop lambda function, in this case deque instead of set could be used // for delay elements container - espurnaRegisterLoop(_delayComponentLoop); + espurnaRegisterLoop(_flowComponentLoop); } #endif // FLOW_SUPPORT diff --git a/code/espurna/scheduler.ino b/code/espurna/scheduler.ino index 28bce0c4f0..526165dafe 100644 --- a/code/espurna/scheduler.ino +++ b/code/espurna/scheduler.ino @@ -45,6 +45,44 @@ void _schWebSocketOnSend(JsonObject &root){ #endif // WEB_SUPPORT +// ----------------------------------------------------------------------------- +// FLOW +// ----------------------------------------------------------------------------- + +#if FLOW_SUPPORT + +class FlowScheduleComponent; +std::vector _schedule_components; + +class FlowScheduleComponent : public FlowComponent { + private: + JsonVariant *_data = new JsonVariant(true); + String _weekdays; + int _hour; + int _minute; + + public: + FlowScheduleComponent(JsonObject& properties) { + _weekdays = String((const char *)properties["Weekdays"]); + String time = String((const char *)properties["Time"]); + int colon = time.indexOf(":"); + if (colon > 0) { + _hour = time.substring(0, colon).toInt(); + _minute = time.substring(colon + 1).toInt(); + } + + _schedule_components.push_back(this); + } + + void check(time_t& time) { + if (_schIsThisWeekday(time, _weekdays) && _schMinutesLeft(time, _hour, _minute) == 0) { + processOutput(*_data, 0); + } + } +}; + +#endif // FLOW_SUPPORT + // ----------------------------------------------------------------------------- void _schConfigure() { @@ -189,6 +227,11 @@ void _schCheck() { } + #if FLOW_SUPPORT + for (unsigned int i=0; i < _schedule_components.size(); i++) { + _schedule_components[i]->check(local_time); + } + #endif } void _schLoop() { @@ -218,6 +261,14 @@ void schSetup() { wsOnReceiveRegister(_schWebSocketOnReceive); #endif + #if FLOW_SUPPORT + flowRegisterComponent("Schedule", "calendar", (flow_component_factory_f)([] (JsonObject& properties) { return new FlowScheduleComponent(properties); })) + ->addOutput("Event", BOOL) + ->addProperty("Time", TIME) + ->addProperty("Weekdays", WEEKDAYS) + ; + #endif + // Main callbacks espurnaRegisterLoop(_schLoop); espurnaRegisterReload(_schConfigure); diff --git a/code/espurna/web.ino b/code/espurna/web.ino index cbc9052d46..c4cdd8f556 100644 --- a/code/espurna/web.ino +++ b/code/espurna/web.ino @@ -367,7 +367,7 @@ void _onGetFlowLibrary(AsyncWebServerRequest *request) { AsyncResponseStream *response = request->beginResponseStream("text/json"); - response->addHeader("Content-Disposition", "attachment; filename=\"library.json\""); + response->addHeader("Content-Disposition", "inline; filename=\"library.json\""); response->addHeader("X-XSS-Protection", "1; mode=block"); response->addHeader("X-Content-Type-Options", "nosniff"); response->addHeader("X-Frame-Options", "deny"); From 4f759c4d180cd3ffaf0d9468878d6fd98b0cd635 Mon Sep 17 00:00:00 2001 From: Eugene Schava Date: Fri, 8 Feb 2019 17:26:28 +0200 Subject: [PATCH 11/40] flow: chunked response to avoid out of memory for big number of components --- code/espurna/flow.h | 94 ++++++++++++++++++++----------------------- code/espurna/flow.ino | 4 +- code/espurna/web.ino | 43 ++++++++++++++++++-- 3 files changed, 86 insertions(+), 55 deletions(-) diff --git a/code/espurna/flow.h b/code/espurna/flow.h index 1fa4a0f516..143debf2ef 100644 --- a/code/espurna/flow.h +++ b/code/espurna/flow.h @@ -90,39 +90,35 @@ class FlowComponentType { std::vector _properties; std::map*> _list_values; - void vectorToJSON(AsyncResponseStream *response, std::vector &v, const char* name) { - response->printf("\"%s\": [", name); - if (v.size() > 0) { - for (unsigned int i=0; i < v.size(); i++) { - if (i > 0) - response->print(","); - FlowValueType type = v[i].type; - const char *typeName = "unknown"; - switch(type) { - case ANY: typeName = "any"; break; - case STRING: typeName = "string"; break; - case INT: typeName = "int"; break; - case DOUBLE: typeName = "double"; break; - case BOOL: typeName = "bool"; break; - case LIST: typeName = "list"; break; - case TIME: typeName = "time"; break; - case WEEKDAYS: typeName = "weekdays"; break; - } - response->printf("\n\t\t\t{\"name\": \"%s\", \"type\": \"%s\"", v[i].name.c_str(), typeName); - if (type == LIST) { - std::vector* values = _list_values[v[i].name]; - response->print(", \"values\": ["); - for (unsigned int j=0; j < values->size(); j++) { - if (j > 0) - response->print(", "); - response->printf("\"%s\"", values->at(j).c_str()); - } - response->print("]"); + void vectorToJSON(JsonObject& root, std::vector &v, const char* name) { + JsonArray& array = root.createNestedArray(name); + for (unsigned int i=0; i < v.size(); i++) { + name_type_t& element = v[i]; + JsonObject& elementObject = array.createNestedObject(); + elementObject["name"] = element.name; + + FlowValueType type = element.type; + const char *typeName = "unknown"; + switch(type) { + case ANY: typeName = "any"; break; + case STRING: typeName = "string"; break; + case INT: typeName = "int"; break; + case DOUBLE: typeName = "double"; break; + case BOOL: typeName = "bool"; break; + case LIST: typeName = "list"; break; + case TIME: typeName = "time"; break; + case WEEKDAYS: typeName = "weekdays"; break; + } + elementObject["type"] = typeName; + + if (type == LIST) { + std::vector* values = _list_values[element.name]; + JsonArray& valuesArray = elementObject.createNestedArray("values"); + for (unsigned int j=0; j < values->size(); j++) { + valuesArray.add(values->at(j)); } - response->print("}"); } } - response->print("]"); } public: @@ -173,16 +169,13 @@ class FlowComponentType { return -1; } - void toJSON(AsyncResponseStream *response) { - response->print("{\n\t\t"); - response->printf("\"name\": \"%s\",\n\t\t", _name.c_str()); - response->printf("\"icon\": \"%s\",\n\t\t", _icon.c_str()); - vectorToJSON(response, _inputs, "inports"); - response->print(",\n\t\t"); - vectorToJSON(response, _outputs, "outports"); - response->print(",\n\t\t"); - vectorToJSON(response, _properties, "properties"); - response->print("}"); + void toJSON(JsonObject& root) { + root["name"] = _name; + root["icon"] = _icon; + + vectorToJSON(root, _inputs, "inports"); + vectorToJSON(root, _outputs, "outports"); + vectorToJSON(root, _properties, "properties"); } FlowComponent* createComponent(JsonObject& properties) { @@ -201,19 +194,20 @@ class FlowComponentLibrary { return type; } - void toJSON(AsyncResponseStream *response) { - response->print("{"); - bool first = true; - for (auto pair : _types) { - FlowComponentType* type = pair.second; - if (first) first = false; else response->print(","); - response->printf("\n\t\"%s\": ", type->name().c_str()); - type->toJSON(response); + FlowComponentType* getComponent(int index) { + auto end = _types.end(); + + int counter = 0; + for (auto const& pair : _types) { + if (counter++ == index) + return pair.second; } - response->print("}"); + return NULL; } FlowComponentType* getType(String name) { - return _types[name]; + auto it = _types.find(name); + if (it == _types.end()) return NULL; + return it->second; } }; diff --git a/code/espurna/flow.ino b/code/espurna/flow.ino index 62bdffa868..7ed1f4305c 100644 --- a/code/espurna/flow.ino +++ b/code/espurna/flow.ino @@ -40,8 +40,8 @@ bool flowSaveData(char* data) { return result; } -void flowOutputLibrary(AsyncResponseStream *response) { - _library.toJSON(response); +FlowComponentType* flowGetComponent(int index) { + return _library.getComponent(index); } void flowStart() { diff --git a/code/espurna/web.ino b/code/espurna/web.ino index c4cdd8f556..348a0d2001 100644 --- a/code/espurna/web.ino +++ b/code/espurna/web.ino @@ -365,15 +365,52 @@ void _onGetFlowLibrary(AsyncWebServerRequest *request) { return request->requestAuthentication(getSetting("hostname").c_str()); } - AsyncResponseStream *response = request->beginResponseStream("text/json"); + // chunked response to avoid out of memory for big number of components + int* ip = new int(0); // index of component in loop + AsyncWebServerResponse *response = request->beginChunkedResponse("text/json", [ip](uint8_t *buffer, size_t maxLen, size_t index) -> size_t { + size_t size = 0; + int i = *ip; + + if (i < 0) { // no more components left + delete ip; + return size; + } + + if (i == 0) { + buffer[0] = '{'; + buffer++; size++; maxLen--; + } + + FlowComponentType* component = flowGetComponent(i); + if (component != NULL) { + if (i > 0) { + buffer[0] = ','; + buffer++; size++; maxLen--; + } + + int count = sprintf((char*)buffer, "\"%s\": ", component->name().c_str()); + size += count; maxLen -= count; buffer += count; + + DynamicJsonBuffer jsonBuffer; + JsonObject& root = jsonBuffer.createObject(); + component->toJSON(root); + size += root.printTo((char*)buffer, maxLen); + + i++; + } else { + buffer[0] = '}'; + size++; + i = -1; // last chunk + } + *ip = i; + return size; + }); response->addHeader("Content-Disposition", "inline; filename=\"library.json\""); response->addHeader("X-XSS-Protection", "1; mode=block"); response->addHeader("X-Content-Type-Options", "nosniff"); response->addHeader("X-Frame-Options", "deny"); - flowOutputLibrary(response); - request->send(response); } From ed7a6471e9a46b0c5a16fa7ea7a8e2cb78fce715 Mon Sep 17 00:00:00 2001 From: Eugene Schava Date: Sat, 9 Feb 2019 09:56:41 +0200 Subject: [PATCH 12/40] flow: start component --- code/espurna/flow.ino | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/code/espurna/flow.ino b/code/espurna/flow.ino index 7ed1f4305c..f196e91b10 100644 --- a/code/espurna/flow.ino +++ b/code/espurna/flow.ino @@ -247,7 +247,31 @@ class FlowTimerComponent : public FlowComponent { } }; +class FlowStartComponent; +std::vector _start_components; + +class FlowStartComponent : public FlowComponent { + private: + JsonVariant *_data = new JsonVariant(true); + + public: + FlowStartComponent(JsonObject& properties) { + _start_components.push_back(this); + } + + void start() { + processOutput(*_data, 0); + } +}; + void _flowComponentLoop() { + if (!_start_components.empty()) { + for (unsigned int i=0; i < _start_components.size(); i++) { + _start_components[i]->start(); + } + _start_components.clear(); + } + if (!_delay_queue.empty()) { auto it = _delay_queue.begin(); const delay_queue_element_t element = *it; @@ -265,6 +289,10 @@ void _flowComponentLoop() { } void flowSetup() { + flowRegisterComponent("Start", "play", (flow_component_factory_f)([] (JsonObject& properties) { return new FlowStartComponent(properties); })) + ->addOutput("Event", BOOL) + ; + flowRegisterComponent("Debug", "eye", (flow_component_factory_f)([] (JsonObject& properties) { return new FlowDebugComponent(properties); })) ->addInput("Data", ANY) ->addProperty("Prefix", STRING) @@ -284,7 +312,7 @@ void flowSetup() { ; flowRegisterComponent("Timer", "clock-o", (flow_component_factory_f)([] (JsonObject& properties) { return new FlowTimerComponent(properties); })) - ->addOutput("Data", ANY) + ->addOutput("Event", BOOL) ->addProperty("Seconds", INT) ; From ebb25da08d7dff4db8d1c5602f2efb5f4ff47624 Mon Sep 17 00:00:00 2001 From: Eugene Schava Date: Sat, 9 Feb 2019 10:01:47 +0200 Subject: [PATCH 13/40] flow: correct order of components in library --- code/espurna/flow.h | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/code/espurna/flow.h b/code/espurna/flow.h index 143debf2ef..731a52f881 100644 --- a/code/espurna/flow.h +++ b/code/espurna/flow.h @@ -185,29 +185,26 @@ class FlowComponentType { class FlowComponentLibrary { private: - std::map _types; + std::vector _types; + std::map _typesMap; public: FlowComponentType* addType(String name, String icon, flow_component_factory_f factory) { FlowComponentType* type = new FlowComponentType(name, icon, factory); - _types[name] = type; + _types.push_back(type); + _typesMap[name] = type; return type; } FlowComponentType* getComponent(int index) { - auto end = _types.end(); - - int counter = 0; - for (auto const& pair : _types) { - if (counter++ == index) - return pair.second; - } - return NULL; + if (index >= _types.size()) + return NULL; + return _types[index]; } FlowComponentType* getType(String name) { - auto it = _types.find(name); - if (it == _types.end()) return NULL; + auto it = _typesMap.find(name); + if (it == _typesMap.end()) return NULL; return it->second; } }; From d1c93b3c96d928aee6c1e20e47b8fa33b9fbff16 Mon Sep 17 00:00:00 2001 From: Eugene Schava Date: Sat, 9 Feb 2019 22:51:41 +0200 Subject: [PATCH 14/40] flow: gate component --- code/espurna/flow.ino | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/code/espurna/flow.ino b/code/espurna/flow.ino index f196e91b10..26e765a613 100644 --- a/code/espurna/flow.ino +++ b/code/espurna/flow.ino @@ -265,6 +265,7 @@ class FlowStartComponent : public FlowComponent { }; void _flowComponentLoop() { + // TODO: have single scheduler map for all components if (!_start_components.empty()) { for (unsigned int i=0; i < _start_components.size(); i++) { _start_components[i]->start(); @@ -288,6 +289,25 @@ void _flowComponentLoop() { } } +class FlowGateComponent : public FlowComponent { + private: + bool _state = true; + + public: + FlowGateComponent(JsonObject& properties) { + } + + virtual void processInput(JsonVariant& data, int inputNumber) { + if (inputNumber == 0) { // data + if (_state) { + processOutput(data, 0); + } + } else { // state + _state = data.as(); + } + } +}; + void flowSetup() { flowRegisterComponent("Start", "play", (flow_component_factory_f)([] (JsonObject& properties) { return new FlowStartComponent(properties); })) ->addOutput("Event", BOOL) @@ -316,6 +336,12 @@ void flowSetup() { ->addProperty("Seconds", INT) ; + flowRegisterComponent("Gate", "unlock", (flow_component_factory_f)([] (JsonObject& properties) { return new FlowGateComponent(properties); })) + ->addInput("Data", ANY) + ->addInput("State", BOOL) + ->addOutput("Data", ANY) + ; + // TODO: each component should have its own loop lambda function, in this case deque instead of set could be used // for delay elements container espurnaRegisterLoop(_flowComponentLoop); From e19b859a42a89078637ca31aca752f80a7cbcf8e Mon Sep 17 00:00:00 2001 From: Eugene Schava Date: Sun, 10 Feb 2019 10:25:34 +0200 Subject: [PATCH 15/40] flow: hysteresis component --- code/espurna/flow.h | 7 ++-- code/espurna/flow.ino | 78 +++++++++++++++++++++++++++++++++---------- 2 files changed, 64 insertions(+), 21 deletions(-) diff --git a/code/espurna/flow.h b/code/espurna/flow.h index 731a52f881..1e0e29b31e 100644 --- a/code/espurna/flow.h +++ b/code/espurna/flow.h @@ -32,9 +32,10 @@ class FlowComponent { } JsonVariant* clone(JsonVariant& data) { - if (data == NULL) { - return new JsonVariant(false); // workaround for JSON parsing issue - } else if (data.is()) { +// if (data == NULL) { +// return new JsonVariant(false); // workaround for JSON parsing issue +// } else + if (data.is()) { return new JsonVariant(data.as()); } else if (data.is()) { return new JsonVariant(data.as()); diff --git a/code/espurna/flow.ino b/code/espurna/flow.ino index 26e765a613..ccad99782f 100644 --- a/code/espurna/flow.ino +++ b/code/espurna/flow.ino @@ -49,20 +49,11 @@ void flowStart() { File file = SPIFFS.open("/flow.json", "r"); // TODO: file name to constant if (file) { - size_t size = file.size(); - uint8_t* nbuf = (uint8_t*) malloc(size + 1); - if (nbuf) { - size = file.read(nbuf, size); - file.close(); - nbuf[size] = 0; - - DynamicJsonBuffer jsonBuffer; - JsonObject& root = jsonBuffer.parseObject((char *) nbuf); - if (root.success()) _flowStart(root); - else DEBUG_MSG("[FLOW] Error: Flow cannot be parsed as correct JSON\n"); - - free(nbuf); - } + DynamicJsonBuffer jsonBuffer; + JsonObject& root = jsonBuffer.parseObject(file); + if (root.success()) _flowStart(root); + else DEBUG_MSG("[FLOW] Error: Flow cannot be parsed as correct JSON\n"); + file.close(); } else { DEBUG_MSG("[FLOW] No flow found\n"); @@ -144,9 +135,11 @@ class FlowDebugComponent : public FlowComponent { } virtual void processInput(JsonVariant& data, int inputNumber) { - if (data == NULL) { - DEBUG_MSG_P("[FLOW DEBUG] %sEMPTY\n", _prefix.c_str()); - } else if (data.is()) { +// if (data == NULL) { +// if (data.as() == NULL) { +// DEBUG_MSG_P("[FLOW DEBUG] %sEMPTY\n", _prefix.c_str()); +// } else + if (data.is()) { DEBUG_MSG_P("[FLOW DEBUG] %s%i\n", _prefix.c_str(), data.as()); } else if (data.is()) { char buffer[64]; @@ -308,12 +301,51 @@ class FlowGateComponent : public FlowComponent { } }; +class FlowHysteresisComponent : public FlowComponent { + private: + bool _state = false; + double _min = NAN; + double _max = NAN; + double _value = NAN; + + public: + FlowHysteresisComponent(JsonObject& properties) { + JsonVariant min = properties["Min"]; + JsonVariant max = properties["Max"]; + _min = min.success() && min.is() ? min.as() : NAN; + _max = max.success() && max.is() ? max.as() : NAN; + } + + virtual void processInput(JsonVariant& data, int inputNumber) { + if (inputNumber == 0) { // value + _value = data.as(); + if ((_state && _value >= _max) || (!_state && _value <= _min)) + _state = !_state; + processOutput(data, _state ? 0 : 1); + } else if (inputNumber == 1) { // min + _min = data.as(); + if (!_state && _value <= _min) { + _state = true; + JsonVariant value(_value); + processOutput(value, 0); + } + } else { // max + _max = data.as(); + if (_state && _value >= _max) { + _state = false; + JsonVariant value(_value); + processOutput(value, 1); + } + } + } +}; + void flowSetup() { flowRegisterComponent("Start", "play", (flow_component_factory_f)([] (JsonObject& properties) { return new FlowStartComponent(properties); })) ->addOutput("Event", BOOL) ; - flowRegisterComponent("Debug", "eye", (flow_component_factory_f)([] (JsonObject& properties) { return new FlowDebugComponent(properties); })) + flowRegisterComponent("Debug", "bug", (flow_component_factory_f)([] (JsonObject& properties) { return new FlowDebugComponent(properties); })) ->addInput("Data", ANY) ->addProperty("Prefix", STRING) ; @@ -342,6 +374,16 @@ void flowSetup() { ->addOutput("Data", ANY) ; + flowRegisterComponent("Hysteresis", "line-chart", (flow_component_factory_f)([] (JsonObject& properties) { return new FlowHysteresisComponent(properties); })) + ->addInput("Value", DOUBLE) + ->addInput("Min", DOUBLE) + ->addInput("Max", DOUBLE) + ->addOutput("Rise", DOUBLE) + ->addOutput("Fall", DOUBLE) + ->addProperty("Min", DOUBLE) + ->addProperty("Max", DOUBLE) + ; + // TODO: each component should have its own loop lambda function, in this case deque instead of set could be used // for delay elements container espurnaRegisterLoop(_flowComponentLoop); From 41d860af6dd74f3e553bd64fbb7da00e0897c02b Mon Sep 17 00:00:00 2001 From: Eugene Schava Date: Sun, 10 Feb 2019 12:12:32 +0200 Subject: [PATCH 16/40] flow: support compressed JSON in flow config --- code/espurna/flow.ino | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/code/espurna/flow.ino b/code/espurna/flow.ino index ccad99782f..8c2f57e1f8 100644 --- a/code/espurna/flow.ino +++ b/code/espurna/flow.ino @@ -64,16 +64,16 @@ void _flowStart(JsonObject& data) { std::map componentTypes; std::map components; - JsonObject& processes = data["processes"]; + JsonObject& processes = data.containsKey("P") ? data["P"] : data["processes"]; for (auto process_kv: processes) { String id = process_kv.key; - JsonVariant& value = process_kv.value; - String componentName = value["component"]; + JsonObject& value = process_kv.value; + String componentName = value.containsKey("C") ? value["C"] : value["component"]; FlowComponentType* componentType = _library.getType(componentName); if (componentType != NULL) { - JsonObject& metadata = value["metadata"]; - JsonObject& properties = metadata["properties"]; + JsonObject& metadata = value.containsKey("M") ? value["M"] : value["metadata"]; + JsonObject& properties = metadata.containsKey("R") ? metadata["R"] : metadata["properties"]; FlowComponent* component = componentType->createComponent(properties); componentTypes[id] = componentType; @@ -83,15 +83,15 @@ void _flowStart(JsonObject& data) { } } - JsonArray& connections = data["connections"]; + JsonArray& connections = data.containsKey("X") ? data["X"] : data["connections"]; for (JsonObject& connection: connections) { - JsonObject& src = connection["src"]; - String srcProcess = src["process"]; - String srcPort = src["port"]; + JsonObject& src = connection.containsKey("S") ? connection["S"] : connection["src"]; + String srcProcess = src.containsKey("I") ? src["I"] : src["process"]; + String srcPort = src.containsKey("N") ? src["N"] : src["port"]; - JsonObject& tgt = connection["tgt"]; - String tgtProcess = tgt["process"]; - String tgtPort = tgt["port"]; + JsonObject& tgt = connection.containsKey("T") ? connection["T"] : connection["tgt"]; + String tgtProcess = tgt.containsKey("I") ? tgt["I"] : tgt["process"]; + String tgtPort = tgt.containsKey("N") ? tgt["N"] : tgt["port"]; FlowComponent* srcComponent = components[srcProcess]; if (srcComponent == NULL) { From a77132bd30c6bafe5b463b0438198c12b7bb5d12 Mon Sep 17 00:00:00 2001 From: Eugene Schava Date: Sun, 10 Feb 2019 13:08:42 +0200 Subject: [PATCH 17/40] flow: schedule component - minor optimization --- code/espurna/scheduler.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/espurna/scheduler.ino b/code/espurna/scheduler.ino index 526165dafe..88637b3e21 100644 --- a/code/espurna/scheduler.ino +++ b/code/espurna/scheduler.ino @@ -75,7 +75,7 @@ class FlowScheduleComponent : public FlowComponent { } void check(time_t& time) { - if (_schIsThisWeekday(time, _weekdays) && _schMinutesLeft(time, _hour, _minute) == 0) { + if (_schMinutesLeft(time, _hour, _minute) == 0 && (_weekdays.length() == 0 || _schIsThisWeekday(time, _weekdays))) { processOutput(*_data, 0); } } From 5a8a33c7b629c177020fd01ca6485e19f668aebc Mon Sep 17 00:00:00 2001 From: Eugene Schava Date: Wed, 13 Feb 2019 23:13:55 +0200 Subject: [PATCH 18/40] flow: sensor component --- code/espurna/flow.ino | 2 +- code/espurna/sensor.ino | 54 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/code/espurna/flow.ino b/code/espurna/flow.ino index 8c2f57e1f8..5342ba19df 100644 --- a/code/espurna/flow.ino +++ b/code/espurna/flow.ino @@ -52,7 +52,7 @@ void flowStart() { DynamicJsonBuffer jsonBuffer; JsonObject& root = jsonBuffer.parseObject(file); if (root.success()) _flowStart(root); - else DEBUG_MSG("[FLOW] Error: Flow cannot be parsed as correct JSON\n"); + else DEBUG_MSG("[FLOW] Error: flow cannot be parsed as correct JSON\n"); file.close(); } else { diff --git a/code/espurna/sensor.ino b/code/espurna/sensor.ino index 4dc84e7bc2..a4e3966f27 100644 --- a/code/espurna/sensor.ino +++ b/code/espurna/sensor.ino @@ -345,6 +345,41 @@ void _sensorInitCommands() { #endif +#if FLOW_SUPPORT + +class FlowSensorComponent; +std::vector _flow_sensors; + +class FlowSensorComponent : public FlowComponent { + private: + int _magnitude = -1; + + public: + FlowSensorComponent(JsonObject& properties) { + String magnitude = properties["Magnitude"]; + for (unsigned int i=0; i < magnitudeCount(); i++) { + if (magnitudeName(i).equals(magnitude)) { + _magnitude = i; + } + } + + if (_magnitude >= 0) { + _flow_sensors.push_back(this); + } else { + DEBUG_MSG_P("[FLOW] Sensor %s not found\n", magnitude.c_str()); + } + } + + void sensorReport(int magnitude, double value) { + if (magnitude == _magnitude) { + JsonVariant data(value); + processOutput(data, 0); + } + } +}; + +#endif + void _sensorTick() { for (unsigned char i=0; i<_sensors.size(); i++) { _sensors[i]->tick(); @@ -1219,6 +1254,12 @@ void _sensorReport(unsigned char index, double value) { } #endif // DOMOTICZ_SUPPORT + #if FLOW_SUPPORT + for (FlowSensorComponent* component : _flow_sensors) { + component->sensorReport(index, value); + } + #endif + } // ----------------------------------------------------------------------------- @@ -1327,6 +1368,19 @@ void sensorSetup() { _sensorInitCommands(); #endif + // Flow + #if FLOW_SUPPORT + std::vector* magnitudes = new std::vector(); + for (unsigned int i=0; i < magnitudeCount(); i++) { + magnitudes->push_back(magnitudeName(i)); + } + + flowRegisterComponent("Sensor", "thermometer-3", (flow_component_factory_f)([] (JsonObject& properties) { return new FlowSensorComponent(properties); })) + ->addOutput("Data", DOUBLE) + ->addProperty("Magnitude", magnitudes) + ; + #endif + // Main callbacks espurnaRegisterLoop(sensorLoop); espurnaRegisterReload(_sensorConfigure); From 66a125e095cd95537887c372121208f96293693c Mon Sep 17 00:00:00 2001 From: Eugene Schava Date: Thu, 14 Feb 2019 01:32:52 +0200 Subject: [PATCH 19/40] flow: sensor component --- code/espurna/sensor.ino | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/code/espurna/sensor.ino b/code/espurna/sensor.ino index a4e3966f27..fafb4a3aca 100644 --- a/code/espurna/sensor.ino +++ b/code/espurna/sensor.ino @@ -356,9 +356,19 @@ class FlowSensorComponent : public FlowComponent { public: FlowSensorComponent(JsonObject& properties) { - String magnitude = properties["Magnitude"]; - for (unsigned int i=0; i < magnitudeCount(); i++) { - if (magnitudeName(i).equals(magnitude)) { + String magnitude = properties["Sensor"]; + + int slash = magnitude.indexOf("/"); + if (slash < 0) { + DEBUG_MSG_P("[FLOW] Sensor %s has incorrect name\n", magnitude.c_str()); + return; + } + String sensor = magnitude.substring(0, slash); + String topic = magnitude.substring(slash + 1); + + for (unsigned char i = 0; i < _magnitudes.size(); i++) { + sensor_magnitude_t m = _magnitudes[i]; + if (magnitudeName(i).equals(sensor) && magnitudeTopic(m.type).equals(topic)) { _magnitude = i; } } @@ -1370,14 +1380,17 @@ void sensorSetup() { // Flow #if FLOW_SUPPORT - std::vector* magnitudes = new std::vector(); - for (unsigned int i=0; i < magnitudeCount(); i++) { - magnitudes->push_back(magnitudeName(i)); + std::vector* sensors = new std::vector(); + for (unsigned char i = 0; i < _magnitudes.size(); i++) { + sensor_magnitude_t magnitude = _magnitudes[i]; + String sensor = magnitudeName(i); + String topic = magnitudeTopic(magnitude.type); + sensors->push_back(sensor + "/" + topic); } flowRegisterComponent("Sensor", "thermometer-3", (flow_component_factory_f)([] (JsonObject& properties) { return new FlowSensorComponent(properties); })) ->addOutput("Data", DOUBLE) - ->addProperty("Magnitude", magnitudes) + ->addProperty("Sensor", sensors) ; #endif From 001e3b91302b292406ba52f73f85fed086849016 Mon Sep 17 00:00:00 2001 From: Eugene Schava Date: Thu, 14 Feb 2019 01:40:06 +0200 Subject: [PATCH 20/40] flow: gate component --- code/espurna/flow.ino | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/code/espurna/flow.ino b/code/espurna/flow.ino index 5342ba19df..15980c9471 100644 --- a/code/espurna/flow.ino +++ b/code/espurna/flow.ino @@ -292,9 +292,7 @@ class FlowGateComponent : public FlowComponent { virtual void processInput(JsonVariant& data, int inputNumber) { if (inputNumber == 0) { // data - if (_state) { - processOutput(data, 0); - } + processOutput(data, _state ? 0 : 1); } else { // state _state = data.as(); } @@ -371,7 +369,8 @@ void flowSetup() { flowRegisterComponent("Gate", "unlock", (flow_component_factory_f)([] (JsonObject& properties) { return new FlowGateComponent(properties); })) ->addInput("Data", ANY) ->addInput("State", BOOL) - ->addOutput("Data", ANY) + ->addOutput("Open", ANY) + ->addOutput("Close", ANY) ; flowRegisterComponent("Hysteresis", "line-chart", (flow_component_factory_f)([] (JsonObject& properties) { return new FlowHysteresisComponent(properties); })) From 7574de4331cdc4bfff6c0b1f9315b7633d599162 Mon Sep 17 00:00:00 2001 From: Eugene Schava Date: Sat, 16 Feb 2019 12:15:41 +0200 Subject: [PATCH 21/40] flow: memory optimizations and refactoring --- code/espurna/button.ino | 26 +- code/espurna/config/prototypes.h | 6 +- code/espurna/flow.h | 164 +++--------- code/espurna/flow.ino | 433 +++++++++++++++++++++---------- code/espurna/mqtt.ino | 46 +++- code/espurna/relay.ino | 26 +- code/espurna/scheduler.ino | 22 +- code/espurna/sensor.ino | 22 +- code/espurna/web.ino | 34 +-- 9 files changed, 462 insertions(+), 317 deletions(-) diff --git a/code/espurna/button.ino b/code/espurna/button.ino index ba3c20014b..fb577a8528 100644 --- a/code/espurna/button.ino +++ b/code/espurna/button.ino @@ -16,7 +16,6 @@ Copyright (C) 2016-2018 by Xose Pérez #include #if FLOW_SUPPORT -#include "flow.h" class FlowButtonComponent; // forward declaration #endif @@ -56,6 +55,24 @@ bool _buttonWebSocketOnReceive(const char * key, JsonVariant& value) { #if FLOW_SUPPORT +PROGMEM const char flow_data2[] = "Data"; +PROGMEM const char* const flow_data2_array[] = {flow_data2}; + +PROGMEM const FlowConnections flow_button_component = { + 0, NULL, + 1, flow_data2_array, +}; + +PROGMEM const char flow_button_component_json[] = + "\"Button\": " + "{" + "\"name\":\"Button\"," + "\"icon\":\"toggle-on\"," + "\"inports\":[]," + "\"outports\":[{\"name\":\"Data\",\"type\":\"int\"}]," + "\"properties\":[{\"name\":\"Button\",\"type\":\"list\"}]" + "}"; + class FlowButtonComponent : public FlowComponent { public: FlowButtonComponent(JsonObject& properties) { @@ -250,10 +267,9 @@ void buttonSetup() { buttons->push_back(String(i)); } - flowRegisterComponent("Button", "toggle-on", (flow_component_factory_f)([] (JsonObject& properties) { return new FlowButtonComponent(properties); })) - ->addOutput("Event", INT) - ->addProperty("Button", buttons) - ; + flowRegisterComponent("Button", &flow_button_component, flow_button_component_json, + (flow_component_factory_f)([] (JsonObject& properties) { return new FlowButtonComponent(properties); })); + flowRegisterComponentValues("Button", "Button", buttons); #endif // Register loop diff --git a/code/espurna/config/prototypes.h b/code/espurna/config/prototypes.h index eddf01f8aa..8e400fe39f 100644 --- a/code/espurna/config/prototypes.h +++ b/code/espurna/config/prototypes.h @@ -210,10 +210,10 @@ bool wifiConnected(); // ----------------------------------------------------------------------------- #if FLOW_SUPPORT - class FlowComponent; - class FlowComponentType; + #include "flow.h" typedef std::function flow_component_factory_f; + void flowRegisterComponentValues(String component, String property, std::vector* values); #else - #define FlowComponentType void + #define FlowConnections void #define flow_component_factory_f void * #endif diff --git a/code/espurna/flow.h b/code/espurna/flow.h index 1e0e29b31e..38e375768c 100644 --- a/code/espurna/flow.h +++ b/code/espurna/flow.h @@ -3,15 +3,11 @@ #include #include -enum FlowValueType { - ANY, - STRING, - INT, - DOUBLE, - BOOL, - LIST, - TIME, - WEEKDAYS +struct FlowConnections { + int inputsNumber; + const char* const* inputs; + int outputsNumber; + const char* const* outputs; }; class FlowComponent { @@ -32,9 +28,6 @@ class FlowComponent { } JsonVariant* clone(JsonVariant& data) { -// if (data == NULL) { -// return new JsonVariant(false); // workaround for JSON parsing issue -// } else if (data.is()) { return new JsonVariant(data.as()); } else if (data.is()) { @@ -76,136 +69,57 @@ class FlowComponent { typedef std::function flow_component_factory_f; -class FlowComponentType { +class FlowComponentLibrary { private: - typedef struct { - String name; - FlowValueType type; - } name_type_t; - - String _name; - String _icon; - flow_component_factory_f _factory; - std::vector _inputs; - std::vector _outputs; - std::vector _properties; - std::map*> _list_values; - - void vectorToJSON(JsonObject& root, std::vector &v, const char* name) { - JsonArray& array = root.createNestedArray(name); - for (unsigned int i=0; i < v.size(); i++) { - name_type_t& element = v[i]; - JsonObject& elementObject = array.createNestedObject(); - elementObject["name"] = element.name; - - FlowValueType type = element.type; - const char *typeName = "unknown"; - switch(type) { - case ANY: typeName = "any"; break; - case STRING: typeName = "string"; break; - case INT: typeName = "int"; break; - case DOUBLE: typeName = "double"; break; - case BOOL: typeName = "bool"; break; - case LIST: typeName = "list"; break; - case TIME: typeName = "time"; break; - case WEEKDAYS: typeName = "weekdays"; break; - } - elementObject["type"] = typeName; - - if (type == LIST) { - std::vector* values = _list_values[element.name]; - JsonArray& valuesArray = elementObject.createNestedArray("values"); - for (unsigned int j=0; j < values->size(); j++) { - valuesArray.add(values->at(j)); - } - } - } - } + std::vector _jsons; + std::map _connectionsMap; + std::map _factoryMap; public: - FlowComponentType(String name, String icon, flow_component_factory_f factory) { - _name = name; - _icon = icon; - _factory = factory; - } - - String name() { - return _name; - } - - FlowComponentType* addInput(String name, FlowValueType type) { - _inputs.push_back({name, type}); - return this; + void addType(String name, const FlowConnections* connections, const char* json, flow_component_factory_f factory) { + _jsons.push_back(json); + _connectionsMap[name] = connections; + _factoryMap[name] = factory; } - FlowComponentType* addOutput(String name, FlowValueType type) { - _outputs.push_back({name, type}); - return this; + const char* getComponentJson(int index) { + if (index >= _jsons.size()) + return NULL; + return _jsons[index]; } - FlowComponentType* addProperty(String name, FlowValueType type) { - _properties.push_back({name, type}); - return this; + FlowComponent* createComponent(String name, JsonObject& properties) { + flow_component_factory_f& factory = _factoryMap[name]; + return factory != NULL ? factory(properties) : NULL; } - FlowComponentType* addProperty(String name, std::vector* values) { - _properties.push_back({name, LIST}); - _list_values[name] = values; - return this; - } + int getInputNumber(String name, String input) { + const FlowConnections* connections = _connectionsMap[name]; + if (connections == NULL) + return -1; - int getInputNumber(String& name) { - for (int i = 0; i < _inputs.size(); i++) { - if (_inputs[i].name.equalsIgnoreCase(name)) + FlowConnections temp; + memcpy_P (&temp, connections, sizeof (FlowConnections)); + for (int i = 0; i < temp.inputsNumber; i++) { + if (strcmp_P(input.c_str(), temp.inputs[i]) == 0) return i; } - return -1; - } - int getOutputNumber(String& name) { - for (int i = 0; i < _outputs.size(); i++) { - if (_outputs[i].name.equalsIgnoreCase(name)) - return i; - } return -1; } - void toJSON(JsonObject& root) { - root["name"] = _name; - root["icon"] = _icon; - - vectorToJSON(root, _inputs, "inports"); - vectorToJSON(root, _outputs, "outports"); - vectorToJSON(root, _properties, "properties"); - } - - FlowComponent* createComponent(JsonObject& properties) { - return _factory(properties); - } -}; - -class FlowComponentLibrary { - private: - std::vector _types; - std::map _typesMap; + int getOutputNumber(String name, String output) { + const FlowConnections* connections = _connectionsMap[name]; + if (connections == NULL) + return -1; - public: - FlowComponentType* addType(String name, String icon, flow_component_factory_f factory) { - FlowComponentType* type = new FlowComponentType(name, icon, factory); - _types.push_back(type); - _typesMap[name] = type; - return type; - } - - FlowComponentType* getComponent(int index) { - if (index >= _types.size()) - return NULL; - return _types[index]; - } + FlowConnections temp; + memcpy_P (&temp, connections, sizeof (FlowConnections)); + for (int i = 0; i < temp.outputsNumber; i++) { + if (strcmp_P(output.c_str(), temp.outputs[i]) == 0) + return i; + } - FlowComponentType* getType(String name) { - auto it = _typesMap.find(name); - if (it == _typesMap.end()) return NULL; - return it->second; + return -1; } }; diff --git a/code/espurna/flow.ino b/code/espurna/flow.ino index 15980c9471..9f28592b7e 100644 --- a/code/espurna/flow.ino +++ b/code/espurna/flow.ino @@ -12,16 +12,38 @@ Copyright (C) 2016-2018 by Xose P #include #include -#include "flow.h" - // ----------------------------------------------------------------------------- // FLOW // ----------------------------------------------------------------------------- FlowComponentLibrary _library; -FlowComponentType* flowRegisterComponent(String name, String icon, flow_component_factory_f factory) { - return _library.addType(name, icon, factory); +typedef struct { + String component; + String property; + std::vector* values; +} component_property_values_t; + +std::vector _component_property_values_list; + +void flowRegisterComponent(String name, const FlowConnections* connections, const char* json, flow_component_factory_f factory) { + _library.addType(name, connections, json, factory); +} + +void flowRegisterComponentValues(String component, String property, std::vector* values) { + _component_property_values_list.push_back({component, property, values}); +} + +void flowGetComponentValuesJson(JsonArray& root) { + for (component_property_values_t entry : _component_property_values_list) { + JsonObject& object = root.createNestedObject(); + object["component"] = entry.component; + object["property"] = entry.property; + JsonArray& valuesArray = object.createNestedArray("values"); + for (unsigned int j=0; j < entry.values->size(); j++) { + valuesArray.add(entry.values->at(j)); + } + } } String flowGetDataPath() { @@ -35,49 +57,49 @@ bool flowSaveData(char* data) { result = file.print(data); file.close(); } else { - DEBUG_MSG("[FLOW] Error saving data\n"); + DEBUG_MSG_P(PSTR("[FLOW] Error saving data\n")); } return result; } -FlowComponentType* flowGetComponent(int index) { - return _library.getComponent(index); +const char* flowGetComponentJson(int index) { + return _library.getComponentJson(index); } void flowStart() { - DEBUG_MSG("[FLOW] Starting\n"); + DEBUG_MSG_P(PSTR("[FLOW] Starting\n")); File file = SPIFFS.open("/flow.json", "r"); // TODO: file name to constant if (file) { DynamicJsonBuffer jsonBuffer; JsonObject& root = jsonBuffer.parseObject(file); if (root.success()) _flowStart(root); - else DEBUG_MSG("[FLOW] Error: flow cannot be parsed as correct JSON\n"); + else DEBUG_MSG_P(PSTR("[FLOW] Error: flow cannot be parsed as correct JSON\n")); file.close(); } else { - DEBUG_MSG("[FLOW] No flow found\n"); + DEBUG_MSG_P(PSTR("[FLOW] No flow found\n")); } } void _flowStart(JsonObject& data) { - std::map componentTypes; std::map components; + std::map componentsNames; JsonObject& processes = data.containsKey("P") ? data["P"] : data["processes"]; for (auto process_kv: processes) { String id = process_kv.key; JsonObject& value = process_kv.value; + String componentName = value.containsKey("C") ? value["C"] : value["component"]; - FlowComponentType* componentType = _library.getType(componentName); + JsonObject& metadata = value.containsKey("M") ? value["M"] : value["metadata"]; + JsonObject& properties = metadata.containsKey("R") ? metadata["R"] : metadata["properties"]; - if (componentType != NULL) { - JsonObject& metadata = value.containsKey("M") ? value["M"] : value["metadata"]; - JsonObject& properties = metadata.containsKey("R") ? metadata["R"] : metadata["properties"]; - FlowComponent* component = componentType->createComponent(properties); + FlowComponent* component = _library.createComponent(componentName, properties); - componentTypes[id] = componentType; + if (component != NULL) { components[id] = component; + componentsNames[id] = componentName; } else { DEBUG_MSG_P(PSTR("[FLOW] Error: component '%s' is not registered\n"), componentName.c_str()); } @@ -105,18 +127,15 @@ void _flowStart(JsonObject& data) { continue; } - FlowComponentType* srcComponentType = componentTypes[srcProcess]; - FlowComponentType* tgtComponentType = componentTypes[tgtProcess]; - - int srcNumber = srcComponentType->getOutputNumber(srcPort); + int srcNumber = _library.getOutputNumber(componentsNames[srcProcess], srcPort); if (srcNumber < 0) { - DEBUG_MSG_P(PSTR("[FLOW] Error: component '%s' has no output named '%s'\n"), srcComponentType->name().c_str(), srcPort.c_str()); + DEBUG_MSG_P(PSTR("[FLOW] Error: component '%s' has no output named '%s'\n"), componentsNames[srcProcess].c_str(), srcPort.c_str()); continue; } - int tgtNumber = tgtComponentType->getInputNumber(tgtPort); + int tgtNumber = _library.getInputNumber(componentsNames[tgtProcess], tgtPort); if (tgtNumber < 0) { - DEBUG_MSG_P(PSTR("[FLOW] Error: component '%s' has no input named '%s'\n"), tgtComponentType->name().c_str(), tgtPort.c_str()); + DEBUG_MSG_P(PSTR("[FLOW] Error: component '%s' has no input named '%s'\n"), componentsNames[tgtProcess].c_str(), tgtPort.c_str()); continue; } @@ -124,6 +143,92 @@ void _flowStart(JsonObject& data) { } } +typedef std::function flow_scheduled_task_callback_f; + +struct flow_scheduled_task_t { + JsonVariant *data; + unsigned long time; + flow_scheduled_task_callback_f callback; + + bool operator() (const flow_scheduled_task_t& lhs, const flow_scheduled_task_t& rhs) const { + return lhs.time < rhs.time; + } +}; + +std::set _flow_scheduled_tasks_queue; + +void _flowComponentLoop() { + if (!_flow_scheduled_tasks_queue.empty()) { + auto it = _flow_scheduled_tasks_queue.begin(); + const flow_scheduled_task_t element = *it; + unsigned long now = millis(); + if (element.time <= now) { + element.callback(now, element.data); + _flow_scheduled_tasks_queue.erase(it); + } + } +} + +// ----------------------------------------------------------------------------- +// Start component +// ----------------------------------------------------------------------------- + +PROGMEM const char flow_data[] = "Data"; +PROGMEM const char* const flow_data_array[] = {flow_data}; + +PROGMEM const FlowConnections flow_start_component = { + 0, NULL, + 1, flow_data_array, +}; + +PROGMEM const char flow_start_component_json[] = + "\"Start\": " + "{" + "\"name\":\"Start\"," + "\"icon\":\"play\"," + "\"inports\":[]," + "\"outports\":[{\"name\":\"Data\",\"type\":\"bool\"}]," + "\"properties\":[]" + "}"; + +class FlowStartComponent : public FlowComponent { + private: + JsonVariant *_data = new JsonVariant(true); + + public: + FlowStartComponent(JsonObject& properties) { + flow_scheduled_task_callback_f callback = [this](unsigned long time, JsonVariant *data){ this->onStart(); }; + _flow_scheduled_tasks_queue.insert({NULL, 0, callback}); + } + + void onStart() { + processOutput(*_data, 0); + } +}; + +// ----------------------------------------------------------------------------- +// Debug component +// ----------------------------------------------------------------------------- + +PROGMEM const FlowConnections flow_debug_component = { + 1, flow_data_array, + 0, NULL, +}; + +PROGMEM const char flow_debug_component_json[] = + "\"Debug\": " + "{" + "\"name\":\"Debug\"," + "\"icon\":\"bug\"," + "\"inports\":[{\"name\":\"Data\",\"type\":\"any\"}]," + "\"outports\":[]," + "\"properties\":[{\"name\":\"Prefix\",\"type\":\"string\"}]" + "}"; + +PROGMEM const char flow_debug_int[] = "[FLOW DEBUG] %s%i\n"; +PROGMEM const char flow_debug_string[] = "[FLOW DEBUG] %s%s\n"; +PROGMEM const char flow_debug_unknown[] = "[FLOW DEBUG] %sUNKNOWN\n"; + class FlowDebugComponent : public FlowComponent { private: String _prefix; @@ -135,26 +240,42 @@ class FlowDebugComponent : public FlowComponent { } virtual void processInput(JsonVariant& data, int inputNumber) { -// if (data == NULL) { -// if (data.as() == NULL) { -// DEBUG_MSG_P("[FLOW DEBUG] %sEMPTY\n", _prefix.c_str()); -// } else if (data.is()) { - DEBUG_MSG_P("[FLOW DEBUG] %s%i\n", _prefix.c_str(), data.as()); + DEBUG_MSG_P(flow_debug_int, _prefix.c_str(), data.as()); } else if (data.is()) { char buffer[64]; dtostrf(data.as(), 1 - sizeof(buffer), 3, buffer); - DEBUG_MSG_P("[FLOW DEBUG] %s%s\n", _prefix.c_str(), buffer); + DEBUG_MSG_P(flow_debug_string, _prefix.c_str(), buffer); } else if (data.is()) { - DEBUG_MSG_P("[FLOW DEBUG] %s%s\n", _prefix.c_str(), data.as() ? "true" : "false"); + DEBUG_MSG_P(flow_debug_string, _prefix.c_str(), data.as() ? "true" : "false"); } else if (data.is()) { - DEBUG_MSG_P("[FLOW DEBUG] %s%s\n", _prefix.c_str(), data.as()); + DEBUG_MSG_P(flow_debug_string, _prefix.c_str(), data.as()); } else { - DEBUG_MSG_P("[FLOW DEBUG] %sUNKNOWN\n", _prefix.c_str()); + DEBUG_MSG_P(flow_debug_unknown, _prefix.c_str()); } } }; +// ----------------------------------------------------------------------------- +// Change component +// ----------------------------------------------------------------------------- + +PROGMEM const FlowConnections flow_change_component = { + 1, flow_data_array, + 1, flow_data_array, +}; + +PROGMEM const char flow_change_component_json[] = + "\"Change\": " + "{" + "\"name\":\"Change\"," + "\"icon\":\"edit\"," + "\"inports\":[{\"name\":\"Data\",\"type\":\"any\"}]," + "\"outports\":[{\"name\":\"Data\",\"type\":\"any\"}]," + "\"properties\":[{\"name\":\"Value\",\"type\":\"any\"}]" + "}"; + + class FlowChangeComponent : public FlowComponent { private: JsonVariant* _value; @@ -170,19 +291,24 @@ class FlowChangeComponent : public FlowComponent { } }; -class FlowDelayComponent; - -struct delay_queue_element_t { - JsonVariant *data; - unsigned long time; - FlowDelayComponent *component; +// ----------------------------------------------------------------------------- +// Delay component +// ----------------------------------------------------------------------------- - bool operator() (const delay_queue_element_t& lhs, const delay_queue_element_t& rhs) const { - return lhs.time < rhs.time; - } +PROGMEM const FlowConnections flow_delay_component = { + 1, flow_data_array, + 1, flow_data_array, }; -std::set _delay_queue; +PROGMEM const char flow_delay_component_json[] = + "\"Delay\": " + "{" + "\"name\":\"Delay\"," + "\"icon\":\"pause\"," + "\"inports\":[{\"name\":\"Data\",\"type\":\"any\"}]," + "\"outports\":[{\"name\":\"Data\",\"type\":\"any\"}]," + "\"properties\":[{\"name\":\"Seconds\",\"type\":\"int\"}, {\"name\":\"Last only\",\"type\":\"bool\"}]" + "}"; class FlowDelayComponent : public FlowComponent { private: @@ -197,11 +323,12 @@ class FlowDelayComponent : public FlowComponent { } virtual void processInput(JsonVariant& data, int inputNumber) { - _delay_queue.insert({clone(data), millis() + _time, this}); + flow_scheduled_task_callback_f callback = [this](unsigned long time, JsonVariant *data){ this->onDelay(data); }; + _flow_scheduled_tasks_queue.insert({clone(data), millis() + _time, callback}); _queueSize++; } - void processElement(JsonVariant *data) { + void onDelay(JsonVariant *data) { if (!_lastOnly || _queueSize == 1) processOutput(*data, 0); @@ -210,77 +337,77 @@ class FlowDelayComponent : public FlowComponent { } }; -class FlowTimerComponent; -std::vector _timer_components; +// ----------------------------------------------------------------------------- +// Timer component +// ----------------------------------------------------------------------------- + +PROGMEM const FlowConnections flow_timer_component = { + 0, NULL, + 1, flow_data_array, +}; + +PROGMEM const char flow_timer_component_json[] = + "\"Timer\": " + "{" + "\"name\":\"Timer\"," + "\"icon\":\"clock-o\"," + "\"inports\":[]," + "\"outports\":[{\"name\":\"Data\",\"type\":\"bool\"}]," + "\"properties\":[{\"name\":\"Seconds\",\"type\":\"int\"}]" + "}"; + +PROGMEM const char flow_incorrect_timer_delay[] = "[FLOW] Incorrect timer delay: %i\n"; class FlowTimerComponent : public FlowComponent { private: JsonVariant *_data = new JsonVariant(true); - long _period; - long _lastMillis; + unsigned long _period; + flow_scheduled_task_callback_f _callback; public: FlowTimerComponent(JsonObject& properties) { int seconds = properties["Seconds"]; _period = 1000 * (int)seconds; - _lastMillis = millis(); - if (_period > 0) - _timer_components.push_back(this); - else - DEBUG_MSG_P("[FLOW] Incorrect timer delay: %i\n", seconds); - } - - void check() { - long now = millis(); - if (now >= _lastMillis + _period) { - processOutput(*_data, 0); - _lastMillis = now; + if (_period > 0) { + _callback = [this](unsigned long time, JsonVariant *data){ this->onSchedule(time); }; + _flow_scheduled_tasks_queue.insert({NULL, millis() + _period, _callback}); + } else { + DEBUG_MSG_P(flow_incorrect_timer_delay, seconds); } } -}; - -class FlowStartComponent; -std::vector _start_components; - -class FlowStartComponent : public FlowComponent { - private: - JsonVariant *_data = new JsonVariant(true); - - public: - FlowStartComponent(JsonObject& properties) { - _start_components.push_back(this); - } - void start() { + void onSchedule(unsigned long now) { processOutput(*_data, 0); + + // reschedule + _flow_scheduled_tasks_queue.insert({NULL, now + _period, _callback}); } }; -void _flowComponentLoop() { - // TODO: have single scheduler map for all components - if (!_start_components.empty()) { - for (unsigned int i=0; i < _start_components.size(); i++) { - _start_components[i]->start(); - } - _start_components.clear(); - } +// ----------------------------------------------------------------------------- +// Gate component +// ----------------------------------------------------------------------------- - if (!_delay_queue.empty()) { - auto it = _delay_queue.begin(); - const delay_queue_element_t element = *it; - if (element.time <= millis()) { - element.component->processElement(element.data); - _delay_queue.erase(it); - } - } +PROGMEM const char flow_state[] = "State"; +PROGMEM const char flow_open[] = "Open"; +PROGMEM const char flow_close[] = "Close"; +PROGMEM const char* const flow_gate_component_inputs[] = {flow_data, flow_state}; +PROGMEM const char* const flow_gate_component_outputs[] = {flow_open, flow_close}; +PROGMEM const FlowConnections flow_gate_component = { + 2, flow_gate_component_inputs, + 2, flow_gate_component_outputs, +}; - if (!_timer_components.empty()) { - for (unsigned int i=0; i < _timer_components.size(); i++) { - _timer_components[i]->check(); - } - } -} +PROGMEM const char flow_gate_component_json[] = + "\"Gate\": " + "{" + "\"name\":\"Gate\"," + "\"icon\":\"unlock\"," + "\"inports\":[{\"name\":\"Data\",\"type\":\"any\"}, {\"name\":\"State\",\"type\":\"bool\"}]," + "\"outports\":[{\"name\":\"Open\",\"type\":\"any\"}, {\"name\":\"Close\",\"type\":\"any\"}]," + "\"properties\":[]" + "}"; class FlowGateComponent : public FlowComponent { private: @@ -299,6 +426,32 @@ class FlowGateComponent : public FlowComponent { } }; +// ----------------------------------------------------------------------------- +// Hysteresis component +// ----------------------------------------------------------------------------- + +PROGMEM const char flow_value[] = "Value"; +PROGMEM const char flow_min[] = "Min"; +PROGMEM const char flow_max[] = "Max"; +PROGMEM const char flow_rise[] = "Rise"; +PROGMEM const char flow_fall[] = "Fall"; +PROGMEM const char* const flow_hysteresis_component_inputs[] = {flow_value, flow_min, flow_max}; +PROGMEM const char* const flow_hysteresis_component_outputs[] = {flow_rise, flow_fall}; +PROGMEM const FlowConnections flow_hysteresis_component = { + 3, flow_hysteresis_component_inputs, + 2, flow_hysteresis_component_outputs, +}; + +PROGMEM const char flow_hysteresis_component_json[] = + "\"Hysteresis\": " + "{" + "\"name\":\"Hysteresis\"," + "\"icon\":\"line-chart\"," + "\"inports\":[{\"name\":\"Value\",\"type\":\"double\"}, {\"name\":\"Min\",\"type\":\"double\"}, {\"name\":\"Max\",\"type\":\"double\"}]," + "\"outports\":[{\"name\":\"Rise\",\"type\":\"double\"}, {\"name\":\"Fall\",\"type\":\"double\"}]," + "\"properties\":[{\"name\":\"Min\",\"type\":\"double\"}, {\"name\":\"Max\",\"type\":\"double\"}]" + "}"; + class FlowHysteresisComponent : public FlowComponent { private: bool _state = false; @@ -338,53 +491,47 @@ class FlowHysteresisComponent : public FlowComponent { } }; +void _flowMQTTCallback(unsigned int type, const char * topic, const char * payload) { + + if (type == MQTT_CONNECT_EVENT) { + mqttSubscribe("flow"); + } + + if (type == MQTT_MESSAGE_EVENT) { + + // Match topic + String t = mqttMagnitude((char *) topic); + if (t.equals("flow")) { + flowStart(); + } + + } +} + void flowSetup() { - flowRegisterComponent("Start", "play", (flow_component_factory_f)([] (JsonObject& properties) { return new FlowStartComponent(properties); })) - ->addOutput("Event", BOOL) - ; - - flowRegisterComponent("Debug", "bug", (flow_component_factory_f)([] (JsonObject& properties) { return new FlowDebugComponent(properties); })) - ->addInput("Data", ANY) - ->addProperty("Prefix", STRING) - ; - - flowRegisterComponent("Change", "edit", (flow_component_factory_f)([] (JsonObject& properties) { return new FlowChangeComponent(properties); })) - ->addInput("Data", ANY) - ->addOutput("Data", ANY) - ->addProperty("Value", ANY) - ; - - flowRegisterComponent("Delay", "pause", (flow_component_factory_f)([] (JsonObject& properties) { return new FlowDelayComponent(properties); })) - ->addInput("Data", ANY) - ->addOutput("Data", ANY) - ->addProperty("Seconds", INT) - ->addProperty("Last only", BOOL) - ; - - flowRegisterComponent("Timer", "clock-o", (flow_component_factory_f)([] (JsonObject& properties) { return new FlowTimerComponent(properties); })) - ->addOutput("Event", BOOL) - ->addProperty("Seconds", INT) - ; - - flowRegisterComponent("Gate", "unlock", (flow_component_factory_f)([] (JsonObject& properties) { return new FlowGateComponent(properties); })) - ->addInput("Data", ANY) - ->addInput("State", BOOL) - ->addOutput("Open", ANY) - ->addOutput("Close", ANY) - ; - - flowRegisterComponent("Hysteresis", "line-chart", (flow_component_factory_f)([] (JsonObject& properties) { return new FlowHysteresisComponent(properties); })) - ->addInput("Value", DOUBLE) - ->addInput("Min", DOUBLE) - ->addInput("Max", DOUBLE) - ->addOutput("Rise", DOUBLE) - ->addOutput("Fall", DOUBLE) - ->addProperty("Min", DOUBLE) - ->addProperty("Max", DOUBLE) - ; - - // TODO: each component should have its own loop lambda function, in this case deque instead of set could be used - // for delay elements container + mqttRegister(_flowMQTTCallback); + + flowRegisterComponent("Start", &flow_start_component, flow_start_component_json, + (flow_component_factory_f)([] (JsonObject& properties) { return new FlowStartComponent(properties); })); + + flowRegisterComponent("Debug", &flow_debug_component, flow_debug_component_json, + (flow_component_factory_f)([] (JsonObject& properties) { return new FlowDebugComponent(properties); })); + + flowRegisterComponent("Change", &flow_change_component, flow_change_component_json, + (flow_component_factory_f)([] (JsonObject& properties) { return new FlowChangeComponent(properties); })); + + flowRegisterComponent("Delay", &flow_delay_component, flow_delay_component_json, + (flow_component_factory_f)([] (JsonObject& properties) { return new FlowDelayComponent(properties); })); + + flowRegisterComponent("Timer", &flow_timer_component, flow_timer_component_json, + (flow_component_factory_f)([] (JsonObject& properties) { return new FlowTimerComponent(properties); })); + + flowRegisterComponent("Gate", &flow_gate_component, flow_gate_component_json, + (flow_component_factory_f)([] (JsonObject& properties) { return new FlowGateComponent(properties); })); + + flowRegisterComponent("Hysteresis", &flow_hysteresis_component, flow_hysteresis_component_json, + (flow_component_factory_f)([] (JsonObject& properties) { return new FlowHysteresisComponent(properties); })); + espurnaRegisterLoop(_flowComponentLoop); } diff --git a/code/espurna/mqtt.ino b/code/espurna/mqtt.ino index 628c273b21..265f896028 100644 --- a/code/espurna/mqtt.ino +++ b/code/espurna/mqtt.ino @@ -365,6 +365,22 @@ void _mqttInitCommands() { // ----------------------------------------------------------------------------- #if FLOW_SUPPORT + +PROGMEM const FlowConnections flow_mqtt_subscribe_component = { + 0, NULL, + 1, flow_data_array, +}; + +PROGMEM const char flow_mqtt_subscribe_component_json[] = + "\"MQTT subscribe\": " + "{" + "\"name\":\"MQTT subscribe\"," + "\"icon\":\"sign-out\"," + "\"inports\":[]," + "\"outports\":[{\"name\":\"Data\",\"type\":\"string\"}]," + "\"properties\":[{\"name\":\"Topic\",\"type\":\"string\"}]" + "}"; + class FlowMqttSubscribeComponent : public FlowComponent { private: String _topic; @@ -393,6 +409,21 @@ class FlowMqttSubscribeComponent : public FlowComponent { } }; +PROGMEM const FlowConnections flow_mqtt_publish_component = { + 1, flow_data_array, + 0, NULL, +}; + +PROGMEM const char flow_mqtt_publish_component_json[] = + "\"MQTT publish\": " + "{" + "\"name\":\"MQTT publish\"," + "\"icon\":\"sign-in\"," + "\"inports\":[{\"name\":\"Data\",\"type\":\"string\"}]," + "\"outports\":[]," + "\"properties\":[{\"name\":\"Topic\",\"type\":\"string\"}, {\"name\":\"Retain\",\"type\":\"bool\"}]" + "}"; + class FlowMqttPublishComponent : public FlowComponent { private: String _topic; @@ -890,16 +921,11 @@ void mqttSetup() { #endif #if FLOW_SUPPORT - flowRegisterComponent("MQTT subscribe", "sign-out", (flow_component_factory_f)([] (JsonObject& properties) { return new FlowMqttSubscribeComponent(properties); })) - ->addOutput("Payload", STRING) - ->addProperty("Topic", STRING) - ; - - flowRegisterComponent("MQTT publish", "sign-in", (flow_component_factory_f)([] (JsonObject& properties) { return new FlowMqttPublishComponent(properties); })) - ->addInput("Payload", STRING) - ->addProperty("Topic", STRING) - ->addProperty("Retain", BOOL) - ; + flowRegisterComponent("MQTT subscribe", &flow_mqtt_subscribe_component, flow_mqtt_subscribe_component_json, + (flow_component_factory_f)([] (JsonObject& properties) { return new FlowMqttSubscribeComponent(properties); })); + + flowRegisterComponent("MQTT publish", &flow_mqtt_publish_component, flow_mqtt_publish_component_json, + (flow_component_factory_f)([] (JsonObject& properties) { return new FlowMqttPublishComponent(properties); })); #endif // Main callbacks diff --git a/code/espurna/relay.ino b/code/espurna/relay.ino index af938beda5..69f9e879ca 100644 --- a/code/espurna/relay.ino +++ b/code/espurna/relay.ino @@ -1022,6 +1022,24 @@ void _relayInitCommands() { #if FLOW_SUPPORT +PROGMEM const char flow_toggle[] = "Toggle"; +PROGMEM const char* const flow_relay_component_inputs[] = {flow_state, flow_toggle}; + +PROGMEM const FlowConnections flow_relay_component = { + 2, flow_relay_component_inputs, + 0, NULL, +}; + +PROGMEM const char flow_relay_component_json[] = + "\"Relay\": " + "{" + "\"name\":\"Relay\"," + "\"icon\":\"lightbulb-o\"," + "\"inports\":[{\"name\":\"State\",\"type\":\"bool\"}, {\"name\":\"Toggle\",\"type\":\"any\"}]," + "\"outports\":[{\"name\":\"Data\",\"type\":\"int\"}]," + "\"properties\":[{\"name\":\"Relay\",\"type\":\"list\"}]" + "}"; + class FlowRelayComponent : public FlowComponent { private: int _relay_id; @@ -1110,11 +1128,9 @@ void relaySetup() { relays->push_back(String(i)); } - flowRegisterComponent("Relay", "lightbulb-o", (flow_component_factory_f)([] (JsonObject& properties) { return new FlowRelayComponent(properties); })) - ->addInput("State", BOOL) - ->addInput("Toggle", ANY) - ->addProperty("Relay", relays) - ; + flowRegisterComponent("Relay", &flow_relay_component, flow_relay_component_json, + (flow_component_factory_f)([] (JsonObject& properties) { return new FlowRelayComponent(properties); })); + flowRegisterComponentValues("Relay", "Relay", relays); #endif diff --git a/code/espurna/scheduler.ino b/code/espurna/scheduler.ino index 88637b3e21..4db2ccf9cb 100644 --- a/code/espurna/scheduler.ino +++ b/code/espurna/scheduler.ino @@ -51,6 +51,21 @@ void _schWebSocketOnSend(JsonObject &root){ #if FLOW_SUPPORT +PROGMEM const FlowConnections flow_schedule_component = { + 0, NULL, + 1, flow_data_array, +}; + +PROGMEM const char flow_schedule_component_json[] = + "\"Schedule\": " + "{" + "\"name\":\"Schedule\"," + "\"icon\":\"calendar\"," + "\"inports\":[]," + "\"outports\":[{\"name\":\"Data\",\"type\":\"bool\"}]," + "\"properties\":[{\"name\":\"Time\",\"type\":\"time\"}, {\"name\":\"Weekdays\",\"type\":\"weekdays\"}]" + "}"; + class FlowScheduleComponent; std::vector _schedule_components; @@ -262,11 +277,8 @@ void schSetup() { #endif #if FLOW_SUPPORT - flowRegisterComponent("Schedule", "calendar", (flow_component_factory_f)([] (JsonObject& properties) { return new FlowScheduleComponent(properties); })) - ->addOutput("Event", BOOL) - ->addProperty("Time", TIME) - ->addProperty("Weekdays", WEEKDAYS) - ; + flowRegisterComponent("Schedule", &flow_schedule_component, flow_schedule_component_json, + (flow_component_factory_f)([] (JsonObject& properties) { return new FlowScheduleComponent(properties); })); #endif // Main callbacks diff --git a/code/espurna/sensor.ino b/code/espurna/sensor.ino index fafb4a3aca..7bf020de0c 100644 --- a/code/espurna/sensor.ino +++ b/code/espurna/sensor.ino @@ -347,6 +347,21 @@ void _sensorInitCommands() { #if FLOW_SUPPORT +PROGMEM const FlowConnections flow_sensor_component = { + 0, NULL, + 1, flow_data_array, +}; + +PROGMEM const char flow_sensor_component_json[] = + "\"Sensor\": " + "{" + "\"name\":\"Sensor\"," + "\"icon\":\"thermometer-3\"," + "\"inports\":[]," + "\"outports\":[{\"name\":\"Data\",\"type\":\"double\"}]," + "\"properties\":[{\"name\":\"Sensor\",\"type\":\"list\"}]" + "}"; + class FlowSensorComponent; std::vector _flow_sensors; @@ -1388,10 +1403,9 @@ void sensorSetup() { sensors->push_back(sensor + "/" + topic); } - flowRegisterComponent("Sensor", "thermometer-3", (flow_component_factory_f)([] (JsonObject& properties) { return new FlowSensorComponent(properties); })) - ->addOutput("Data", DOUBLE) - ->addProperty("Sensor", sensors) - ; + flowRegisterComponent("Sensor", &flow_sensor_component, flow_sensor_component_json, + (flow_component_factory_f)([] (JsonObject& properties) { return new FlowSensorComponent(properties); })); + flowRegisterComponentValues("Sensor", "Sensor", sensors); #endif // Main callbacks diff --git a/code/espurna/web.ino b/code/espurna/web.ino index 348a0d2001..40d5bfcf51 100644 --- a/code/espurna/web.ino +++ b/code/espurna/web.ino @@ -376,28 +376,28 @@ void _onGetFlowLibrary(AsyncWebServerRequest *request) { return size; } - if (i == 0) { - buffer[0] = '{'; - buffer++; size++; maxLen--; - } + buffer[0] = i == 0 ? '{' : ','; + buffer++; size++; maxLen--; - FlowComponentType* component = flowGetComponent(i); - if (component != NULL) { - if (i > 0) { - buffer[0] = ','; - buffer++; size++; maxLen--; - } + const char* json = flowGetComponentJson(i); + if (json != NULL) { + strncpy_P((char*)buffer, json, maxLen); + int count = strnlen_P(json, maxLen); + size += count; //maxLen -= count; buffer += count; - int count = sprintf((char*)buffer, "\"%s\": ", component->name().c_str()); - size += count; maxLen -= count; buffer += count; + i++; + } else { + // last chunk is always list values array + strcpy((char*)buffer, "_values: "); + buffer+=9; size+=9; maxLen-=9; DynamicJsonBuffer jsonBuffer; - JsonObject& root = jsonBuffer.createObject(); - component->toJSON(root); - size += root.printTo((char*)buffer, maxLen); + JsonArray& values = jsonBuffer.createArray(); + flowGetComponentValuesJson(values); + int count = values.printTo((char*)buffer, maxLen); + size += count; + buffer += count; - i++; - } else { buffer[0] = '}'; size++; i = -1; // last chunk From 2a1a09cb3e1fc8c942091d751a9cdf525649ffdd Mon Sep 17 00:00:00 2001 From: Eugene Schava Date: Sun, 17 Feb 2019 01:04:41 +0200 Subject: [PATCH 22/40] flow: memory optimizations and refactoring --- code/espurna/web.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/espurna/web.ino b/code/espurna/web.ino index 40d5bfcf51..cc3c238bf0 100644 --- a/code/espurna/web.ino +++ b/code/espurna/web.ino @@ -388,8 +388,8 @@ void _onGetFlowLibrary(AsyncWebServerRequest *request) { i++; } else { // last chunk is always list values array - strcpy((char*)buffer, "_values: "); - buffer+=9; size+=9; maxLen-=9; + strcpy((char*)buffer, "\"_values\": "); + buffer+=11; size+=11; maxLen-=11; DynamicJsonBuffer jsonBuffer; JsonArray& values = jsonBuffer.createArray(); From cb2cb5ea79c70c26bc23e0004a18c1558b855136 Mon Sep 17 00:00:00 2001 From: Eugene Schava Date: Mon, 18 Feb 2019 22:05:47 +0200 Subject: [PATCH 23/40] flow: memory optimizations and refactoring --- code/espurna/flow.h | 6 +++--- code/espurna/flow.ino | 36 ++++++++++++++++++------------------ code/espurna/relay.ino | 2 +- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/code/espurna/flow.h b/code/espurna/flow.h index 38e375768c..94bd4896d4 100644 --- a/code/espurna/flow.h +++ b/code/espurna/flow.h @@ -88,12 +88,12 @@ class FlowComponentLibrary { return _jsons[index]; } - FlowComponent* createComponent(String name, JsonObject& properties) { + FlowComponent* createComponent(String& name, JsonObject& properties) { flow_component_factory_f& factory = _factoryMap[name]; return factory != NULL ? factory(properties) : NULL; } - int getInputNumber(String name, String input) { + int getInputNumber(String& name, String& input) { const FlowConnections* connections = _connectionsMap[name]; if (connections == NULL) return -1; @@ -108,7 +108,7 @@ class FlowComponentLibrary { return -1; } - int getOutputNumber(String name, String output) { + int getOutputNumber(String& name, String& output) { const FlowConnections* connections = _connectionsMap[name]; if (connections == NULL) return -1; diff --git a/code/espurna/flow.ino b/code/espurna/flow.ino index 9f28592b7e..7856be179d 100644 --- a/code/espurna/flow.ino +++ b/code/espurna/flow.ino @@ -2,7 +2,7 @@ FLOW MODULE -Copyright (C) 2016-2018 by Xose Pérez +Copyright (C) 2016-2018 by Xose P�rez */ @@ -491,25 +491,25 @@ class FlowHysteresisComponent : public FlowComponent { } }; -void _flowMQTTCallback(unsigned int type, const char * topic, const char * payload) { - - if (type == MQTT_CONNECT_EVENT) { - mqttSubscribe("flow"); - } - - if (type == MQTT_MESSAGE_EVENT) { - - // Match topic - String t = mqttMagnitude((char *) topic); - if (t.equals("flow")) { - flowStart(); - } - - } -} +//void _flowMQTTCallback(unsigned int type, const char * topic, const char * payload) { +// +// if (type == MQTT_CONNECT_EVENT) { +// mqttSubscribe("flow"); +// } +// +// if (type == MQTT_MESSAGE_EVENT) { +// +// // Match topic +// String t = mqttMagnitude((char *) topic); +// if (t.equals("flow")) { +// flowStart(); +// } +// +// } +//} void flowSetup() { - mqttRegister(_flowMQTTCallback); +// mqttRegister(_flowMQTTCallback); flowRegisterComponent("Start", &flow_start_component, flow_start_component_json, (flow_component_factory_f)([] (JsonObject& properties) { return new FlowStartComponent(properties); })); diff --git a/code/espurna/relay.ino b/code/espurna/relay.ino index 69f9e879ca..a1b4180131 100644 --- a/code/espurna/relay.ino +++ b/code/espurna/relay.ino @@ -1036,7 +1036,7 @@ PROGMEM const char flow_relay_component_json[] = "\"name\":\"Relay\"," "\"icon\":\"lightbulb-o\"," "\"inports\":[{\"name\":\"State\",\"type\":\"bool\"}, {\"name\":\"Toggle\",\"type\":\"any\"}]," - "\"outports\":[{\"name\":\"Data\",\"type\":\"int\"}]," + "\"outports\":[]," "\"properties\":[{\"name\":\"Relay\",\"type\":\"list\"}]" "}"; From f59ae97e4db14bf3506aec7a60bbdb0ad463d970 Mon Sep 17 00:00:00 2001 From: Eugene Schava Date: Wed, 20 Feb 2019 22:59:59 +0200 Subject: [PATCH 24/40] flow: have Value property for start/timer/schedule components --- code/espurna/flow.ino | 18 ++++++++++++------ code/espurna/scheduler.ino | 9 ++++++--- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/code/espurna/flow.ino b/code/espurna/flow.ino index 7856be179d..a8c93d023e 100644 --- a/code/espurna/flow.ino +++ b/code/espurna/flow.ino @@ -188,21 +188,24 @@ PROGMEM const char flow_start_component_json[] = "\"icon\":\"play\"," "\"inports\":[]," "\"outports\":[{\"name\":\"Data\",\"type\":\"bool\"}]," - "\"properties\":[]" + "\"properties\":[{\"name\":\"Value\",\"type\":\"any\"}]" "}"; class FlowStartComponent : public FlowComponent { private: - JsonVariant *_data = new JsonVariant(true); + JsonVariant *_value; public: FlowStartComponent(JsonObject& properties) { + JsonVariant value = properties["Value"]; + _value = clone(value); + flow_scheduled_task_callback_f callback = [this](unsigned long time, JsonVariant *data){ this->onStart(); }; _flow_scheduled_tasks_queue.insert({NULL, 0, callback}); } void onStart() { - processOutput(*_data, 0); + processOutput(*_value, 0); } }; @@ -353,19 +356,22 @@ PROGMEM const char flow_timer_component_json[] = "\"icon\":\"clock-o\"," "\"inports\":[]," "\"outports\":[{\"name\":\"Data\",\"type\":\"bool\"}]," - "\"properties\":[{\"name\":\"Seconds\",\"type\":\"int\"}]" + "\"properties\":[{\"name\":\"Seconds\",\"type\":\"int\"},{\"name\":\"Value\",\"type\":\"any\"}]" "}"; PROGMEM const char flow_incorrect_timer_delay[] = "[FLOW] Incorrect timer delay: %i\n"; class FlowTimerComponent : public FlowComponent { private: - JsonVariant *_data = new JsonVariant(true); + JsonVariant *_value; unsigned long _period; flow_scheduled_task_callback_f _callback; public: FlowTimerComponent(JsonObject& properties) { + JsonVariant value = properties["Value"]; + _value = clone(value); + int seconds = properties["Seconds"]; _period = 1000 * (int)seconds; @@ -378,7 +384,7 @@ class FlowTimerComponent : public FlowComponent { } void onSchedule(unsigned long now) { - processOutput(*_data, 0); + processOutput(*_value, 0); // reschedule _flow_scheduled_tasks_queue.insert({NULL, now + _period, _callback}); diff --git a/code/espurna/scheduler.ino b/code/espurna/scheduler.ino index 4db2ccf9cb..aa0b4d08d7 100644 --- a/code/espurna/scheduler.ino +++ b/code/espurna/scheduler.ino @@ -63,7 +63,7 @@ PROGMEM const char flow_schedule_component_json[] = "\"icon\":\"calendar\"," "\"inports\":[]," "\"outports\":[{\"name\":\"Data\",\"type\":\"bool\"}]," - "\"properties\":[{\"name\":\"Time\",\"type\":\"time\"}, {\"name\":\"Weekdays\",\"type\":\"weekdays\"}]" + "\"properties\":[{\"name\":\"Time\",\"type\":\"time\"}, {\"name\":\"Weekdays\",\"type\":\"weekdays\"},{\"name\":\"Value\",\"type\":\"any\"}]" "}"; class FlowScheduleComponent; @@ -71,13 +71,16 @@ std::vector _schedule_components; class FlowScheduleComponent : public FlowComponent { private: - JsonVariant *_data = new JsonVariant(true); + JsonVariant *_value; String _weekdays; int _hour; int _minute; public: FlowScheduleComponent(JsonObject& properties) { + JsonVariant value = properties["Value"]; + _value = clone(value); + _weekdays = String((const char *)properties["Weekdays"]); String time = String((const char *)properties["Time"]); int colon = time.indexOf(":"); @@ -91,7 +94,7 @@ class FlowScheduleComponent : public FlowComponent { void check(time_t& time) { if (_schMinutesLeft(time, _hour, _minute) == 0 && (_weekdays.length() == 0 || _schIsThisWeekday(time, _weekdays))) { - processOutput(*_data, 0); + processOutput(*_value, 0); } } }; From 5bc2e7e3e30cd4cca350f8c66efaa577610cbc36 Mon Sep 17 00:00:00 2001 From: Eugene Schava Date: Wed, 20 Feb 2019 23:43:37 +0200 Subject: [PATCH 25/40] flow: setting load/save components --- code/espurna/settings.ino | 90 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/code/espurna/settings.ino b/code/espurna/settings.ino index 8d323d0217..b06ae2eabf 100644 --- a/code/espurna/settings.ino +++ b/code/espurna/settings.ino @@ -211,6 +211,91 @@ void settingsGetJson(JsonObject& root) { } +// ----------------------------------------------------------------------------- +// FLOW +// ----------------------------------------------------------------------------- + +#if FLOW_SUPPORT + +//PROGMEM const char flow_value[] = "Value"; +PROGMEM const char* const flow_value_array[] = {flow_value}; + +PROGMEM const FlowConnections flow_save_setting_component = { + 1, flow_value_array, + 0, NULL, +}; + +PROGMEM const char flow_save_setting_component_json[] = + "\"Save setting\": " + "{" + "\"name\":\"Save setting\"," + "\"icon\":\"save\"," + "\"inports\":[{\"name\":\"Value\",\"type\":\"string\"}]," + "\"outports\":[]," + "\"properties\":[{\"name\":\"Name\",\"type\":\"string\"}]" + "}"; + +class FlowSaveSettingComponent : public FlowComponent { + private: + String _name; + + public: + FlowSaveSettingComponent(JsonObject& properties) { + const char * name = properties["Name"]; + _name = String(name != NULL ? name : ""); + } + + virtual void processInput(JsonVariant& data, int inputNumber) { + setSetting(_name, data.as()); + } + + static void reg() { + flowRegisterComponent("Save setting", &flow_save_setting_component, flow_save_setting_component_json, + (flow_component_factory_f)([] (JsonObject& properties) { return new FlowSaveSettingComponent(properties); })); + } +}; + +PROGMEM const char flow_name[] = "Name"; +PROGMEM const char* const flow_name_array[] = {flow_name}; + +PROGMEM const FlowConnections flow_load_setting_component = { + 1, flow_name_array, + 1, flow_value_array, +}; + +PROGMEM const char flow_load_setting_component_json[] = + "\"Load setting\": " + "{" + "\"name\":\"Load setting\"," + "\"icon\":\"database\"," + "\"inports\":[{\"name\":\"Name\",\"type\":\"string\"}]," + "\"outports\":[{\"name\":\"Value\",\"type\":\"string\"}]," + "\"properties\":[{\"name\":\"Default\",\"type\":\"string\"}]" + "}"; + +class FlowLoadSettingComponent : public FlowComponent { + private: + String _default; + + public: + FlowLoadSettingComponent(JsonObject& properties) { + const char * def = properties["Default"]; + _default = String(def != NULL ? def : ""); + } + + virtual void processInput(JsonVariant& data, int inputNumber) { + JsonVariant value(getSetting(data.as(), _default)); + processOutput(value, 0); + } + + static void reg() { + flowRegisterComponent("Load setting", &flow_load_setting_component, flow_load_setting_component_json, + (flow_component_factory_f)([] (JsonObject& properties) { return new FlowLoadSettingComponent(properties); })); + } +}; + +#endif // FLOW_SUPPORT + // ----------------------------------------------------------------------------- // Initialization // ----------------------------------------------------------------------------- @@ -228,4 +313,9 @@ void settingsSetup() { #endif ); + #if FLOW_SUPPORT + FlowSaveSettingComponent::reg(); + FlowLoadSettingComponent::reg(); + #endif + } \ No newline at end of file From 61a0b77d1cadd7c42cdf4f3f6e8605fac5442b89 Mon Sep 17 00:00:00 2001 From: Eugene Schava Date: Thu, 21 Feb 2019 01:05:42 +0200 Subject: [PATCH 26/40] flow: setting load/save components --- code/espurna/espurna.ino | 2 ++ code/espurna/flow.ino | 2 +- code/espurna/settings.ino | 23 +++++++++++++++-------- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/code/espurna/espurna.ino b/code/espurna/espurna.ino index fa601646d5..08d2fa347b 100644 --- a/code/espurna/espurna.ino +++ b/code/espurna/espurna.ino @@ -117,6 +117,8 @@ void setup() { #if FLOW_SUPPORT // register default flow components first flowSetup(); + // settings component after + settingsFlowSetup(); #endif // lightSetup must be called before relaySetup diff --git a/code/espurna/flow.ino b/code/espurna/flow.ino index a8c93d023e..79747adfc1 100644 --- a/code/espurna/flow.ino +++ b/code/espurna/flow.ino @@ -250,7 +250,7 @@ class FlowDebugComponent : public FlowComponent { dtostrf(data.as(), 1 - sizeof(buffer), 3, buffer); DEBUG_MSG_P(flow_debug_string, _prefix.c_str(), buffer); } else if (data.is()) { - DEBUG_MSG_P(flow_debug_string, _prefix.c_str(), data.as() ? "true" : "false"); + DEBUG_MSG_P(flow_debug_string, _prefix.c_str(), data.as() ? "" : ""); } else if (data.is()) { DEBUG_MSG_P(flow_debug_string, _prefix.c_str(), data.as()); } else { diff --git a/code/espurna/settings.ino b/code/espurna/settings.ino index b06ae2eabf..29b7598541 100644 --- a/code/espurna/settings.ino +++ b/code/espurna/settings.ino @@ -246,7 +246,12 @@ class FlowSaveSettingComponent : public FlowComponent { } virtual void processInput(JsonVariant& data, int inputNumber) { - setSetting(_name, data.as()); + String value = data.as(); + if (value != "") { + setSetting(_name, value); + } else { + delSetting(_name); + } } static void reg() { @@ -284,8 +289,10 @@ class FlowLoadSettingComponent : public FlowComponent { } virtual void processInput(JsonVariant& data, int inputNumber) { - JsonVariant value(getSetting(data.as(), _default)); - processOutput(value, 0); + String name = data.as(); + String value = getSetting(name, _default); + JsonVariant output(value.c_str()); + processOutput(output, 0); } static void reg() { @@ -294,6 +301,11 @@ class FlowLoadSettingComponent : public FlowComponent { } }; +void settingsFlowSetup() { + FlowSaveSettingComponent::reg(); + FlowLoadSettingComponent::reg(); +} + #endif // FLOW_SUPPORT // ----------------------------------------------------------------------------- @@ -313,9 +325,4 @@ void settingsSetup() { #endif ); - #if FLOW_SUPPORT - FlowSaveSettingComponent::reg(); - FlowLoadSettingComponent::reg(); - #endif - } \ No newline at end of file From 90b90a23fbaa37ab7eaee6ac46f87f1bdf752261 Mon Sep 17 00:00:00 2001 From: Eugene Schava Date: Thu, 21 Feb 2019 18:27:25 +0200 Subject: [PATCH 27/40] flow: use MQTT retained message to save flow config if SPIFFS is not available --- code/espurna/config/general.h | 8 +++ code/espurna/espurna.ino | 2 +- code/espurna/flow.ino | 112 ++++++++++++++++++++++------------ code/espurna/mqtt.ino | 15 ++++- code/espurna/web.ino | 6 +- 5 files changed, 100 insertions(+), 43 deletions(-) diff --git a/code/espurna/config/general.h b/code/espurna/config/general.h index 08bb4c56af..b22d6a96be 100644 --- a/code/espurna/config/general.h +++ b/code/espurna/config/general.h @@ -1491,3 +1491,11 @@ #ifndef FLOW_SUPPORT #define FLOW_SUPPORT 0 #endif + +#ifndef FLOW_SPIFFS_FILE +#define FLOW_SPIFFS_FILE "/flow.json" +#endif + +#ifndef FLOW_MQTT_TOPIC +#define FLOW_MQTT_TOPIC "flow" +#endif \ No newline at end of file diff --git a/code/espurna/espurna.ino b/code/espurna/espurna.ino index 08d2fa347b..4bf25f4e85 100644 --- a/code/espurna/espurna.ino +++ b/code/espurna/espurna.ino @@ -196,7 +196,7 @@ void setup() { #if UART_MQTT_SUPPORT uartmqttSetup(); #endif - #if FLOW_SUPPORT + #if FLOW_SUPPORT && SPIFFS_SUPPORT flowStart(); #endif diff --git a/code/espurna/flow.ino b/code/espurna/flow.ino index 79747adfc1..7e4cc262de 100644 --- a/code/espurna/flow.ino +++ b/code/espurna/flow.ino @@ -16,6 +16,11 @@ Copyright (C) 2016-2018 by Xose P�rez // FLOW // ----------------------------------------------------------------------------- +#if !SPIFFS_SUPPORT +String _flow; +unsigned long _mqtt_flow_sent_at = 0; +#endif + FlowComponentLibrary _library; typedef struct { @@ -46,19 +51,40 @@ void flowGetComponentValuesJson(JsonArray& root) { } } -String flowGetDataPath() { - return "/flow.json"; // TODO: file name to constant +AsyncWebServerResponse* flowGetConfigResponse(AsyncWebServerRequest *request) { + #if SPIFFS_SUPPORT + return request->beginResponse(SPIFFS, FLOW_SPIFFS_FILE, "text/json"); + #else + return request->beginResponse(200, "text/json", _flow); + #endif } -bool flowSaveData(char* data) { +bool flowSaveConfig(char* data) { bool result = false; - File file = SPIFFS.open("/flow.json", "w"); // TODO: file name to constant - if (file) { - result = file.print(data); - file.close(); - } else { - DEBUG_MSG_P(PSTR("[FLOW] Error saving data\n")); - } + + #if SPIFFS_SUPPORT + File file = SPIFFS.open(FLOW_SPIFFS_FILE, "w"); + if (file) { + result = file.print(data); + file.close(); + } else { + DEBUG_MSG_P(PSTR("[FLOW] Error saving flow to file\n")); + } + #elif MQTT_SUPPORT + result = mqttConnected(); + _flow = String(data); + if (result) { + _mqtt_flow_sent_at = millis(); + mqttSendRaw(mqttTopic("flow", true).c_str(), data, true); + } + else { + DEBUG_MSG_P(PSTR("[FLOW] Error publishing flow because MQTT is disconnected\n")); + } + #else + _flow = String(data); + DEBUG_MSG_P(PSTR("[FLOW] Error saving flow\n")); + #endif + return result; } @@ -69,17 +95,24 @@ const char* flowGetComponentJson(int index) { void flowStart() { DEBUG_MSG_P(PSTR("[FLOW] Starting\n")); - File file = SPIFFS.open("/flow.json", "r"); // TODO: file name to constant - if (file) { - DynamicJsonBuffer jsonBuffer; - JsonObject& root = jsonBuffer.parseObject(file); - if (root.success()) _flowStart(root); - else DEBUG_MSG_P(PSTR("[FLOW] Error: flow cannot be parsed as correct JSON\n")); - - file.close(); - } else { - DEBUG_MSG_P(PSTR("[FLOW] No flow found\n")); - } + #if SPIFFS_SUPPORT + File source = SPIFFS.open(FLOW_SPIFFS_FILE, "r"); + if (!source) { + DEBUG_MSG_P(PSTR("[FLOW] No flow file found\n")); + return; + } + #else + String& source = _flow; + #endif + + DynamicJsonBuffer jsonBuffer; + JsonObject& root = jsonBuffer.parseObject(source); + if (root.success()) _flowStart(root); + else DEBUG_MSG_P(PSTR("[FLOW] Error: flow cannot be parsed as correct JSON\n")); + + #if SPIFFS_SUPPORT + source.close(); + #endif } void _flowStart(JsonObject& data) { @@ -497,25 +530,28 @@ class FlowHysteresisComponent : public FlowComponent { } }; -//void _flowMQTTCallback(unsigned int type, const char * topic, const char * payload) { -// -// if (type == MQTT_CONNECT_EVENT) { -// mqttSubscribe("flow"); -// } -// -// if (type == MQTT_MESSAGE_EVENT) { -// -// // Match topic -// String t = mqttMagnitude((char *) topic); -// if (t.equals("flow")) { -// flowStart(); -// } -// -// } -//} +#if !SPIFFS_SUPPORT && MQTT_SUPPORT +void _flowMQTTCallback(unsigned int type, const char * topic, const char * payload) { + + if (type == MQTT_CONNECT_EVENT) { + mqttSubscribe(FLOW_MQTT_TOPIC); + } + + if (type == MQTT_MESSAGE_EVENT) { + // Match topic + String t = mqttMagnitude((char *) topic); + if (t.equals(FLOW_MQTT_TOPIC) && millis() - _mqtt_flow_sent_at > MQTT_SKIP_TIME) { + _flow = String(payload); + flowStart(); + } + } +} +#endif void flowSetup() { -// mqttRegister(_flowMQTTCallback); + #if !SPIFFS_SUPPORT && MQTT_SUPPORT + mqttRegister(_flowMQTTCallback); + #endif flowRegisterComponent("Start", &flow_start_component, flow_start_component_json, (flow_component_factory_f)([] (JsonObject& properties) { return new FlowStartComponent(properties); })); diff --git a/code/espurna/mqtt.ino b/code/espurna/mqtt.ino index 265f896028..8f10f85fcf 100644 --- a/code/espurna/mqtt.ino +++ b/code/espurna/mqtt.ino @@ -406,6 +406,11 @@ class FlowMqttSubscribeComponent : public FlowComponent { mqtt_callback_f callback = [this](unsigned int type, const char * topic, const char * payload){ this->mqttCallback(type, topic, payload); }; mqttRegister(callback); + + // emulate connect event if MQTT is connected already + if (mqttConnected) { + mqttCallback(MQTT_CONNECT_EVENT, NULL, NULL); + } } }; @@ -516,7 +521,15 @@ void _mqttOnMessage(char* topic, char* payload, unsigned int len) { strlcpy(message, (char *) payload, len + 1); #if MQTT_SKIP_RETAINED - if (millis() - _mqtt_connected_at < MQTT_SKIP_TIME) { + bool skip = millis() - _mqtt_connected_at < MQTT_SKIP_TIME; + + #if FLOW_SUPPORT + // workaround for persisted flow + if (skip && mqttMagnitude((char *) topic).equals(FLOW_MQTT_TOPIC)) + skip = false; + #endif + + if (skip) { DEBUG_MSG_P(PSTR("[MQTT] Received %s => %s - SKIPPED\n"), topic, message); return; } diff --git a/code/espurna/web.ino b/code/espurna/web.ino index cc3c238bf0..b182f67efc 100644 --- a/code/espurna/web.ino +++ b/code/espurna/web.ino @@ -421,7 +421,7 @@ void _onGetFlowConfig(AsyncWebServerRequest *request) { return request->requestAuthentication(getSetting("hostname").c_str()); } - AsyncWebServerResponse *response = request->beginResponse(SPIFFS, flowGetDataPath(), "text/json"); + AsyncWebServerResponse *response = flowGetConfigResponse(request); response->addHeader("X-XSS-Protection", "1; mode=block"); response->addHeader("X-Content-Type-Options", "nosniff"); @@ -442,7 +442,7 @@ void _onPostFlowConfigData(AsyncWebServerRequest *request, String filename, size // No buffer if (final && (index == 0)) { data[len] = 0; - _webFlowSuccess = flowSaveData((char *) data); + _webFlowSuccess = flowSaveConfig((char *) data); return; } @@ -464,7 +464,7 @@ void _onPostFlowConfigData(AsyncWebServerRequest *request, String filename, size // Ending if (final) { _webFlowBuffer->push_back(0); - _webFlowSuccess = flowSaveData((char *) _webFlowBuffer->data()); + _webFlowSuccess = flowSaveConfig((char *) _webFlowBuffer->data()); delete _webFlowBuffer; } } From dd9509a20c816de82a11f11d6f6a20d119a2670d Mon Sep 17 00:00:00 2001 From: Eugene Schava Date: Thu, 21 Feb 2019 18:29:23 +0200 Subject: [PATCH 28/40] flow: use MQTT retained message to save flow config if SPIFFS is not available --- code/espurna/flow.ino | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/code/espurna/flow.ino b/code/espurna/flow.ino index 7e4cc262de..4cd84fcf46 100644 --- a/code/espurna/flow.ino +++ b/code/espurna/flow.ino @@ -21,6 +21,7 @@ String _flow; unsigned long _mqtt_flow_sent_at = 0; #endif +bool _flow_started = false; FlowComponentLibrary _library; typedef struct { @@ -93,6 +94,11 @@ const char* flowGetComponentJson(int index) { } void flowStart() { + if (_flow_started) { + DEBUG_MSG_P(PSTR("[FLOW] Started already\n")); + return; + } + DEBUG_MSG_P(PSTR("[FLOW] Starting\n")); #if SPIFFS_SUPPORT @@ -113,6 +119,8 @@ void flowStart() { #if SPIFFS_SUPPORT source.close(); #endif + + _flow_started = true; } void _flowStart(JsonObject& data) { From fe49b5611a90dd0f65889665bc6b8b574bdad323 Mon Sep 17 00:00:00 2001 From: Eugene Schava Date: Fri, 22 Feb 2019 00:33:52 +0200 Subject: [PATCH 29/40] flow: math component --- code/espurna/flow.ino | 85 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/code/espurna/flow.ino b/code/espurna/flow.ino index 4cd84fcf46..06994bd214 100644 --- a/code/espurna/flow.ino +++ b/code/espurna/flow.ino @@ -335,6 +335,89 @@ class FlowChangeComponent : public FlowComponent { } }; +// ----------------------------------------------------------------------------- +// Math component +// ----------------------------------------------------------------------------- + +PROGMEM const char flow_input1[] = "Input 1"; +PROGMEM const char flow_input2[] = "Input 2"; +PROGMEM const char* const flow_inputs_array[] = {flow_input1, flow_input2}; + +PROGMEM const FlowConnections flow_math_component = { + 2, flow_inputs_array, + 1, flow_data_array, +}; + +PROGMEM const char flow_math_component_json[] = + "\"Math\": " + "{" + "\"name\":\"Math\"," + "\"icon\":\"plus-circle\"," + "\"inports\":[{\"name\":\"Input 1\",\"type\":\"any\"},{\"name\":\"Input 2\",\"type\":\"any\"}]," + "\"outports\":[{\"name\":\"Data\",\"type\":\"any\"}]," + "\"properties\":[{\"name\":\"Operation\",\"type\":\"list\"," + "\"values\":[\"+\",\"-\",\"*\",\"/\"]}]" + "}"; + + +class FlowMathComponent : public FlowComponent { + private: + String _operation; + JsonVariant *_input1, *_input2; + + public: + FlowMathComponent(JsonObject& properties) { + const char * operation = properties["Operation"]; + _operation = String(operation != NULL ? operation : ""); + } + + virtual void processInput(JsonVariant& data, int inputNumber) { + if (inputNumber == 0) { + if (_input1 != NULL) release(_input1); + _input1 = clone(data); + } else if (inputNumber == 1) { + if (_input2 != NULL) release(_input2); + _input2 = clone(data); + } + + if (_input1 != NULL && _input2 != NULL) { + if (_input1->is()) { + JsonVariant r( + _operation.equals("+") ? _input1->as() + _input2->as() : + _operation.equals("-") ? _input1->as() - _input2->as() : + _operation.equals("*") ? _input1->as() * _input2->as() : + /*_operation.equals("/") ?*/ _input1->as() / _input2->as() + ); + processOutput(r, 0); + } else if (_input1->is()) { + JsonVariant r( + _operation.equals("+") ? _input1->as() + _input2->as() : + _operation.equals("-") ? _input1->as() - _input2->as() : + _operation.equals("*") ? _input1->as() * _input2->as() : + /*_operation.equals("/") ?*/ _input1->as() / _input2->as() + ); + processOutput(r, 0); + } else if (_input1->is()) { + // only + is supported + const char *s1 = _input1->as(); + const char *s2 = _input2->as(); + char *concat = new char[strlen(s1) + (s2 != NULL ? strlen(s2) : 0) + 1]; + strcpy(concat, s1); + if (s2 != NULL) + strcat(concat, s2); + JsonVariant r(concat); + processOutput(r, 0); + free(concat); + } + } + } + + static void reg() { + flowRegisterComponent("Math", &flow_math_component, flow_math_component_json, + (flow_component_factory_f)([] (JsonObject& properties) { return new FlowMathComponent(properties); })); + } +}; + // ----------------------------------------------------------------------------- // Delay component // ----------------------------------------------------------------------------- @@ -570,6 +653,8 @@ void flowSetup() { flowRegisterComponent("Change", &flow_change_component, flow_change_component_json, (flow_component_factory_f)([] (JsonObject& properties) { return new FlowChangeComponent(properties); })); + FlowMathComponent::reg(); + flowRegisterComponent("Delay", &flow_delay_component, flow_delay_component_json, (flow_component_factory_f)([] (JsonObject& properties) { return new FlowDelayComponent(properties); })); From 64dfb47f8b4fabd265bd3e72a88e2c9d8c7315ee Mon Sep 17 00:00:00 2001 From: Eugene Schava Date: Fri, 22 Feb 2019 17:55:46 +0200 Subject: [PATCH 30/40] better compression of flow --- code/espurna/flow.ino | 122 +++++++++++++++++++++++++++--------------- 1 file changed, 78 insertions(+), 44 deletions(-) diff --git a/code/espurna/flow.ino b/code/espurna/flow.ino index 06994bd214..f9b1f5927f 100644 --- a/code/espurna/flow.ino +++ b/code/espurna/flow.ino @@ -123,64 +123,98 @@ void flowStart() { _flow_started = true; } +void _flowAddConnection(std::map& components, std::map& componentsNames, + String& srcProcess, String& srcPort, String& tgtProcess, String& tgtPort) { + FlowComponent* srcComponent = components[srcProcess]; + if (srcComponent == NULL) { + DEBUG_MSG_P(PSTR("[FLOW] Error: component ID='%s' is not registered\n"), srcProcess.c_str()); + return; + } + + FlowComponent* tgtComponent = components[tgtProcess]; + if (tgtComponent == NULL) { + DEBUG_MSG_P(PSTR("[FLOW] Error: component ID='%s' is not registered\n"), tgtProcess.c_str()); + return; + } + + int srcNumber = _library.getOutputNumber(componentsNames[srcProcess], srcPort); + if (srcNumber < 0) { + DEBUG_MSG_P(PSTR("[FLOW] Error: component '%s' has no output named '%s'\n"), componentsNames[srcProcess].c_str(), srcPort.c_str()); + return; + } + + int tgtNumber = _library.getInputNumber(componentsNames[tgtProcess], tgtPort); + if (tgtNumber < 0) { + DEBUG_MSG_P(PSTR("[FLOW] Error: component '%s' has no input named '%s'\n"), componentsNames[tgtProcess].c_str(), tgtPort.c_str()); + return; + } + + srcComponent->addOutput(srcNumber, tgtComponent, tgtNumber); +} + void _flowStart(JsonObject& data) { std::map components; std::map componentsNames; - JsonObject& processes = data.containsKey("P") ? data["P"] : data["processes"]; - for (auto process_kv: processes) { - String id = process_kv.key; - JsonObject& value = process_kv.value; + JsonVariant processes = data.containsKey("P") ? data["P"] : data["processes"]; + if (processes.is()) { + for (auto process_kv: processes.as()) { + String id = process_kv.key; + JsonObject& value = process_kv.value; - String componentName = value.containsKey("C") ? value["C"] : value["component"]; - JsonObject& metadata = value.containsKey("M") ? value["M"] : value["metadata"]; - JsonObject& properties = metadata.containsKey("R") ? metadata["R"] : metadata["properties"]; + String componentName = value.containsKey("C") ? value["C"] : value["component"]; + JsonObject& metadata = value.containsKey("M") ? value["M"] : value["metadata"]; + JsonObject& properties = metadata.containsKey("R") ? metadata["R"] : metadata["properties"]; - FlowComponent* component = _library.createComponent(componentName, properties); + FlowComponent* component = _library.createComponent(componentName, properties); - if (component != NULL) { - components[id] = component; - componentsNames[id] = componentName; - } else { - DEBUG_MSG_P(PSTR("[FLOW] Error: component '%s' is not registered\n"), componentName.c_str()); + if (component != NULL) { + components[id] = component; + componentsNames[id] = componentName; + } else { + DEBUG_MSG_P(PSTR("[FLOW] Error: component '%s' is not registered\n"), componentName.c_str()); + } } - } + } else { + for (JsonArray& process: processes.as()) { + String id = process[0]; + String componentName = process[1]; + JsonObject& properties = process[5]; - JsonArray& connections = data.containsKey("X") ? data["X"] : data["connections"]; - for (JsonObject& connection: connections) { - JsonObject& src = connection.containsKey("S") ? connection["S"] : connection["src"]; - String srcProcess = src.containsKey("I") ? src["I"] : src["process"]; - String srcPort = src.containsKey("N") ? src["N"] : src["port"]; - - JsonObject& tgt = connection.containsKey("T") ? connection["T"] : connection["tgt"]; - String tgtProcess = tgt.containsKey("I") ? tgt["I"] : tgt["process"]; - String tgtPort = tgt.containsKey("N") ? tgt["N"] : tgt["port"]; - - FlowComponent* srcComponent = components[srcProcess]; - if (srcComponent == NULL) { - DEBUG_MSG_P(PSTR("[FLOW] Error: component ID='%s' is not registered\n"), srcProcess.c_str()); - continue; - } + FlowComponent* component = _library.createComponent(componentName, properties); - FlowComponent* tgtComponent = components[tgtProcess]; - if (tgtComponent == NULL) { - DEBUG_MSG_P(PSTR("[FLOW] Error: component ID='%s' is not registered\n"), tgtProcess.c_str()); - continue; + if (component != NULL) { + components[id] = component; + componentsNames[id] = componentName; + } else { + DEBUG_MSG_P(PSTR("[FLOW] Error: component '%s' is not registered\n"), componentName.c_str()); + } } + } - int srcNumber = _library.getOutputNumber(componentsNames[srcProcess], srcPort); - if (srcNumber < 0) { - DEBUG_MSG_P(PSTR("[FLOW] Error: component '%s' has no output named '%s'\n"), componentsNames[srcProcess].c_str(), srcPort.c_str()); - continue; - } + JsonArray& connections = data.containsKey("X") ? data["X"] : data["connections"]; + for (JsonVariant& connectionVariant: connections) { + if (connectionVariant.is()) { + JsonObject& connection = connectionVariant.as(); + JsonObject& src = connection.containsKey("S") ? connection["S"] : connection["src"]; + JsonObject& tgt = connection.containsKey("T") ? connection["T"] : connection["tgt"]; + + String srcProcess = src.containsKey("I") ? src["I"] : src["process"]; + String srcPort = src.containsKey("N") ? src["N"] : src["port"]; + String tgtProcess = tgt.containsKey("I") ? tgt["I"] : tgt["process"]; + String tgtPort = tgt.containsKey("N") ? tgt["N"] : tgt["port"]; + + _flowAddConnection(components, componentsNames, srcProcess, srcPort, tgtProcess, tgtPort); + } else { + JsonArray& connection = connectionVariant.as(); - int tgtNumber = _library.getInputNumber(componentsNames[tgtProcess], tgtPort); - if (tgtNumber < 0) { - DEBUG_MSG_P(PSTR("[FLOW] Error: component '%s' has no input named '%s'\n"), componentsNames[tgtProcess].c_str(), tgtPort.c_str()); - continue; - } + String srcProcess = connection[0]; + String srcPort = connection[1]; + String tgtProcess = connection[2]; + String tgtPort = connection[3]; - srcComponent->addOutput(srcNumber, tgtComponent, tgtNumber); + _flowAddConnection(components, componentsNames, srcProcess, srcPort, tgtProcess, tgtPort); + } } } From 5e8666c7949248dda488304ada41885109349791 Mon Sep 17 00:00:00 2001 From: Eugene Schava Date: Fri, 22 Feb 2019 23:33:19 +0200 Subject: [PATCH 31/40] flow: compare component --- code/espurna/flow.ino | 116 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 105 insertions(+), 11 deletions(-) diff --git a/code/espurna/flow.ino b/code/espurna/flow.ino index f9b1f5927f..bd8b9a841a 100644 --- a/code/espurna/flow.ino +++ b/code/espurna/flow.ino @@ -353,7 +353,6 @@ PROGMEM const char flow_change_component_json[] = "\"properties\":[{\"name\":\"Value\",\"type\":\"any\"}]" "}"; - class FlowChangeComponent : public FlowComponent { private: JsonVariant* _value; @@ -393,7 +392,6 @@ PROGMEM const char flow_math_component_json[] = "\"values\":[\"+\",\"-\",\"*\",\"/\"]}]" "}"; - class FlowMathComponent : public FlowComponent { private: String _operation; @@ -416,19 +414,23 @@ class FlowMathComponent : public FlowComponent { if (_input1 != NULL && _input2 != NULL) { if (_input1->is()) { + int i1 = _input1->as(); + int i2 = _input2->as(); JsonVariant r( - _operation.equals("+") ? _input1->as() + _input2->as() : - _operation.equals("-") ? _input1->as() - _input2->as() : - _operation.equals("*") ? _input1->as() * _input2->as() : - /*_operation.equals("/") ?*/ _input1->as() / _input2->as() + _operation.equals("+") ? i1 + i2 : + _operation.equals("-") ? i1 - i2 : + _operation.equals("*") ? i1 * i2 : + /*_operation.equals("/") ?*/ i1 / i2 ); processOutput(r, 0); } else if (_input1->is()) { + double d1 = _input1->as(); + double d2 = _input2->as(); JsonVariant r( - _operation.equals("+") ? _input1->as() + _input2->as() : - _operation.equals("-") ? _input1->as() - _input2->as() : - _operation.equals("*") ? _input1->as() * _input2->as() : - /*_operation.equals("/") ?*/ _input1->as() / _input2->as() + _operation.equals("+") ? d1 + d2 : + _operation.equals("-") ? d1 - d2 : + _operation.equals("*") ? d1 * d2 : + /*_operation.equals("/") ?*/ d1 / d2 ); processOutput(r, 0); } else if (_input1->is()) { @@ -442,7 +444,17 @@ class FlowMathComponent : public FlowComponent { JsonVariant r(concat); processOutput(r, 0); free(concat); - } + } else if (_input1->is()) { + bool b1 = _input1->as(); + bool b2 = _input2->as(); + JsonVariant r( + _operation.equals("+") ? b1 || b2 : + _operation.equals("-") ? !b1 : // NOT for first only + _operation.equals("*") ? b1 && b2 : + /*_operation.equals("/") ?*/ (b1 && !b2) || (!b1 && b2) // XOR + ); + processOutput(r, 0); + } } } @@ -452,6 +464,87 @@ class FlowMathComponent : public FlowComponent { } }; +// ----------------------------------------------------------------------------- +// Compare component +// ----------------------------------------------------------------------------- + +PROGMEM const char flow_true[] = "True"; +PROGMEM const char flow_false[] = "False"; +PROGMEM const char flow_test[] = "Test"; +PROGMEM const char* const flow_compare_inputs[] = {flow_data, flow_test}; +PROGMEM const char* const flow_compare_outputs[] = {flow_true, flow_false}; + +PROGMEM const FlowConnections flow_compare_component = { + 2, flow_compare_inputs, + 2, flow_compare_outputs, +}; + +PROGMEM const char flow_compare_component_json[] = + "\"Compare\": " + "{" + "\"name\":\"Compare\"," + "\"icon\":\"chevron-circle-right\"," + "\"inports\":[{\"name\":\"Data\",\"type\":\"any\"},{\"name\":\"Test\",\"type\":\"any\"}]," + "\"outports\":[{\"name\":\"True\",\"type\":\"any\"},{\"name\":\"False\",\"type\":\"any\"}]," + "\"properties\":[{\"name\":\"Operation\",\"type\":\"list\",\"values\":[\"=\",\">\",\"<\"]},{\"name\":\"Test\",\"type\":\"any\"}]" + "}"; + +class FlowCompareComponent : public FlowComponent { + private: + String _operation; + JsonVariant *_data, *_test; + + public: + FlowCompareComponent(JsonObject& properties) { + const char * operation = properties["Operation"]; + _operation = String(operation != NULL ? operation : ""); + + JsonVariant test = properties["Test"]; + _test = clone(test); + } + + virtual void processInput(JsonVariant& data, int inputNumber) { + if (inputNumber == 0) { + if (_data != NULL) release(_data); + _data = clone(data); + } else if (inputNumber == 1) { + if (_test != NULL) release(_test); + _test = clone(data); + } + + if (_data != NULL && _test != NULL) { + bool r; + if (_data->is()) { + double d1 = _data->as(); + double d2 = _test->as(); + r = _operation.equals("=") ? d1 == d2 : + _operation.equals(">") ? d1 > d2 : + /*_operation.equals("<") ?*/ d1 < d2 + ; + } else if (_data->is()) { + int cmp = strcmp(_data->as(), _test->as()); + r = _operation.equals("=") ? cmp == 0 : + _operation.equals(">") ? cmp > 0 : + /*_operation.equals("<") ?*/ cmp < 0 + ; + } else if (_data->is()) { + bool b1 = _data->as(); + bool b2 = _test->as(); + r = _operation.equals("=") ? b1 == b2 : + _operation.equals(">") ? b1 > b2 : + /*_operation.equals("<") ?*/ b1 < b2 + ; + } + processOutput(*_data, r ? 0 : 1); + } + } + + static void reg() { + flowRegisterComponent("Compare", &flow_compare_component, flow_compare_component_json, + (flow_component_factory_f)([] (JsonObject& properties) { return new FlowCompareComponent(properties); })); + } +}; + // ----------------------------------------------------------------------------- // Delay component // ----------------------------------------------------------------------------- @@ -688,6 +781,7 @@ void flowSetup() { (flow_component_factory_f)([] (JsonObject& properties) { return new FlowChangeComponent(properties); })); FlowMathComponent::reg(); + FlowCompareComponent::reg(); flowRegisterComponent("Delay", &flow_delay_component, flow_delay_component_json, (flow_component_factory_f)([] (JsonObject& properties) { return new FlowDelayComponent(properties); })); From 2c8a0448a1f05db0251113ae7751c80af9df8069 Mon Sep 17 00:00:00 2001 From: Eugene Schava Date: Sat, 23 Feb 2019 01:07:43 +0200 Subject: [PATCH 32/40] flow: hysteresis component --- code/espurna/flow.ino | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/code/espurna/flow.ino b/code/espurna/flow.ino index bd8b9a841a..c8c8d93502 100644 --- a/code/espurna/flow.ino +++ b/code/espurna/flow.ino @@ -727,9 +727,10 @@ class FlowHysteresisComponent : public FlowComponent { virtual void processInput(JsonVariant& data, int inputNumber) { if (inputNumber == 0) { // value _value = data.as(); - if ((_state && _value >= _max) || (!_state && _value <= _min)) + if ((_state && _value >= _max) || (!_state && _value <= _min)) { _state = !_state; - processOutput(data, _state ? 0 : 1); + processOutput(data, _state ? 0 : 1); + } } else if (inputNumber == 1) { // min _min = data.as(); if (!_state && _value <= _min) { From 620e410c538283bf114dab74fa9f102be9834116 Mon Sep 17 00:00:00 2001 From: Eugene Schava Date: Thu, 28 Feb 2019 23:35:19 +0200 Subject: [PATCH 33/40] flow: different fixes --- code/espurna/flow.h | 14 ++++++++++ code/espurna/flow.ino | 63 ++++++++++++++++++++----------------------- code/espurna/mqtt.ino | 3 ++- 3 files changed, 45 insertions(+), 35 deletions(-) diff --git a/code/espurna/flow.h b/code/espurna/flow.h index 94bd4896d4..d0bce3bd2d 100644 --- a/code/espurna/flow.h +++ b/code/espurna/flow.h @@ -42,6 +42,20 @@ class FlowComponent { } } + String toString(JsonVariant& data) { + if (data.is()) { + return String(data.as()); + } else if (data.is()) { + return String(data.as(), 3); + } else if (data.is()) { + return String(data.as() ? "" : ""); + } else if (data.is()) { + return String(data.as()); + } else { + return String(); + } + } + void release(JsonVariant* data) { if (data == NULL) return; diff --git a/code/espurna/flow.ino b/code/espurna/flow.ino index c8c8d93502..e40ffedd18 100644 --- a/code/espurna/flow.ino +++ b/code/espurna/flow.ino @@ -303,9 +303,7 @@ PROGMEM const char flow_debug_component_json[] = "\"properties\":[{\"name\":\"Prefix\",\"type\":\"string\"}]" "}"; -PROGMEM const char flow_debug_int[] = "[FLOW DEBUG] %s%i\n"; PROGMEM const char flow_debug_string[] = "[FLOW DEBUG] %s%s\n"; -PROGMEM const char flow_debug_unknown[] = "[FLOW DEBUG] %sUNKNOWN\n"; class FlowDebugComponent : public FlowComponent { private: @@ -318,19 +316,8 @@ class FlowDebugComponent : public FlowComponent { } virtual void processInput(JsonVariant& data, int inputNumber) { - if (data.is()) { - DEBUG_MSG_P(flow_debug_int, _prefix.c_str(), data.as()); - } else if (data.is()) { - char buffer[64]; - dtostrf(data.as(), 1 - sizeof(buffer), 3, buffer); - DEBUG_MSG_P(flow_debug_string, _prefix.c_str(), buffer); - } else if (data.is()) { - DEBUG_MSG_P(flow_debug_string, _prefix.c_str(), data.as() ? "" : ""); - } else if (data.is()) { - DEBUG_MSG_P(flow_debug_string, _prefix.c_str(), data.as()); - } else { - DEBUG_MSG_P(flow_debug_unknown, _prefix.c_str()); - } + String s = toString(data); + DEBUG_MSG_P(flow_debug_string, _prefix.c_str(), s.c_str()); } }; @@ -435,15 +422,11 @@ class FlowMathComponent : public FlowComponent { processOutput(r, 0); } else if (_input1->is()) { // only + is supported - const char *s1 = _input1->as(); - const char *s2 = _input2->as(); - char *concat = new char[strlen(s1) + (s2 != NULL ? strlen(s2) : 0) + 1]; - strcpy(concat, s1); - if (s2 != NULL) - strcat(concat, s2); - JsonVariant r(concat); + String s(_input1->as()); + s += toString(*_input2); + + JsonVariant r(s.c_str()); processOutput(r, 0); - free(concat); } else if (_input1->is()) { bool b1 = _input1->as(); bool b2 = _input2->as(); @@ -522,7 +505,11 @@ class FlowCompareComponent : public FlowComponent { /*_operation.equals("<") ?*/ d1 < d2 ; } else if (_data->is()) { - int cmp = strcmp(_data->as(), _test->as()); + const char *s1 = _data->as(); + const char *s2 = _test->as(); + int cmp = s1 == NULL ? (s2 == NULL ? 0 : -1) : + s2 == NULL ? 1 : + strcmp(s1, s2); r = _operation.equals("=") ? cmp == 0 : _operation.equals(">") ? cmp > 0 : /*_operation.equals("<") ?*/ cmp < 0 @@ -549,8 +536,11 @@ class FlowCompareComponent : public FlowComponent { // Delay component // ----------------------------------------------------------------------------- +PROGMEM const char flow_reset[] = "Reset"; +PROGMEM const char* const flow_delay_inputs[] = {flow_data, flow_reset}; + PROGMEM const FlowConnections flow_delay_component = { - 1, flow_data_array, + 2, flow_delay_inputs, 1, flow_data_array, }; @@ -559,7 +549,7 @@ PROGMEM const char flow_delay_component_json[] = "{" "\"name\":\"Delay\"," "\"icon\":\"pause\"," - "\"inports\":[{\"name\":\"Data\",\"type\":\"any\"}]," + "\"inports\":[{\"name\":\"Data\",\"type\":\"any\"},{\"name\":\"Reset\",\"type\":\"any\"}]," "\"outports\":[{\"name\":\"Data\",\"type\":\"any\"}]," "\"properties\":[{\"name\":\"Seconds\",\"type\":\"int\"}, {\"name\":\"Last only\",\"type\":\"bool\"}]" "}"; @@ -569,6 +559,7 @@ class FlowDelayComponent : public FlowComponent { long _time; bool _lastOnly; int _queueSize = 0; + long _skipUntil = 0; public: FlowDelayComponent(JsonObject& properties) { @@ -577,13 +568,17 @@ class FlowDelayComponent : public FlowComponent { } virtual void processInput(JsonVariant& data, int inputNumber) { - flow_scheduled_task_callback_f callback = [this](unsigned long time, JsonVariant *data){ this->onDelay(data); }; - _flow_scheduled_tasks_queue.insert({clone(data), millis() + _time, callback}); - _queueSize++; + if (inputNumber == 0) { // data + flow_scheduled_task_callback_f callback = [this](unsigned long time, JsonVariant *data){ this->onDelay(time, data); }; + _flow_scheduled_tasks_queue.insert({clone(data), millis() + _time, callback}); + _queueSize++; + } else { // reset + _skipUntil = millis() + _time; + } } - void onDelay(JsonVariant *data) { - if (!_lastOnly || _queueSize == 1) + void onDelay(long now, JsonVariant *data) { + if (now > _skipUntil && (!_lastOnly || _queueSize == 1)) processOutput(*data, 0); _queueSize--; @@ -729,21 +724,21 @@ class FlowHysteresisComponent : public FlowComponent { _value = data.as(); if ((_state && _value >= _max) || (!_state && _value <= _min)) { _state = !_state; - processOutput(data, _state ? 0 : 1); + processOutput(data, _state ? 1 : 0); } } else if (inputNumber == 1) { // min _min = data.as(); if (!_state && _value <= _min) { _state = true; JsonVariant value(_value); - processOutput(value, 0); + processOutput(value, 1); } } else { // max _max = data.as(); if (_state && _value >= _max) { _state = false; JsonVariant value(_value); - processOutput(value, 1); + processOutput(value, 0); } } } diff --git a/code/espurna/mqtt.ino b/code/espurna/mqtt.ino index c7aba6a65a..4eab6aa762 100644 --- a/code/espurna/mqtt.ino +++ b/code/espurna/mqtt.ino @@ -443,7 +443,8 @@ class FlowMqttPublishComponent : public FlowComponent { } virtual void processInput(JsonVariant& data, int inputNumber) { - mqttSendRaw(_topic.c_str(), data.as(), _retain); + String s = toString(data); + mqttSendRaw(_topic.c_str(), s.c_str(), _retain); } }; #endif //FLOW_SUPPORT From f14f676ed548b6de9772e2b19d3bd0a516c72cc4 Mon Sep 17 00:00:00 2001 From: Eugene Schava Date: Sat, 2 Mar 2019 09:14:01 +0200 Subject: [PATCH 34/40] flow: light component --- code/espurna/light.ino | 52 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/code/espurna/light.ino b/code/espurna/light.ino index 16b72d335d..51f4a50fdd 100644 --- a/code/espurna/light.ino +++ b/code/espurna/light.ino @@ -647,6 +647,54 @@ void lightBroker() { #endif +// ----------------------------------------------------------------------------- +// FLOW +// ----------------------------------------------------------------------------- + +#if FLOW_SUPPORT + +PROGMEM const char flow_color[] = "Color"; +PROGMEM const char flow_brightness[] = "Brightness"; +PROGMEM const char* const flow_light_component_inputs[] = {flow_color, flow_brightness}; + +PROGMEM const FlowConnections flow_light_component = { + 2, flow_light_component_inputs, + 0, NULL, +}; + +PROGMEM const char flow_light_component_json[] = + "\"Light\": " + "{" + "\"name\":\"Light\"," + "\"icon\":\"sun-o\"," + "\"inports\":[{\"name\":\"Color\",\"type\":\"string\"}, {\"name\":\"Brightness\",\"type\":\"int\"}]," + "\"outports\":[]," + "\"properties\":[]" + "}"; + +class FlowLightComponent : public FlowComponent { + public: + FlowLightComponent(JsonObject& properties) { + } + + virtual void processInput(JsonVariant& data, int inputNumber) { + if (inputNumber == 0) { // color + lightColor(data.as(), true); + lightUpdate(true, true); + } else { // brightness + _light_brightness = constrain(data.as(), 0, LIGHT_MAX_BRIGHTNESS); + lightUpdate(true, true); + } + } + + static void reg() { + flowRegisterComponent("Light", &flow_light_component, flow_light_component_json, + (flow_component_factory_f)([] (JsonObject& properties) { return new FlowLightComponent(properties); })); + } +}; + +#endif // FLOW_SUPPORT + // ----------------------------------------------------------------------------- // API // ----------------------------------------------------------------------------- @@ -1170,6 +1218,10 @@ void lightSetup() { _lightInitCommands(); #endif + #if FLOW_SUPPORT + FlowLightComponent::reg(); + #endif + // Main callbacks espurnaRegisterReload([]() { #if LIGHT_SAVE_ENABLED == 0 From 95428dca914b71f5ae49c8125dca7ef51e3c4843 Mon Sep 17 00:00:00 2001 From: Eugene Schava Date: Wed, 6 Mar 2019 22:58:44 +0200 Subject: [PATCH 35/40] flow: use Ticker for delayed task execution --- code/espurna/flow.ino | 85 ++++++++++++++++++------------------------- 1 file changed, 35 insertions(+), 50 deletions(-) diff --git a/code/espurna/flow.ino b/code/espurna/flow.ino index e40ffedd18..50655f7523 100644 --- a/code/espurna/flow.ino +++ b/code/espurna/flow.ino @@ -8,6 +8,7 @@ Copyright (C) 2016-2018 by Xose P�rez #if FLOW_SUPPORT +#include #include #include #include @@ -218,32 +219,6 @@ void _flowStart(JsonObject& data) { } } -typedef std::function flow_scheduled_task_callback_f; - -struct flow_scheduled_task_t { - JsonVariant *data; - unsigned long time; - flow_scheduled_task_callback_f callback; - - bool operator() (const flow_scheduled_task_t& lhs, const flow_scheduled_task_t& rhs) const { - return lhs.time < rhs.time; - } -}; - -std::set _flow_scheduled_tasks_queue; - -void _flowComponentLoop() { - if (!_flow_scheduled_tasks_queue.empty()) { - auto it = _flow_scheduled_tasks_queue.begin(); - const flow_scheduled_task_t element = *it; - unsigned long now = millis(); - if (element.time <= now) { - element.callback(now, element.data); - _flow_scheduled_tasks_queue.erase(it); - } - } -} - // ----------------------------------------------------------------------------- // Start component // ----------------------------------------------------------------------------- @@ -269,18 +244,18 @@ PROGMEM const char flow_start_component_json[] = class FlowStartComponent : public FlowComponent { private: JsonVariant *_value; + Ticker _startTicker; public: FlowStartComponent(JsonObject& properties) { JsonVariant value = properties["Value"]; _value = clone(value); - flow_scheduled_task_callback_f callback = [this](unsigned long time, JsonVariant *data){ this->onStart(); }; - _flow_scheduled_tasks_queue.insert({NULL, 0, callback}); + _startTicker.once_ms(100, onStart, this); } - void onStart() { - processOutput(*_value, 0); + static void onStart(FlowStartComponent* component) { + component->processOutput(*component->_value, 0); } }; @@ -556,6 +531,12 @@ PROGMEM const char flow_delay_component_json[] = class FlowDelayComponent : public FlowComponent { private: + struct scheduled_task_t { + FlowDelayComponent *component; + Ticker *ticker; + JsonVariant *data; + }; + long _time; bool _lastOnly; int _queueSize = 0; @@ -569,20 +550,31 @@ class FlowDelayComponent : public FlowComponent { virtual void processInput(JsonVariant& data, int inputNumber) { if (inputNumber == 0) { // data - flow_scheduled_task_callback_f callback = [this](unsigned long time, JsonVariant *data){ this->onDelay(time, data); }; - _flow_scheduled_tasks_queue.insert({clone(data), millis() + _time, callback}); + Ticker *ticker = new Ticker(); + scheduled_task_t *task = new flow_scheduled_task_t(); + task->component = this; + task->ticker = ticker; + task->data = clone(data); + + ticker->once_ms(_time, onDelay, task); + _queueSize++; } else { // reset _skipUntil = millis() + _time; } } - void onDelay(long now, JsonVariant *data) { - if (now > _skipUntil && (!_lastOnly || _queueSize == 1)) - processOutput(*data, 0); + static void onDelay(scheduled_task_t *task) { + FlowDelayComponent *that = task->component; + if (millis() > that->_skipUntil && (!that->_lastOnly || that->_queueSize == 1)) + that->processOutput(*task->data, 0); - _queueSize--; - release(data); + that->_queueSize--; + + that->release(task->data); + task->ticker->detach(); + free(task->ticker); + free(task); } }; @@ -610,8 +602,7 @@ PROGMEM const char flow_incorrect_timer_delay[] = "[FLOW] Incorrect timer delay: class FlowTimerComponent : public FlowComponent { private: JsonVariant *_value; - unsigned long _period; - flow_scheduled_task_callback_f _callback; + Ticker _ticker; public: FlowTimerComponent(JsonObject& properties) { @@ -619,21 +610,17 @@ class FlowTimerComponent : public FlowComponent { _value = clone(value); int seconds = properties["Seconds"]; - _period = 1000 * (int)seconds; + int period = 1000 * (int)seconds; - if (_period > 0) { - _callback = [this](unsigned long time, JsonVariant *data){ this->onSchedule(time); }; - _flow_scheduled_tasks_queue.insert({NULL, millis() + _period, _callback}); + if (period > 0) { + _ticker.attach_ms(period, onSchedule, this); } else { DEBUG_MSG_P(flow_incorrect_timer_delay, seconds); } } - void onSchedule(unsigned long now) { - processOutput(*_value, 0); - - // reschedule - _flow_scheduled_tasks_queue.insert({NULL, now + _period, _callback}); + static void onSchedule(FlowTimerComponent *component) { + component->processOutput(*component->_value, 0); } }; @@ -790,8 +777,6 @@ void flowSetup() { flowRegisterComponent("Hysteresis", &flow_hysteresis_component, flow_hysteresis_component_json, (flow_component_factory_f)([] (JsonObject& properties) { return new FlowHysteresisComponent(properties); })); - - espurnaRegisterLoop(_flowComponentLoop); } #endif // FLOW_SUPPORT From 80c62886709f3cef3fca60f72dbc2d18c6bc3df4 Mon Sep 17 00:00:00 2001 From: Eugene Schava Date: Thu, 7 Mar 2019 11:04:59 +0200 Subject: [PATCH 36/40] flow: use Ticker for delayed task execution --- code/espurna/flow.ino | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/code/espurna/flow.ino b/code/espurna/flow.ino index 50655f7523..94fe1e6288 100644 --- a/code/espurna/flow.ino +++ b/code/espurna/flow.ino @@ -540,7 +540,7 @@ class FlowDelayComponent : public FlowComponent { long _time; bool _lastOnly; int _queueSize = 0; - long _skipUntil = 0; + long _skipNumber = 0; public: FlowDelayComponent(JsonObject& properties) { @@ -551,7 +551,7 @@ class FlowDelayComponent : public FlowComponent { virtual void processInput(JsonVariant& data, int inputNumber) { if (inputNumber == 0) { // data Ticker *ticker = new Ticker(); - scheduled_task_t *task = new flow_scheduled_task_t(); + scheduled_task_t *task = new scheduled_task_t(); task->component = this; task->ticker = ticker; task->data = clone(data); @@ -560,22 +560,30 @@ class FlowDelayComponent : public FlowComponent { _queueSize++; } else { // reset - _skipUntil = millis() + _time; + _skipNumber = _queueSize; } } static void onDelay(scheduled_task_t *task) { - FlowDelayComponent *that = task->component; - if (millis() > that->_skipUntil && (!that->_lastOnly || that->_queueSize == 1)) - that->processOutput(*task->data, 0); + task->component->onDelayImpl(task->data); - that->_queueSize--; - - that->release(task->data); task->ticker->detach(); free(task->ticker); free(task); } + + void onDelayImpl(JsonVariant *data) { + if (_skipNumber == 0) { + if (!_lastOnly || _queueSize == 1) + processOutput(*data, 0); + } else { + _skipNumber--; + } + + _queueSize--; + + release(data); + } }; // ----------------------------------------------------------------------------- From c375ea347c1497804b525554f763bd1d8da586bf Mon Sep 17 00:00:00 2001 From: Eugene Schava Date: Fri, 8 Mar 2019 01:53:24 +0200 Subject: [PATCH 37/40] flow: memory optimization and refactoring --- code/espurna/button.ino | 14 +-- code/espurna/config/progmem.h | 170 +++++++++++++++++++++++++++++++ code/espurna/config/prototypes.h | 2 +- code/espurna/flow.h | 10 +- code/espurna/flow.ino | 154 +++++----------------------- code/espurna/light.ino | 12 +-- code/espurna/mqtt.ino | 24 +---- code/espurna/relay.ino | 14 +-- code/espurna/scheduler.ino | 12 +-- code/espurna/sensor.ino | 14 +-- code/espurna/settings.ino | 24 +---- code/espurna/web.ino | 41 +------- 12 files changed, 212 insertions(+), 279 deletions(-) diff --git a/code/espurna/button.ino b/code/espurna/button.ino index a8ba85281b..29de8ced90 100644 --- a/code/espurna/button.ino +++ b/code/espurna/button.ino @@ -63,16 +63,6 @@ PROGMEM const FlowConnections flow_button_component = { 1, flow_data2_array, }; -PROGMEM const char flow_button_component_json[] = - "\"Button\": " - "{" - "\"name\":\"Button\"," - "\"icon\":\"toggle-on\"," - "\"inports\":[]," - "\"outports\":[{\"name\":\"Data\",\"type\":\"int\"}]," - "\"properties\":[{\"name\":\"Button\",\"type\":\"list\"}]" - "}"; - class FlowButtonComponent : public FlowComponent { public: FlowButtonComponent(JsonObject& properties) { @@ -291,9 +281,9 @@ void buttonSetup() { buttons->push_back(String(i)); } - flowRegisterComponent("Button", &flow_button_component, flow_button_component_json, + flowRegisterComponent("Button", &flow_button_component, (flow_component_factory_f)([] (JsonObject& properties) { return new FlowButtonComponent(properties); })); - flowRegisterComponentValues("Button", "Button", buttons); + flowRegisterComponentValues("BUTTON_VALUES", buttons); #endif // Register loop diff --git a/code/espurna/config/progmem.h b/code/espurna/config/progmem.h index d4cbf880db..c553a490f1 100644 --- a/code/espurna/config/progmem.h +++ b/code/espurna/config/progmem.h @@ -127,6 +127,9 @@ PROGMEM const char espurna_modules[] = #if WEB_SUPPORT "WEB " #endif + #if FLOW_SUPPORT + "FLOW " + #endif ""; //-------------------------------------------------------------------------------- @@ -342,3 +345,170 @@ PROGMEM const char* const magnitude_units[] = { }; #endif + +// ----------------------------------------------------------------------------- +// FLOW +// ----------------------------------------------------------------------------- + +#if FLOW_SUPPORT + +PROGMEM const char flow_library_json[] = + "{" + "\"Start\": " + "{" + "\"name\":\"Start\"," + "\"icon\":\"play\"," + "\"inports\":[]," + "\"outports\":[{\"name\":\"Data\",\"type\":\"bool\"}]," + "\"properties\":[{\"name\":\"Value\",\"type\":\"any\"}]" + "}" + ",\"Debug\": " + "{" + "\"name\":\"Debug\"," + "\"icon\":\"bug\"," + "\"inports\":[{\"name\":\"Data\",\"type\":\"any\"}]," + "\"outports\":[]," + "\"properties\":[{\"name\":\"Prefix\",\"type\":\"string\"}]" + "}" + ",\"Change\": " + "{" + "\"name\":\"Change\"," + "\"icon\":\"edit\"," + "\"inports\":[{\"name\":\"Data\",\"type\":\"any\"}]," + "\"outports\":[{\"name\":\"Data\",\"type\":\"any\"}]," + "\"properties\":[{\"name\":\"Value\",\"type\":\"any\"}]" + "}" + ",\"Math\": " + "{" + "\"name\":\"Math\"," + "\"icon\":\"plus-circle\"," + "\"inports\":[{\"name\":\"Input 1\",\"type\":\"any\"},{\"name\":\"Input 2\",\"type\":\"any\"}]," + "\"outports\":[{\"name\":\"Data\",\"type\":\"any\"}]," + "\"properties\":[{\"name\":\"Operation\",\"type\":\"list\"," + "\"values\":[\"+\",\"-\",\"*\",\"/\"]}]" + "}" + ",\"Compare\": " + "{" + "\"name\":\"Compare\"," + "\"icon\":\"chevron-circle-right\"," + "\"inports\":[{\"name\":\"Data\",\"type\":\"any\"},{\"name\":\"Test\",\"type\":\"any\"}]," + "\"outports\":[{\"name\":\"True\",\"type\":\"any\"},{\"name\":\"False\",\"type\":\"any\"}]," + "\"properties\":[{\"name\":\"Operation\",\"type\":\"list\",\"values\":[\"=\",\">\",\"<\"]},{\"name\":\"Test\",\"type\":\"any\"}]" + "}" + ",\"Delay\": " + "{" + "\"name\":\"Delay\"," + "\"icon\":\"pause\"," + "\"inports\":[{\"name\":\"Data\",\"type\":\"any\"},{\"name\":\"Reset\",\"type\":\"any\"}]," + "\"outports\":[{\"name\":\"Data\",\"type\":\"any\"}]," + "\"properties\":[{\"name\":\"Seconds\",\"type\":\"int\"}, {\"name\":\"Last only\",\"type\":\"bool\"}]" + "}" + ",\"Timer\": " + "{" + "\"name\":\"Timer\"," + "\"icon\":\"clock-o\"," + "\"inports\":[]," + "\"outports\":[{\"name\":\"Data\",\"type\":\"bool\"}]," + "\"properties\":[{\"name\":\"Seconds\",\"type\":\"int\"},{\"name\":\"Value\",\"type\":\"any\"}]" + "}" + ",\"Gate\": " + "{" + "\"name\":\"Gate\"," + "\"icon\":\"unlock\"," + "\"inports\":[{\"name\":\"Data\",\"type\":\"any\"}, {\"name\":\"State\",\"type\":\"bool\"}]," + "\"outports\":[{\"name\":\"Open\",\"type\":\"any\"}, {\"name\":\"Close\",\"type\":\"any\"}]," + "\"properties\":[]" + "}" + ",\"Hysteresis\": " + "{" + "\"name\":\"Hysteresis\"," + "\"icon\":\"line-chart\"," + "\"inports\":[{\"name\":\"Value\",\"type\":\"double\"}, {\"name\":\"Min\",\"type\":\"double\"}, {\"name\":\"Max\",\"type\":\"double\"}]," + "\"outports\":[{\"name\":\"Rise\",\"type\":\"double\"}, {\"name\":\"Fall\",\"type\":\"double\"}]," + "\"properties\":[{\"name\":\"Min\",\"type\":\"double\"}, {\"name\":\"Max\",\"type\":\"double\"}]" + "}" + ",\"Save setting\": " + "{" + "\"name\":\"Save setting\"," + "\"icon\":\"save\"," + "\"inports\":[{\"name\":\"Value\",\"type\":\"string\"}]," + "\"outports\":[]," + "\"properties\":[{\"name\":\"Name\",\"type\":\"string\"}]" + "}" + ",\"Load setting\": " + "{" + "\"name\":\"Load setting\"," + "\"icon\":\"database\"," + "\"inports\":[{\"name\":\"Name\",\"type\":\"string\"}]," + "\"outports\":[{\"name\":\"Value\",\"type\":\"string\"}]," + "\"properties\":[{\"name\":\"Default\",\"type\":\"string\"}]" + "}" +#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE + ",\"Light\": " + "{" + "\"name\":\"Light\"," + "\"icon\":\"sun-o\"," + "\"inports\":[{\"name\":\"Color\",\"type\":\"string\"}, {\"name\":\"Brightness\",\"type\":\"int\"}]," + "\"outports\":[]," + "\"properties\":[]" + "}" +#endif + ",\"Relay\": " + "{" + "\"name\":\"Relay\"," + "\"icon\":\"lightbulb-o\"," + "\"inports\":[{\"name\":\"State\",\"type\":\"bool\"}, {\"name\":\"Toggle\",\"type\":\"any\"}]," + "\"outports\":[]," + "\"properties\":[{\"name\":\"Relay\",\"type\":\"list\",\"values\":[%RELAY_VALUES%]}]" + "}" +#if BUTTON_SUPPORT + ",\"Button\": " + "{" + "\"name\":\"Button\"," + "\"icon\":\"toggle-on\"," + "\"inports\":[]," + "\"outports\":[{\"name\":\"Data\",\"type\":\"int\"}]," + "\"properties\":[{\"name\":\"Button\",\"type\":\"list\",\"values\":[%BUTTON_VALUES%]}]" + "}" +#endif +#if MQTT_SUPPORT + ",\"MQTT subscribe\": " + "{" + "\"name\":\"MQTT subscribe\"," + "\"icon\":\"sign-out\"," + "\"inports\":[]," + "\"outports\":[{\"name\":\"Data\",\"type\":\"string\"}]," + "\"properties\":[{\"name\":\"Topic\",\"type\":\"string\"}]" + "}" + ",\"MQTT publish\": " + "{" + "\"name\":\"MQTT publish\"," + "\"icon\":\"sign-in\"," + "\"inports\":[{\"name\":\"Data\",\"type\":\"string\"}]," + "\"outports\":[]," + "\"properties\":[{\"name\":\"Topic\",\"type\":\"string\"}, {\"name\":\"Retain\",\"type\":\"bool\"}]" + "}" +#endif +#if SENSOR_SUPPORT + ",\"Sensor\": " + "{" + "\"name\":\"Sensor\"," + "\"icon\":\"thermometer-3\"," + "\"inports\":[]," + "\"outports\":[{\"name\":\"Data\",\"type\":\"double\"}]," + "\"properties\":[{\"name\":\"Sensor\",\"type\":\"list\",\"values\":[%SENSOR_VALUES%]}]" + "}" +#endif +#if SCHEDULER_SUPPORT + ",\"Schedule\": " + "{" + "\"name\":\"Schedule\"," + "\"icon\":\"calendar\"," + "\"inports\":[]," + "\"outports\":[{\"name\":\"Data\",\"type\":\"bool\"}]," + "\"properties\":[{\"name\":\"Time\",\"type\":\"time\"}, {\"name\":\"Weekdays\",\"type\":\"weekdays\"},{\"name\":\"Value\",\"type\":\"any\"}]" + "}" +#endif + "}"; + +#endif \ No newline at end of file diff --git a/code/espurna/config/prototypes.h b/code/espurna/config/prototypes.h index 66d2ed2839..013ef7a81a 100644 --- a/code/espurna/config/prototypes.h +++ b/code/espurna/config/prototypes.h @@ -218,7 +218,7 @@ bool wifiConnected(); #if FLOW_SUPPORT #include "flow.h" typedef std::function flow_component_factory_f; - void flowRegisterComponentValues(String component, String property, std::vector* values); + void flowRegisterComponentValues(String placeholder, std::vector* values); #else #define FlowConnections void #define flow_component_factory_f void * diff --git a/code/espurna/flow.h b/code/espurna/flow.h index d0bce3bd2d..110eceba9d 100644 --- a/code/espurna/flow.h +++ b/code/espurna/flow.h @@ -85,23 +85,15 @@ typedef std::function flow_component_factory_f; class FlowComponentLibrary { private: - std::vector _jsons; std::map _connectionsMap; std::map _factoryMap; public: - void addType(String name, const FlowConnections* connections, const char* json, flow_component_factory_f factory) { - _jsons.push_back(json); + void addType(String name, const FlowConnections* connections, flow_component_factory_f factory) { _connectionsMap[name] = connections; _factoryMap[name] = factory; } - const char* getComponentJson(int index) { - if (index >= _jsons.size()) - return NULL; - return _jsons[index]; - } - FlowComponent* createComponent(String& name, JsonObject& properties) { flow_component_factory_f& factory = _factoryMap[name]; return factory != NULL ? factory(properties) : NULL; diff --git a/code/espurna/flow.ino b/code/espurna/flow.ino index 94fe1e6288..a844ee6e98 100644 --- a/code/espurna/flow.ino +++ b/code/espurna/flow.ino @@ -22,35 +22,16 @@ String _flow; unsigned long _mqtt_flow_sent_at = 0; #endif -bool _flow_started = false; FlowComponentLibrary _library; +bool _flow_started = false; +std::map*> _flow_placeholder_values; -typedef struct { - String component; - String property; - std::vector* values; -} component_property_values_t; - -std::vector _component_property_values_list; - -void flowRegisterComponent(String name, const FlowConnections* connections, const char* json, flow_component_factory_f factory) { - _library.addType(name, connections, json, factory); -} - -void flowRegisterComponentValues(String component, String property, std::vector* values) { - _component_property_values_list.push_back({component, property, values}); +void flowRegisterComponent(String name, const FlowConnections* connections, flow_component_factory_f factory) { + _library.addType(name, connections, factory); } -void flowGetComponentValuesJson(JsonArray& root) { - for (component_property_values_t entry : _component_property_values_list) { - JsonObject& object = root.createNestedObject(); - object["component"] = entry.component; - object["property"] = entry.property; - JsonArray& valuesArray = object.createNestedArray("values"); - for (unsigned int j=0; j < entry.values->size(); j++) { - valuesArray.add(entry.values->at(j)); - } - } +void flowRegisterComponentValues(String placeholder, std::vector* values) { + _flow_placeholder_values[placeholder] = values; } AsyncWebServerResponse* flowGetConfigResponse(AsyncWebServerRequest *request) { @@ -90,8 +71,18 @@ bool flowSaveConfig(char* data) { return result; } -const char* flowGetComponentJson(int index) { - return _library.getComponentJson(index); +String flowLibraryProcessor(const String& var) +{ + std::vector* values = _flow_placeholder_values[var]; + if (values != NULL) { + String result; + for (String& value : *values) { + if (result.length() > 0) result += ","; + result += "\"" + value + "\""; + } + return result; + } + return String(); } void flowStart() { @@ -231,16 +222,6 @@ PROGMEM const FlowConnections flow_start_component = { 1, flow_data_array, }; -PROGMEM const char flow_start_component_json[] = - "\"Start\": " - "{" - "\"name\":\"Start\"," - "\"icon\":\"play\"," - "\"inports\":[]," - "\"outports\":[{\"name\":\"Data\",\"type\":\"bool\"}]," - "\"properties\":[{\"name\":\"Value\",\"type\":\"any\"}]" - "}"; - class FlowStartComponent : public FlowComponent { private: JsonVariant *_value; @@ -268,16 +249,6 @@ PROGMEM const FlowConnections flow_debug_component = { 0, NULL, }; -PROGMEM const char flow_debug_component_json[] = - "\"Debug\": " - "{" - "\"name\":\"Debug\"," - "\"icon\":\"bug\"," - "\"inports\":[{\"name\":\"Data\",\"type\":\"any\"}]," - "\"outports\":[]," - "\"properties\":[{\"name\":\"Prefix\",\"type\":\"string\"}]" - "}"; - PROGMEM const char flow_debug_string[] = "[FLOW DEBUG] %s%s\n"; class FlowDebugComponent : public FlowComponent { @@ -305,16 +276,6 @@ PROGMEM const FlowConnections flow_change_component = { 1, flow_data_array, }; -PROGMEM const char flow_change_component_json[] = - "\"Change\": " - "{" - "\"name\":\"Change\"," - "\"icon\":\"edit\"," - "\"inports\":[{\"name\":\"Data\",\"type\":\"any\"}]," - "\"outports\":[{\"name\":\"Data\",\"type\":\"any\"}]," - "\"properties\":[{\"name\":\"Value\",\"type\":\"any\"}]" - "}"; - class FlowChangeComponent : public FlowComponent { private: JsonVariant* _value; @@ -343,17 +304,6 @@ PROGMEM const FlowConnections flow_math_component = { 1, flow_data_array, }; -PROGMEM const char flow_math_component_json[] = - "\"Math\": " - "{" - "\"name\":\"Math\"," - "\"icon\":\"plus-circle\"," - "\"inports\":[{\"name\":\"Input 1\",\"type\":\"any\"},{\"name\":\"Input 2\",\"type\":\"any\"}]," - "\"outports\":[{\"name\":\"Data\",\"type\":\"any\"}]," - "\"properties\":[{\"name\":\"Operation\",\"type\":\"list\"," - "\"values\":[\"+\",\"-\",\"*\",\"/\"]}]" - "}"; - class FlowMathComponent : public FlowComponent { private: String _operation; @@ -417,7 +367,7 @@ class FlowMathComponent : public FlowComponent { } static void reg() { - flowRegisterComponent("Math", &flow_math_component, flow_math_component_json, + flowRegisterComponent("Math", &flow_math_component, (flow_component_factory_f)([] (JsonObject& properties) { return new FlowMathComponent(properties); })); } }; @@ -437,16 +387,6 @@ PROGMEM const FlowConnections flow_compare_component = { 2, flow_compare_outputs, }; -PROGMEM const char flow_compare_component_json[] = - "\"Compare\": " - "{" - "\"name\":\"Compare\"," - "\"icon\":\"chevron-circle-right\"," - "\"inports\":[{\"name\":\"Data\",\"type\":\"any\"},{\"name\":\"Test\",\"type\":\"any\"}]," - "\"outports\":[{\"name\":\"True\",\"type\":\"any\"},{\"name\":\"False\",\"type\":\"any\"}]," - "\"properties\":[{\"name\":\"Operation\",\"type\":\"list\",\"values\":[\"=\",\">\",\"<\"]},{\"name\":\"Test\",\"type\":\"any\"}]" - "}"; - class FlowCompareComponent : public FlowComponent { private: String _operation; @@ -502,7 +442,7 @@ class FlowCompareComponent : public FlowComponent { } static void reg() { - flowRegisterComponent("Compare", &flow_compare_component, flow_compare_component_json, + flowRegisterComponent("Compare", &flow_compare_component, (flow_component_factory_f)([] (JsonObject& properties) { return new FlowCompareComponent(properties); })); } }; @@ -519,16 +459,6 @@ PROGMEM const FlowConnections flow_delay_component = { 1, flow_data_array, }; -PROGMEM const char flow_delay_component_json[] = - "\"Delay\": " - "{" - "\"name\":\"Delay\"," - "\"icon\":\"pause\"," - "\"inports\":[{\"name\":\"Data\",\"type\":\"any\"},{\"name\":\"Reset\",\"type\":\"any\"}]," - "\"outports\":[{\"name\":\"Data\",\"type\":\"any\"}]," - "\"properties\":[{\"name\":\"Seconds\",\"type\":\"int\"}, {\"name\":\"Last only\",\"type\":\"bool\"}]" - "}"; - class FlowDelayComponent : public FlowComponent { private: struct scheduled_task_t { @@ -595,16 +525,6 @@ PROGMEM const FlowConnections flow_timer_component = { 1, flow_data_array, }; -PROGMEM const char flow_timer_component_json[] = - "\"Timer\": " - "{" - "\"name\":\"Timer\"," - "\"icon\":\"clock-o\"," - "\"inports\":[]," - "\"outports\":[{\"name\":\"Data\",\"type\":\"bool\"}]," - "\"properties\":[{\"name\":\"Seconds\",\"type\":\"int\"},{\"name\":\"Value\",\"type\":\"any\"}]" - "}"; - PROGMEM const char flow_incorrect_timer_delay[] = "[FLOW] Incorrect timer delay: %i\n"; class FlowTimerComponent : public FlowComponent { @@ -646,16 +566,6 @@ PROGMEM const FlowConnections flow_gate_component = { 2, flow_gate_component_outputs, }; -PROGMEM const char flow_gate_component_json[] = - "\"Gate\": " - "{" - "\"name\":\"Gate\"," - "\"icon\":\"unlock\"," - "\"inports\":[{\"name\":\"Data\",\"type\":\"any\"}, {\"name\":\"State\",\"type\":\"bool\"}]," - "\"outports\":[{\"name\":\"Open\",\"type\":\"any\"}, {\"name\":\"Close\",\"type\":\"any\"}]," - "\"properties\":[]" - "}"; - class FlowGateComponent : public FlowComponent { private: bool _state = true; @@ -689,16 +599,6 @@ PROGMEM const FlowConnections flow_hysteresis_component = { 2, flow_hysteresis_component_outputs, }; -PROGMEM const char flow_hysteresis_component_json[] = - "\"Hysteresis\": " - "{" - "\"name\":\"Hysteresis\"," - "\"icon\":\"line-chart\"," - "\"inports\":[{\"name\":\"Value\",\"type\":\"double\"}, {\"name\":\"Min\",\"type\":\"double\"}, {\"name\":\"Max\",\"type\":\"double\"}]," - "\"outports\":[{\"name\":\"Rise\",\"type\":\"double\"}, {\"name\":\"Fall\",\"type\":\"double\"}]," - "\"properties\":[{\"name\":\"Min\",\"type\":\"double\"}, {\"name\":\"Max\",\"type\":\"double\"}]" - "}"; - class FlowHysteresisComponent : public FlowComponent { private: bool _state = false; @@ -762,28 +662,28 @@ void flowSetup() { mqttRegister(_flowMQTTCallback); #endif - flowRegisterComponent("Start", &flow_start_component, flow_start_component_json, + flowRegisterComponent("Start", &flow_start_component, (flow_component_factory_f)([] (JsonObject& properties) { return new FlowStartComponent(properties); })); - flowRegisterComponent("Debug", &flow_debug_component, flow_debug_component_json, + flowRegisterComponent("Debug", &flow_debug_component, (flow_component_factory_f)([] (JsonObject& properties) { return new FlowDebugComponent(properties); })); - flowRegisterComponent("Change", &flow_change_component, flow_change_component_json, + flowRegisterComponent("Change", &flow_change_component, (flow_component_factory_f)([] (JsonObject& properties) { return new FlowChangeComponent(properties); })); FlowMathComponent::reg(); FlowCompareComponent::reg(); - flowRegisterComponent("Delay", &flow_delay_component, flow_delay_component_json, + flowRegisterComponent("Delay", &flow_delay_component, (flow_component_factory_f)([] (JsonObject& properties) { return new FlowDelayComponent(properties); })); - flowRegisterComponent("Timer", &flow_timer_component, flow_timer_component_json, + flowRegisterComponent("Timer", &flow_timer_component, (flow_component_factory_f)([] (JsonObject& properties) { return new FlowTimerComponent(properties); })); - flowRegisterComponent("Gate", &flow_gate_component, flow_gate_component_json, + flowRegisterComponent("Gate", &flow_gate_component, (flow_component_factory_f)([] (JsonObject& properties) { return new FlowGateComponent(properties); })); - flowRegisterComponent("Hysteresis", &flow_hysteresis_component, flow_hysteresis_component_json, + flowRegisterComponent("Hysteresis", &flow_hysteresis_component, (flow_component_factory_f)([] (JsonObject& properties) { return new FlowHysteresisComponent(properties); })); } diff --git a/code/espurna/light.ino b/code/espurna/light.ino index 51f4a50fdd..1ac68779b3 100644 --- a/code/espurna/light.ino +++ b/code/espurna/light.ino @@ -662,16 +662,6 @@ PROGMEM const FlowConnections flow_light_component = { 0, NULL, }; -PROGMEM const char flow_light_component_json[] = - "\"Light\": " - "{" - "\"name\":\"Light\"," - "\"icon\":\"sun-o\"," - "\"inports\":[{\"name\":\"Color\",\"type\":\"string\"}, {\"name\":\"Brightness\",\"type\":\"int\"}]," - "\"outports\":[]," - "\"properties\":[]" - "}"; - class FlowLightComponent : public FlowComponent { public: FlowLightComponent(JsonObject& properties) { @@ -688,7 +678,7 @@ class FlowLightComponent : public FlowComponent { } static void reg() { - flowRegisterComponent("Light", &flow_light_component, flow_light_component_json, + flowRegisterComponent("Light", &flow_light_component, (flow_component_factory_f)([] (JsonObject& properties) { return new FlowLightComponent(properties); })); } }; diff --git a/code/espurna/mqtt.ino b/code/espurna/mqtt.ino index 4eab6aa762..b40179f21d 100644 --- a/code/espurna/mqtt.ino +++ b/code/espurna/mqtt.ino @@ -370,16 +370,6 @@ PROGMEM const FlowConnections flow_mqtt_subscribe_component = { 1, flow_data_array, }; -PROGMEM const char flow_mqtt_subscribe_component_json[] = - "\"MQTT subscribe\": " - "{" - "\"name\":\"MQTT subscribe\"," - "\"icon\":\"sign-out\"," - "\"inports\":[]," - "\"outports\":[{\"name\":\"Data\",\"type\":\"string\"}]," - "\"properties\":[{\"name\":\"Topic\",\"type\":\"string\"}]" - "}"; - class FlowMqttSubscribeComponent : public FlowComponent { private: String _topic; @@ -418,16 +408,6 @@ PROGMEM const FlowConnections flow_mqtt_publish_component = { 0, NULL, }; -PROGMEM const char flow_mqtt_publish_component_json[] = - "\"MQTT publish\": " - "{" - "\"name\":\"MQTT publish\"," - "\"icon\":\"sign-in\"," - "\"inports\":[{\"name\":\"Data\",\"type\":\"string\"}]," - "\"outports\":[]," - "\"properties\":[{\"name\":\"Topic\",\"type\":\"string\"}, {\"name\":\"Retain\",\"type\":\"bool\"}]" - "}"; - class FlowMqttPublishComponent : public FlowComponent { private: String _topic; @@ -934,10 +914,10 @@ void mqttSetup() { #endif #if FLOW_SUPPORT - flowRegisterComponent("MQTT subscribe", &flow_mqtt_subscribe_component, flow_mqtt_subscribe_component_json, + flowRegisterComponent("MQTT subscribe", &flow_mqtt_subscribe_component, (flow_component_factory_f)([] (JsonObject& properties) { return new FlowMqttSubscribeComponent(properties); })); - flowRegisterComponent("MQTT publish", &flow_mqtt_publish_component, flow_mqtt_publish_component_json, + flowRegisterComponent("MQTT publish", &flow_mqtt_publish_component, (flow_component_factory_f)([] (JsonObject& properties) { return new FlowMqttPublishComponent(properties); })); #endif diff --git a/code/espurna/relay.ino b/code/espurna/relay.ino index b56be7e3d8..d651104dac 100644 --- a/code/espurna/relay.ino +++ b/code/espurna/relay.ino @@ -1048,16 +1048,6 @@ PROGMEM const FlowConnections flow_relay_component = { 0, NULL, }; -PROGMEM const char flow_relay_component_json[] = - "\"Relay\": " - "{" - "\"name\":\"Relay\"," - "\"icon\":\"lightbulb-o\"," - "\"inports\":[{\"name\":\"State\",\"type\":\"bool\"}, {\"name\":\"Toggle\",\"type\":\"any\"}]," - "\"outports\":[]," - "\"properties\":[{\"name\":\"Relay\",\"type\":\"list\"}]" - "}"; - class FlowRelayComponent : public FlowComponent { private: int _relay_id; @@ -1146,9 +1136,9 @@ void relaySetup() { relays->push_back(String(i)); } - flowRegisterComponent("Relay", &flow_relay_component, flow_relay_component_json, + flowRegisterComponent("Relay", &flow_relay_component, (flow_component_factory_f)([] (JsonObject& properties) { return new FlowRelayComponent(properties); })); - flowRegisterComponentValues("Relay", "Relay", relays); + flowRegisterComponentValues("RELAY_VALUES", relays); #endif diff --git a/code/espurna/scheduler.ino b/code/espurna/scheduler.ino index 9be0186e98..e21dd20537 100644 --- a/code/espurna/scheduler.ino +++ b/code/espurna/scheduler.ino @@ -71,16 +71,6 @@ PROGMEM const FlowConnections flow_schedule_component = { 1, flow_data_array, }; -PROGMEM const char flow_schedule_component_json[] = - "\"Schedule\": " - "{" - "\"name\":\"Schedule\"," - "\"icon\":\"calendar\"," - "\"inports\":[]," - "\"outports\":[{\"name\":\"Data\",\"type\":\"bool\"}]," - "\"properties\":[{\"name\":\"Time\",\"type\":\"time\"}, {\"name\":\"Weekdays\",\"type\":\"weekdays\"},{\"name\":\"Value\",\"type\":\"any\"}]" - "}"; - class FlowScheduleComponent; std::vector _schedule_components; @@ -295,7 +285,7 @@ void schSetup() { #endif #if FLOW_SUPPORT - flowRegisterComponent("Schedule", &flow_schedule_component, flow_schedule_component_json, + flowRegisterComponent("Schedule", &flow_schedule_component, (flow_component_factory_f)([] (JsonObject& properties) { return new FlowScheduleComponent(properties); })); #endif diff --git a/code/espurna/sensor.ino b/code/espurna/sensor.ino index 6a257d9263..f8b11e8208 100644 --- a/code/espurna/sensor.ino +++ b/code/espurna/sensor.ino @@ -389,16 +389,6 @@ PROGMEM const FlowConnections flow_sensor_component = { 1, flow_data_array, }; -PROGMEM const char flow_sensor_component_json[] = - "\"Sensor\": " - "{" - "\"name\":\"Sensor\"," - "\"icon\":\"thermometer-3\"," - "\"inports\":[]," - "\"outports\":[{\"name\":\"Data\",\"type\":\"double\"}]," - "\"properties\":[{\"name\":\"Sensor\",\"type\":\"list\"}]" - "}"; - class FlowSensorComponent; std::vector _flow_sensors; @@ -1440,9 +1430,9 @@ void sensorSetup() { sensors->push_back(sensor + "/" + topic); } - flowRegisterComponent("Sensor", &flow_sensor_component, flow_sensor_component_json, + flowRegisterComponent("Sensor", &flow_sensor_component, (flow_component_factory_f)([] (JsonObject& properties) { return new FlowSensorComponent(properties); })); - flowRegisterComponentValues("Sensor", "Sensor", sensors); + flowRegisterComponentValues("SENSOR_VALUES", sensors); #endif // Main callbacks diff --git a/code/espurna/settings.ino b/code/espurna/settings.ino index 29b7598541..e61a2a6fe1 100644 --- a/code/espurna/settings.ino +++ b/code/espurna/settings.ino @@ -225,16 +225,6 @@ PROGMEM const FlowConnections flow_save_setting_component = { 0, NULL, }; -PROGMEM const char flow_save_setting_component_json[] = - "\"Save setting\": " - "{" - "\"name\":\"Save setting\"," - "\"icon\":\"save\"," - "\"inports\":[{\"name\":\"Value\",\"type\":\"string\"}]," - "\"outports\":[]," - "\"properties\":[{\"name\":\"Name\",\"type\":\"string\"}]" - "}"; - class FlowSaveSettingComponent : public FlowComponent { private: String _name; @@ -255,7 +245,7 @@ class FlowSaveSettingComponent : public FlowComponent { } static void reg() { - flowRegisterComponent("Save setting", &flow_save_setting_component, flow_save_setting_component_json, + flowRegisterComponent("Save setting", &flow_save_setting_component, (flow_component_factory_f)([] (JsonObject& properties) { return new FlowSaveSettingComponent(properties); })); } }; @@ -268,16 +258,6 @@ PROGMEM const FlowConnections flow_load_setting_component = { 1, flow_value_array, }; -PROGMEM const char flow_load_setting_component_json[] = - "\"Load setting\": " - "{" - "\"name\":\"Load setting\"," - "\"icon\":\"database\"," - "\"inports\":[{\"name\":\"Name\",\"type\":\"string\"}]," - "\"outports\":[{\"name\":\"Value\",\"type\":\"string\"}]," - "\"properties\":[{\"name\":\"Default\",\"type\":\"string\"}]" - "}"; - class FlowLoadSettingComponent : public FlowComponent { private: String _default; @@ -296,7 +276,7 @@ class FlowLoadSettingComponent : public FlowComponent { } static void reg() { - flowRegisterComponent("Load setting", &flow_load_setting_component, flow_load_setting_component_json, + flowRegisterComponent("Load setting", &flow_load_setting_component, (flow_component_factory_f)([] (JsonObject& properties) { return new FlowLoadSettingComponent(properties); })); } }; diff --git a/code/espurna/web.ino b/code/espurna/web.ino index b182f67efc..09d85f559c 100644 --- a/code/espurna/web.ino +++ b/code/espurna/web.ino @@ -365,46 +365,7 @@ void _onGetFlowLibrary(AsyncWebServerRequest *request) { return request->requestAuthentication(getSetting("hostname").c_str()); } - // chunked response to avoid out of memory for big number of components - int* ip = new int(0); // index of component in loop - AsyncWebServerResponse *response = request->beginChunkedResponse("text/json", [ip](uint8_t *buffer, size_t maxLen, size_t index) -> size_t { - size_t size = 0; - int i = *ip; - - if (i < 0) { // no more components left - delete ip; - return size; - } - - buffer[0] = i == 0 ? '{' : ','; - buffer++; size++; maxLen--; - - const char* json = flowGetComponentJson(i); - if (json != NULL) { - strncpy_P((char*)buffer, json, maxLen); - int count = strnlen_P(json, maxLen); - size += count; //maxLen -= count; buffer += count; - - i++; - } else { - // last chunk is always list values array - strcpy((char*)buffer, "\"_values\": "); - buffer+=11; size+=11; maxLen-=11; - - DynamicJsonBuffer jsonBuffer; - JsonArray& values = jsonBuffer.createArray(); - flowGetComponentValuesJson(values); - int count = values.printTo((char*)buffer, maxLen); - size += count; - buffer += count; - - buffer[0] = '}'; - size++; - i = -1; // last chunk - } - *ip = i; - return size; - }); + AsyncWebServerResponse *response = request->beginResponse_P(200, "text/json", flow_library_json, flowLibraryProcessor); response->addHeader("Content-Disposition", "inline; filename=\"library.json\""); response->addHeader("X-XSS-Protection", "1; mode=block"); From 98394ba7f0b8636223d1f11c6c1edf8464f4034a Mon Sep 17 00:00:00 2001 From: Eugene Schava Date: Fri, 8 Mar 2019 09:07:20 +0200 Subject: [PATCH 38/40] flow: clear library after flow is loaded --- code/espurna/flow.h | 5 +++++ code/espurna/flow.ino | 1 + 2 files changed, 6 insertions(+) diff --git a/code/espurna/flow.h b/code/espurna/flow.h index 110eceba9d..9129c614c3 100644 --- a/code/espurna/flow.h +++ b/code/espurna/flow.h @@ -128,4 +128,9 @@ class FlowComponentLibrary { return -1; } + + void clear() { + _connectionsMap.clear(); + _factoryMap.clear(); + } }; diff --git a/code/espurna/flow.ino b/code/espurna/flow.ino index a844ee6e98..7e9b2449e5 100644 --- a/code/espurna/flow.ino +++ b/code/espurna/flow.ino @@ -112,6 +112,7 @@ void flowStart() { source.close(); #endif + _library.clear(); // clear library to release memory _flow_started = true; } From e0e8276540778ea19087ecefb7813cafc790f56d Mon Sep 17 00:00:00 2001 From: Eugene Schava Date: Fri, 8 Mar 2019 09:21:19 +0200 Subject: [PATCH 39/40] flow: memory optimization and refactoring --- code/espurna/espurna.ino | 12 +++--------- code/espurna/flow.ino | 4 ++++ code/espurna/settings.ino | 9 +++++---- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/code/espurna/espurna.ino b/code/espurna/espurna.ino index 3681c91d15..96380b3848 100644 --- a/code/espurna/espurna.ino +++ b/code/espurna/espurna.ino @@ -114,13 +114,6 @@ void setup() { apiSetup(); #endif - #if FLOW_SUPPORT - // register default flow components first - flowSetup(); - // settings component after - settingsFlowSetup(); - #endif - // lightSetup must be called before relaySetup #if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE lightSetup(); @@ -199,8 +192,9 @@ void setup() { #ifdef FOXEL_LIGHTFOX_DUAL lightfoxSetup(); #endif - #if FLOW_SUPPORT && SPIFFS_SUPPORT - flowStart(); + #if FLOW_SUPPORT + // after all other components are set up + flowSetup(); #endif diff --git a/code/espurna/flow.ino b/code/espurna/flow.ino index 7e9b2449e5..fb6d07baf6 100644 --- a/code/espurna/flow.ino +++ b/code/espurna/flow.ino @@ -686,6 +686,10 @@ void flowSetup() { flowRegisterComponent("Hysteresis", &flow_hysteresis_component, (flow_component_factory_f)([] (JsonObject& properties) { return new FlowHysteresisComponent(properties); })); + + #if SPIFFS_SUPPORT + flowStart(); + #endif } #endif // FLOW_SUPPORT diff --git a/code/espurna/settings.ino b/code/espurna/settings.ino index e61a2a6fe1..f8e2a71d78 100644 --- a/code/espurna/settings.ino +++ b/code/espurna/settings.ino @@ -281,10 +281,6 @@ class FlowLoadSettingComponent : public FlowComponent { } }; -void settingsFlowSetup() { - FlowSaveSettingComponent::reg(); - FlowLoadSettingComponent::reg(); -} #endif // FLOW_SUPPORT @@ -305,4 +301,9 @@ void settingsSetup() { #endif ); + #if FLOW_SUPPORT + FlowSaveSettingComponent::reg(); + FlowLoadSettingComponent::reg(); + #endif + } \ No newline at end of file From 3330d19d003be2f159168e6f84966842a37d5972 Mon Sep 17 00:00:00 2001 From: Eugene Schava Date: Tue, 23 Apr 2019 12:56:34 +0300 Subject: [PATCH 40/40] flow: terminal component --- code/espurna/config/progmem.h | 10 ++++++++ code/espurna/flow.ino | 47 +++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/code/espurna/config/progmem.h b/code/espurna/config/progmem.h index 687f160127..29de646100 100644 --- a/code/espurna/config/progmem.h +++ b/code/espurna/config/progmem.h @@ -449,6 +449,16 @@ PROGMEM const char flow_library_json[] = "\"outports\":[{\"name\":\"Value\",\"type\":\"string\"}]," "\"properties\":[{\"name\":\"Default\",\"type\":\"string\"}]" "}" +#if TERMINAL_SUPPORT + ",\"Terminal\": " + "{" + "\"name\":\"Terminal\"," + "\"icon\":\"terminal\"," + "\"inports\":[{\"name\":\"Run\",\"type\":\"any\"}, {\"name\":\"Command\",\"type\":\"string\"}]," + "\"outports\":[]," + "\"properties\":[{\"name\":\"Command\",\"type\":\"string\"}]" + "}" +#endif #if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE ",\"Light\": " "{" diff --git a/code/espurna/flow.ino b/code/espurna/flow.ino index fb6d07baf6..36737e2b49 100644 --- a/code/espurna/flow.ino +++ b/code/espurna/flow.ino @@ -640,6 +640,49 @@ class FlowHysteresisComponent : public FlowComponent { } }; +// ----------------------------------------------------------------------------- +// Terminal component +// ----------------------------------------------------------------------------- + +#if TERMINAL_SUPPORT + +PROGMEM const char flow_run[] = "Run"; +PROGMEM const char flow_command[] = "Command"; +PROGMEM const char* const flow_terminal_inputs[] = {flow_run, flow_command}; + +PROGMEM const FlowConnections flow_terminal_component = { + 2, flow_terminal_inputs, + 0, NULL, +}; + +class FlowTerminalComponent : public FlowComponent { + private: + String _command; + + public: + FlowTerminalComponent(JsonObject& properties) { + const char * command = properties["Command"]; + _command = String(command != NULL ? command : ""); + } + + virtual void processInput(JsonVariant& data, int inputNumber) { + if (inputNumber == 0) { + char buffer[_command.length() + 2]; + snprintf(buffer, sizeof(buffer), "%s\n", _command.c_str()); + terminalInject((void*) buffer, strlen(buffer)); + } else if (inputNumber == 1) { + _command = toString(data); + } + } + + static void reg() { + flowRegisterComponent("Terminal", &flow_terminal_component, + (flow_component_factory_f)([] (JsonObject& properties) { return new FlowTerminalComponent(properties); })); + } +}; + +#endif // TERMINAL_SUPPORT + #if !SPIFFS_SUPPORT && MQTT_SUPPORT void _flowMQTTCallback(unsigned int type, const char * topic, const char * payload) { @@ -687,6 +730,10 @@ void flowSetup() { flowRegisterComponent("Hysteresis", &flow_hysteresis_component, (flow_component_factory_f)([] (JsonObject& properties) { return new FlowHysteresisComponent(properties); })); + #if TERMINAL_SUPPORT + FlowTerminalComponent::reg(); + #endif + #if SPIFFS_SUPPORT flowStart(); #endif