Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

flow-based programming #1521

Open
wants to merge 43 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
2a992f6
flow: initial commit
eschava Feb 2, 2019
1c54ac7
flow: major refactoring + button component
eschava Feb 3, 2019
be7be8b
flow: compilation errors
eschava Feb 4, 2019
05938cf
flow: optimization: serve flow.json file right from the SPIFFS filesy…
eschava Feb 4, 2019
faf5cce
flow: optimization: serve flow.json file right from the SPIFFS filesy…
eschava Feb 4, 2019
e5795e8
flow: relay component
eschava Feb 4, 2019
ef966a1
flow: topic placeholders in MQTT components
eschava Feb 5, 2019
d1cebaa
flow: delay component
eschava Feb 5, 2019
70f0d05
flow: change component
eschava Feb 7, 2019
49e4c75
flow: timer and schedule components
eschava Feb 7, 2019
4f759c4
flow: chunked response to avoid out of memory for big number of compo…
eschava Feb 8, 2019
ed7a647
flow: start component
eschava Feb 9, 2019
ebb25da
flow: correct order of components in library
eschava Feb 9, 2019
d1c93b3
flow: gate component
eschava Feb 9, 2019
e19b859
flow: hysteresis component
eschava Feb 10, 2019
41d860a
flow: support compressed JSON in flow config
eschava Feb 10, 2019
a77132b
flow: schedule component - minor optimization
eschava Feb 10, 2019
5a8a33c
flow: sensor component
eschava Feb 13, 2019
66a125e
flow: sensor component
eschava Feb 13, 2019
001e3b9
flow: gate component
eschava Feb 13, 2019
7574de4
flow: memory optimizations and refactoring
eschava Feb 16, 2019
2a1a09c
flow: memory optimizations and refactoring
eschava Feb 16, 2019
cb2cb5e
flow: memory optimizations and refactoring
eschava Feb 18, 2019
f59ae97
flow: have Value property for start/timer/schedule components
eschava Feb 20, 2019
5bc2e7e
flow: setting load/save components
eschava Feb 20, 2019
61a0b77
flow: setting load/save components
eschava Feb 20, 2019
90b90a2
flow: use MQTT retained message to save flow config if SPIFFS is not …
eschava Feb 21, 2019
dd9509a
flow: use MQTT retained message to save flow config if SPIFFS is not …
eschava Feb 21, 2019
fe49b56
flow: math component
eschava Feb 21, 2019
327c924
Merge remote-tracking branch 'origin/dev' into flow
eschava Feb 22, 2019
64dfb47
better compression of flow
eschava Feb 22, 2019
5e8666c
flow: compare component
eschava Feb 22, 2019
2c8a044
flow: hysteresis component
eschava Feb 22, 2019
620e410
flow: different fixes
eschava Feb 28, 2019
f14f676
flow: light component
eschava Mar 2, 2019
95428dc
flow: use Ticker for delayed task execution
eschava Mar 6, 2019
80c6288
flow: use Ticker for delayed task execution
eschava Mar 7, 2019
c375ea3
flow: memory optimization and refactoring
eschava Mar 7, 2019
413b8d2
Merge remote-tracking branch 'origin/dev' into flow
eschava Mar 8, 2019
98394ba
flow: clear library after flow is loaded
eschava Mar 8, 2019
e0e8276
flow: memory optimization and refactoring
eschava Mar 8, 2019
d457811
Merge remote-tracking branch 'origin/dev' into flow
eschava Apr 23, 2019
3330d19
flow: terminal component
eschava Apr 23, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 67 additions & 4 deletions code/espurna/button.ino
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,17 @@ Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
#include <DebounceEvent.h>
#include <vector>

#if FLOW_SUPPORT
class FlowButtonComponent; // forward declaration
#endif

typedef struct {
DebounceEvent * button;
unsigned long actions;
unsigned int relayID;
#if FLOW_SUPPORT
std::vector<FlowButtonComponent *> flow_components;
#endif
} button_t;

std::vector<button_t> _buttons;
Expand All @@ -42,6 +49,45 @@ bool _buttonWebSocketOnReceive(const char * key, JsonVariant& value) {

#endif

// -----------------------------------------------------------------------------
// FLOW
// -----------------------------------------------------------------------------

#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) {
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;
Expand Down Expand Up @@ -105,6 +151,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 (BUTTON_MODE_TOGGLE == action) {
if (_buttons[id].relayID > 0) {
relayToggle(_buttons[id].relayID - 1);
Expand All @@ -122,11 +174,11 @@ void buttonEvent(unsigned int id, unsigned char event) {
relayStatus(_buttons[id].relayID - 1, false);
}
}

if (BUTTON_MODE_AP == action) {
wifiStartAP();
}

if (BUTTON_MODE_RESET == action) {
deferredReset(100, CUSTOM_RESET_HARDWARE);
}
Expand All @@ -142,13 +194,13 @@ void buttonEvent(unsigned int id, unsigned char event) {
wifiStartWPS();
}
#endif // defined(JUSTWIFI_ENABLE_WPS)

#if defined(JUSTWIFI_ENABLE_SMARTCONFIG)
if (BUTTON_MODE_SMART_CONFIG == action) {
wifiStartSmartConfig();
}
#endif // defined(JUSTWIFI_ENABLE_SMARTCONFIG)

#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
if (BUTTON_MODE_DIM_UP == action) {
lightBrightnessStep(1);
Expand Down Expand Up @@ -233,6 +285,17 @@ void buttonSetup() {
wsOnReceiveRegister(_buttonWebSocketOnReceive);
#endif

#if FLOW_SUPPORT
std::vector<String>* buttons = new std::vector<String>();
for (unsigned int i=0; i < _buttons.size(); i++) {
buttons->push_back(String(i));
}

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
espurnaRegisterLoop(buttonLoop);

Expand Down
12 changes: 12 additions & 0 deletions code/espurna/config/general.h
Original file line number Diff line number Diff line change
Expand Up @@ -1492,3 +1492,15 @@
#ifndef RFM69_IS_RFM69HW
#define RFM69_IS_RFM69HW 0
#endif

#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
13 changes: 13 additions & 0 deletions code/espurna/config/prototypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -210,3 +210,16 @@ void webRequestRegister(web_request_callback_f callback);
typedef std::function<void(justwifi_messages_t code, char * parameter)> wifi_callback_f;
void wifiRegister(wifi_callback_f callback);
bool wifiConnected();

// -----------------------------------------------------------------------------
// FLOW
// -----------------------------------------------------------------------------

#if FLOW_SUPPORT
#include "flow.h"
typedef std::function<FlowComponent* (JsonObject&)> flow_component_factory_f;
void flowRegisterComponentValues(String component, String property, std::vector<String>* values);
#else
#define FlowConnections void
#define flow_component_factory_f void *
#endif
10 changes: 10 additions & 0 deletions code/espurna/espurna.ino
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,13 @@ 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();
Expand Down Expand Up @@ -189,6 +196,9 @@ void setup() {
#if UART_MQTT_SUPPORT
uartmqttSetup();
#endif
#if FLOW_SUPPORT && SPIFFS_SUPPORT
flowStart();
#endif


// 3rd party code hook
Expand Down
139 changes: 139 additions & 0 deletions code/espurna/flow.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
#pragma once

#include <vector>
#include <map>

struct FlowConnections {
int inputsNumber;
const char* const* inputs;
int outputsNumber;
const char* const* outputs;
};

class FlowComponent {
private:
typedef struct {
FlowComponent* component;
int inputNumber;
} output_t;

std::vector<std::vector<output_t>> _outputs;

protected:
void processOutput(JsonVariant& data, int outputNumber) {
if (outputNumber < _outputs.size()) {
for (output_t output : _outputs[outputNumber])
output.component->processInput(data, output.inputNumber);
}
}

JsonVariant* clone(JsonVariant& data) {
if (data.is<int>()) {
return new JsonVariant(data.as<int>());
} else if (data.is<double>()) {
return new JsonVariant(data.as<double>());
} else if (data.is<bool>()) {
return new JsonVariant(data.as<bool>());
} else if (data.is<char*>()) {
char *str = strdup(data.as<const char*>());
return new JsonVariant(str);
} else {
return new JsonVariant(data);
}
}

String toString(JsonVariant& data) {
if (data.is<int>()) {
return String(data.as<int>());
} else if (data.is<double>()) {
return String(data.as<double>(), 3);
} else if (data.is<bool>()) {
return String(data.as<bool>() ? "<true>" : "<false>");
} else if (data.is<char*>()) {
return String(data.as<const char*>());
} else {
return String();
}
}

void release(JsonVariant* data) {
if (data == NULL)
return;

if (data->is<char*>()) {
void* str = (void*)data->as<char*>();
free(str);
}
delete data;
}

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<FlowComponent* (JsonObject&)> flow_component_factory_f;

class FlowComponentLibrary {
private:
std::vector<const char*> _jsons;
std::map<String, const FlowConnections*> _connectionsMap;
std::map<String, flow_component_factory_f> _factoryMap;

public:
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;
}

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;
}

int getInputNumber(String& name, String& input) {
const FlowConnections* connections = _connectionsMap[name];
if (connections == NULL)
return -1;

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, String& output) {
const FlowConnections* connections = _connectionsMap[name];
if (connections == NULL)
return -1;

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;
}

return -1;
}
};
Loading