Browse Source

Rules RPN (#1984)

* RPN rules (WIP)

* RPN rules web interface, MQTT inputs

* Stickyness, rpn.ops,...

* Perform light updates only when value changes

* Improve wsSend performance

* Revert PR test

* Check TERMINAL_SUPPORT for _rpnInitCommands and remove unused variable

* Fix merge

* formatting

* disable by default (?)

* changelog

* comment

* remove debug function wrappers in favour of var activation

* fixup! comment
master
Max Prokhorov 5 years ago
committed by GitHub
parent
commit
849f8cf920
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 22363 additions and 21517 deletions
  1. +2
    -0
      CHANGELOG.md
  2. +1
    -0
      code/espurna/config/arduino.h
  3. +5
    -0
      code/espurna/config/dependencies.h
  4. +19
    -2
      code/espurna/config/general.h
  5. +8
    -0
      code/espurna/config/hardware.h
  6. BIN
      code/espurna/data/index.all.html.gz
  7. BIN
      code/espurna/data/index.light.html.gz
  8. BIN
      code/espurna/data/index.lightfox.html.gz
  9. BIN
      code/espurna/data/index.rfbridge.html.gz
  10. BIN
      code/espurna/data/index.rfm69.html.gz
  11. BIN
      code/espurna/data/index.sensor.html.gz
  12. BIN
      code/espurna/data/index.small.html.gz
  13. BIN
      code/espurna/data/index.thermostat.html.gz
  14. +3
    -0
      code/espurna/espurna.ino
  15. +1
    -4
      code/espurna/light.ino
  16. +1
    -0
      code/espurna/ntp.ino
  17. +315
    -0
      code/espurna/rpnrules.ino
  18. +10
    -1
      code/espurna/sensor.ino
  19. +3013
    -2972
      code/espurna/static/index.all.html.gz.h
  20. +2843
    -2800
      code/espurna/static/index.light.html.gz.h
  21. +2388
    -2346
      code/espurna/static/index.lightfox.html.gz.h
  22. +2437
    -2395
      code/espurna/static/index.rfbridge.html.gz.h
  23. +3853
    -3813
      code/espurna/static/index.rfm69.html.gz.h
  24. +2479
    -2436
      code/espurna/static/index.sensor.html.gz.h
  25. +2388
    -2346
      code/espurna/static/index.small.html.gz.h
  26. +2422
    -2380
      code/espurna/static/index.thermostat.html.gz.h
  27. +3
    -1
      code/html/custom.css
  28. +94
    -18
      code/html/custom.js
  29. +76
    -3
      code/html/index.html
  30. +1
    -0
      code/platformio.ini
  31. +1
    -0
      code/test/build/nondefault.h

+ 2
- 0
CHANGELOG.md View File

@ -68,6 +68,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### Added
#### General
- [RPN Rules](https://github.com/xoseperez/espurna/wiki/RPN-Rules) - custom rules to execute actions (mostly changing relay and light statuses) based on different inputs ([#1984](https://github.com/xoseperez/espurna/issues/1984), thanks to **[@xoseperez](https://github.com/xoseperez)**)
- Initial implementation of RTCMEM storage to preserve state (relay status, stability counter, energy etc.) between reboots ([#1420](https://github.com/xoseperez/espurna/issues/1420), [#1770](https://github.com/xoseperez/espurna/issues/1770))
- Allow to configure all LEDs from UI ([#1429](https://github.com/xoseperez/espurna/issues/1429), thanks to **[@xoseperez](https://github.com/xoseperez)**)
- SYNC_FIRST relay sync mode ([#1609](https://github.com/xoseperez/espurna/issues/1609), thanks to **[@foxel](https://github.com/foxel)**)
@ -136,6 +137,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Added LDR sensor (Thanks to Altan Altay)
- ADE9753 Support ([#1827](https://github.com/xoseperez/espurna/issues/1827), thanks to **[@tonilopezmr](https://github.com/tonilopezmr)**)
- Telaire T6613 Support ([#1956](https://github.com/xoseperez/espurna/issues/1956), thanks to **[@james-coder](https://github.com/james-coder)**)
- Adding support for miobulb001 ([#1973](https://github.com/xoseperez/espurna/issues/1973), thanks to **[@ealfaroc](https://github.com/ealfaroc)**)
#### Lights
- Allow to set relative brightness, channel value and color in mireds using +N and -N notation ([#1607](https://github.com/xoseperez/espurna/issues/1607), [#1938](https://github.com/xoseperez/espurna/pull/1938), thanks to **[@tsymbaliuk](https://github.com/tsymbaliuk)**)
- Two channel CCT ([#1732](https://github.com/xoseperez/espurna/issues/1732), thanks to **[@copyrights](https://github.com/copyrights)**)


+ 1
- 0
code/espurna/config/arduino.h View File

@ -172,6 +172,7 @@
//#define NTP_SUPPORT 0
//#define RF_SUPPORT 1
//#define RFM69_SUPPORT 1
//#define RPN_RULES_SUPPORT 0
//#define SCHEDULER_SUPPORT 0
//#define SENSOR_SUPPORT 1
//#define SPIFFS_SUPPORT 1


+ 5
- 0
code/espurna/config/dependencies.h View File

@ -43,6 +43,11 @@
#define BROKER_SUPPORT 1 // If Alexa enabled enable BROKER
#endif
#if RPN_RULES_SUPPORT
#undef BROKER_SUPPORT
#define BROKER_SUPPORT 1 // If RPN Rules enabled enable BROKER
#endif
#if INFLUXDB_SUPPORT
#undef BROKER_SUPPORT
#define BROKER_SUPPORT 1 // If InfluxDB enabled enable BROKER


+ 19
- 2
code/espurna/config/general.h View File

@ -1054,6 +1054,7 @@
#define MQTT_TOPIC_VERSION "version"
#define MQTT_TOPIC_UPTIME "uptime"
#define MQTT_TOPIC_DATETIME "datetime"
#define MQTT_TOPIC_TIMESTAMP "timestamp"
#define MQTT_TOPIC_FREEHEAP "freeheap"
#define MQTT_TOPIC_VCC "vcc"
#ifndef MQTT_TOPIC_STATUS
@ -1132,6 +1133,10 @@
#define BROKER_SUPPORT 1 // The broker is a poor-man's pubsub manager
#endif
#ifndef BROKER_REAL_TIME
#define BROKER_REAL_TIME 1 // Report real time data
#endif
// -----------------------------------------------------------------------------
// SETTINGS
// -----------------------------------------------------------------------------
@ -1388,17 +1393,29 @@
// -----------------------------------------------------------------------------
#ifndef SCHEDULER_SUPPORT
#define SCHEDULER_SUPPORT 1 // Enable scheduler (1.77Kb)
#define SCHEDULER_SUPPORT 1 // Enable scheduler (2.45Kb)
#endif
#ifndef SCHEDULER_MAX_SCHEDULES
#define SCHEDULER_MAX_SCHEDULES 10 // Max schedules alowed
#define SCHEDULER_MAX_SCHEDULES 10 // Max schedules alowed
#endif
#ifndef SCHEDULER_RESTORE_LAST_SCHEDULE
#define SCHEDULER_RESTORE_LAST_SCHEDULE 0 // Restore the last schedule state on the device boot
#endif
// -----------------------------------------------------------------------------
// RPN RULES
// -----------------------------------------------------------------------------
#ifndef RPN_RULES_SUPPORT
#define RPN_RULES_SUPPORT 0 // Enable RPN Rules (8.6Kb)
#endif
#ifndef RPN_DELAY
#define RPN_DELAY 100 // Execute rules after 100ms without messages
#endif
// -----------------------------------------------------------------------------
// NTP
// -----------------------------------------------------------------------------


+ 8
- 0
code/espurna/config/hardware.h View File

@ -54,6 +54,7 @@
#define I2C_SUPPORT 0
#define MQTT_SUPPORT 0
#define NTP_SUPPORT 0
#define RPN_RULES_SUPPORT 0
#define SCHEDULER_SUPPORT 0
#define SENSOR_SUPPORT 0
#define THINGSPEAK_SUPPORT 0
@ -180,6 +181,13 @@
#define MANUFACTURER "WEMOS"
#define DEVICE "D1_TARPUNA_SHIELD"
// Relays
#define RELAY1_PIN 5
#define RELAY1_TYPE RELAY_TYPE_NORMAL
#define DHT_SUPPORT 1
#define DHT_PIN 12
// -----------------------------------------------------------------------------
// ESPurna
// -----------------------------------------------------------------------------


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


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


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


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


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


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


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


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


+ 3
- 0
code/espurna/espurna.ino View File

@ -219,6 +219,9 @@ void setup() {
#if SCHEDULER_SUPPORT
schSetup();
#endif
#if RPN_RULES_SUPPORT
rpnSetup();
#endif
#if UART_MQTT_SUPPORT
uartmqttSetup();
#endif


+ 1
- 4
code/espurna/light.ino View File

@ -119,10 +119,7 @@ void _setValue(const unsigned char id, const unsigned int value) {
}
void _setInputValue(const unsigned char id, const unsigned int value) {
if (_light_channel[id].inputValue != value) {
_light_channel[id].inputValue = value;
_light_dirty = true;
}
_light_channel[id].inputValue = value;
}
void _setRGBInputValue(unsigned char red, unsigned char green, unsigned char blue) {


+ 1
- 0
code/espurna/ntp.ino View File

@ -139,6 +139,7 @@ void inline _ntpBroker() {
if (ntpSynced() && (minute() != last_minute)) {
last_minute = minute();
brokerPublish(BROKER_MSG_TYPE_DATETIME, MQTT_TOPIC_DATETIME, ntpDateTime().c_str());
brokerPublish(BROKER_MSG_TYPE_DATETIME, MQTT_TOPIC_TIMESTAMP, String(now()).c_str());
}
}


+ 315
- 0
code/espurna/rpnrules.ino View File

@ -0,0 +1,315 @@
/*
RPN RULES MODULE
Use RPNLib library (https://github.com/xoseperez/rpnlib)
Copyright (C) 2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if RPN_RULES_SUPPORT
#include "rpnlib.h"
// -----------------------------------------------------------------------------
// Custom commands
// -----------------------------------------------------------------------------
rpn_context _rpn_ctxt;
bool _rpn_run = false;
unsigned long _rpn_delay = RPN_DELAY;
unsigned long _rpn_last = 0;
// -----------------------------------------------------------------------------
bool _rpnWebSocketOnKeyCheck(const char * key, JsonVariant& value) {
return (strncmp(key, "rpn", 3) == 0);
}
void _rpnWebSocketOnConnected(JsonObject& root) {
root["rpnSticky"] = getSetting("rpnSticky", 1).toInt();
root["rpnDelay"] = getSetting("rpnDelay", RPN_DELAY).toInt();
JsonArray& rules = root.createNestedArray("rpnRules");
unsigned char i = 0;
String rule = getSetting("rpnRule", i, "");
while (rule.length()) {
rules.add(rule);
rule = getSetting("rpnRule", ++i, "");
}
#if MQTT_SUPPORT
i=0;
JsonArray& topics = root.createNestedArray("rpnTopics");
JsonArray& names = root.createNestedArray("rpnNames");
String rpn_topic = getSetting("rpnTopic", i, "");
while (rpn_topic.length() > 0) {
String rpn_name = getSetting("rpnName", i, "");
topics.add(rpn_topic);
names.add(rpn_name);
rpn_topic = getSetting("rpnTopic", ++i, "");
}
#endif
}
#if MQTT_SUPPORT
void _rpnMQTTSubscribe() {
unsigned char i = 0;
String rpn_topic = getSetting("rpnTopic", i, "");
while (rpn_topic.length()) {
mqttSubscribeRaw(rpn_topic.c_str());
rpn_topic = getSetting("rpnTopic", ++i, "");
}
}
void _rpnMQTTCallback(unsigned int type, const char * topic, const char * payload) {
if (type == MQTT_CONNECT_EVENT) {
_rpnMQTTSubscribe();
}
if (type == MQTT_MESSAGE_EVENT) {
unsigned char i = 0;
String rpn_topic = getSetting("rpnTopic", i, "");
while (rpn_topic.length()) {
if (rpn_topic.equals(topic)) {
String rpn_name = getSetting("rpnName", i, "");
if (rpn_name.length()) {
rpn_variable_set(_rpn_ctxt, rpn_name.c_str(), atof(payload));
_rpn_last = millis();
_rpn_run = true;
break;
}
}
rpn_topic = getSetting("rpnTopic", ++i, "");
}
}
}
#endif // MQTT_SUPPORT
void _rpnConfigure() {
#if MQTT_SUPPORT
if (mqttConnected()) _rpnMQTTSubscribe();
#endif
_rpn_delay = getSetting("rpnDelay", RPN_DELAY).toInt();
}
void _rpnBrokerCallback(const unsigned char type, const char * topic, unsigned char id, const char * payload) {
char name[32] = {0};
if (BROKER_MSG_TYPE_STATUS == type || BROKER_MSG_TYPE_SENSOR == type) {
snprintf(name, sizeof(name), "%s%d", topic, id);
rpn_variable_set(_rpn_ctxt, name, atof(payload));
} else if (BROKER_MSG_TYPE_DATETIME == type) {
// Timestamp is always available via de "now" operator
} else {
return;
}
_rpn_last = millis();
_rpn_run = true;
}
void _rpnInit() {
// Init context
rpn_init(_rpn_ctxt);
// Time functions
rpn_operator_set(_rpn_ctxt, "now", 0, [](rpn_context & ctxt) {
if (!ntpSynced()) return false;
rpn_stack_push(ctxt, now());
return true;
});
rpn_operator_set(_rpn_ctxt, "utc", 0, [](rpn_context & ctxt) {
if (!ntpSynced()) return false;
rpn_stack_push(ctxt, ntpLocal2UTC(now()));
return true;
});
rpn_operator_set(_rpn_ctxt, "dow", 1, [](rpn_context & ctxt) {
float a;
rpn_stack_pop(ctxt, a);
unsigned char dow = (weekday(int(a)) + 5) % 7;
rpn_stack_push(ctxt, dow);
return true;
});
rpn_operator_set(_rpn_ctxt, "hour", 1, [](rpn_context & ctxt) {
float a;
rpn_stack_pop(ctxt, a);
rpn_stack_push(ctxt, hour(int(a)));
return true;
});
rpn_operator_set(_rpn_ctxt, "minute", 1, [](rpn_context & ctxt) {
float a;
rpn_stack_pop(ctxt, a);
rpn_stack_push(ctxt, minute(int(a)));
return true;
});
// Debug
rpn_operator_set(_rpn_ctxt, "debug", 0, [](rpn_context & ctxt) {
_rpnDump();
return true;
});
// Relay operators
rpn_operator_set(_rpn_ctxt, "relay", 2, [](rpn_context & ctxt) {
float a, b;
rpn_stack_pop(ctxt, b); // relay number
rpn_stack_pop(ctxt, a); // new status
if (int(a) == 2) {
relayToggle(int(b));
} else {
relayStatus(int(b), int(a) == 1);
}
return true;
});
// Channel operators
#if RELAY_PROVIDER == RELAY_PROVIDER_LIGHT
rpn_operator_set(_rpn_ctxt, "update", 0, [](rpn_context & ctxt) {
lightUpdate(true, true);
return true;
});
rpn_operator_set(_rpn_ctxt, "black", 0, [](rpn_context & ctxt) {
lightColor((unsigned long) 0);
return true;
});
rpn_operator_set(_rpn_ctxt, "channel", 2, [](rpn_context & ctxt) {
float a, b;
rpn_stack_pop(ctxt, b); // channel number
rpn_stack_pop(ctxt, a); // new value
lightChannel(int(b), int(a));
return true;
});
#endif
}
#if TERMINAL_SUPPORT
void _rpnInitCommands() {
terminalRegisterCommand(F("RPN.VARS"), [](Embedis* e) {
unsigned char num = rpn_variables_size(_rpn_ctxt);
if (0 == num) {
DEBUG_MSG_P(PSTR("[RPN] No variables\n"));
} else {
DEBUG_MSG_P(PSTR("[RPN] Variables:\n"));
for (unsigned char i=0; i<num; i++) {
char * name = rpn_variable_name(_rpn_ctxt, i);
float value;
rpn_variable_get(_rpn_ctxt, name, value);
DEBUG_MSG_P(PSTR(" %s: %s\n"), name, String(value).c_str());
}
}
terminalOK();
});
terminalRegisterCommand(F("RPN.OPS"), [](Embedis* e) {
unsigned char num = _rpn_ctxt.operators.size();
DEBUG_MSG_P(PSTR("[RPN] Operators:\n"));
for (unsigned char i=0; i<num; i++) {
DEBUG_MSG_P(PSTR(" %s (%d)\n"), _rpn_ctxt.operators[i].name, _rpn_ctxt.operators[i].argc);
}
terminalOK();
});
terminalRegisterCommand(F("RPN.TEST"), [](Embedis* e) {
if (e->argc == 2) {
DEBUG_MSG_P(PSTR("[RPN] Running \"%s\"\n"), e->argv[1]);
rpn_process(_rpn_ctxt, e->argv[1], true);
_rpnDump();
rpn_stack_clear(_rpn_ctxt);
terminalOK();
} else {
terminalError(F("Wrong arguments"));
}
});
}
#endif
void _rpnDump() {
float value;
DEBUG_MSG_P(PSTR("[RPN] Stack:\n"));
unsigned char num = rpn_stack_size(_rpn_ctxt);
if (0 == num) {
DEBUG_MSG_P(PSTR(" (empty)\n"));
} else {
unsigned char index = num - 1;
while (rpn_stack_get(_rpn_ctxt, index, value)) {
DEBUG_MSG_P(PSTR(" %02d: %s\n"), index--, String(value).c_str());
}
}
}
void _rpnRun() {
unsigned char i = 0;
String rule = getSetting("rpnRule", i, "");
while (rule.length()) {
//DEBUG_MSG_P(PSTR("[RPN] Running \"%s\"\n"), rule.c_str());
rpn_process(_rpn_ctxt, rule.c_str(), true);
//_rpnDump();
rule = getSetting("rpnRule", ++i, "");
rpn_stack_clear(_rpn_ctxt);
}
if (getSetting("rpnSticky", 1).toInt() == 0) {
rpn_variables_clear(_rpn_ctxt);
}
}
void _rpnLoop() {
if (_rpn_run && (millis() - _rpn_last > _rpn_delay)) {
_rpnRun();
_rpn_run = false;
}
}
void rpnSetup() {
// Init context
_rpnInit();
// Load & cache settings
_rpnConfigure();
// Terminal commands
#if TERMINAL_SUPPORT
_rpnInitCommands();
#endif
// Websockets
#if WEB_SUPPORT
wsRegister()
.onVisible([](JsonObject& root) { root["rpnVisible"] = 1; })
.onConnected(_rpnWebSocketOnConnected)
.onKeyCheck(_rpnWebSocketOnKeyCheck);
#endif
// MQTT
#if MQTT_SUPPORT
mqttRegister(_rpnMQTTCallback);
#endif
brokerRegister(_rpnBrokerCallback);
espurnaRegisterReload(_rpnConfigure);
espurnaRegisterLoop(_rpnLoop);
}
#endif // RPN_RULES_SUPPORT

+ 10
- 1
code/espurna/sensor.ino View File

@ -1541,7 +1541,9 @@ void _sensorReport(unsigned char index, double value) {
dtostrf(value, 1, decimals, buffer);
#if BROKER_SUPPORT
brokerPublish(BROKER_MSG_TYPE_SENSOR ,magnitudeTopic(magnitude.type).c_str(), magnitude.local, buffer);
#if not BROKER_REAL_TIME
brokerPublish(BROKER_MSG_TYPE_SENSOR ,magnitudeTopic(magnitude.type).c_str(), magnitude.global, buffer);
#endif
#endif
#if MQTT_SUPPORT
@ -1798,6 +1800,13 @@ void sensorLoop() {
// -------------------------------------------------------------
value_show = _magnitudeProcess(magnitude.type, magnitude.decimals, value_raw);
#if BROKER_REAL_TIME
{
char buffer[64];
dtostrf(value_show, 1-sizeof(buffer), magnitude.decimals, buffer);
brokerPublish(BROKER_MSG_TYPE_SENSOR ,magnitudeTopic(magnitude.type).c_str(), magnitude.global, buffer);
}
#endif
// -------------------------------------------------------------
// Debug


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


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


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


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


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


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


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


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


+ 3
- 1
code/html/custom.css View File

@ -160,7 +160,7 @@ div.state {
.button-rfb-forget,
.button-lightfox-clear,
.button-del-network,
.button-del-mapping,
.button-del-parent,
.button-del-schedule,
.button-dbg-clear,
.button-upgrade,
@ -175,6 +175,8 @@ div.state {
.button-update-password,
.button-add-network,
.button-add-mapping,
.button-add-rpnrule,
.button-add-rpntopic,
.button-upgrade-browse,
.button-rfb-learn,
.button-lightfox-learn,


+ 94
- 18
code/html/custom.js View File

@ -1,3 +1,4 @@
var debug = false;
var websock;
var password = false;
var maxNetworks;
@ -256,7 +257,8 @@ function addValue(data, name, value) {
"tspkRelay", "tspkMagnitude",
"ledMode", "ledRelay",
"adminPass",
"node", "key", "topic"
"node", "key", "topic",
"rpnRule", "rpnTopic", "rpnName"
];
@ -437,12 +439,17 @@ function initSelectGPIO(select) {
// Actions
// -----------------------------------------------------------------------------
function send(json) {
if (debug) console.log(json);
websock.send(json);
}
function sendAction(action, data) {
websock.send(JSON.stringify({action: action, data: data}));
send(JSON.stringify({action: action, data: data}));
}
function sendConfig(data) {
websock.send(JSON.stringify({config: data}));
send(JSON.stringify({config: data}));
}
function setOriginalsFromValues(force) {
@ -795,6 +802,11 @@ function doClearFilters() {
<!-- endRemoveIf(!rfm69)-->
function delParent() {
var parent = $(this).parent().parent();
$(parent).remove();
}
// -----------------------------------------------------------------------------
// Visualization
// -----------------------------------------------------------------------------
@ -850,11 +862,38 @@ function createMagnitudeList(data, container, template_name) {
}
<!-- endRemoveIf(!sensor)-->
// -----------------------------------------------------------------------------
// RPN Rules
// -----------------------------------------------------------------------------
function addRPNRule() {
var template = $("#rpnRuleTemplate .pure-g")[0];
var line = $(template).clone();
var tabindex = $("#rpnRules > div").length + 100;
$(line).find("input").each(function() {
$(this).attr("tabindex", tabindex++);
});
$(line).find("button").on('click', delParent);
line.appendTo("#rpnRules");
}
function addRPNTopic() {
var template = $("#rpnTopicTemplate .pure-g")[0];
var line = $(template).clone();
var tabindex = $("#rpnTopics > div").length + 120;
$(line).find("input").each(function() {
$(this).attr("tabindex", tabindex++);
});
$(line).find("button").on('click', delParent);
line.appendTo("#rpnTopics");
}
// -----------------------------------------------------------------------------
// RFM69
// -----------------------------------------------------------------------------
<!-- removeIf(!rfm69)-->
function addMapping() {
var template = $("#nodeTemplate .pure-g")[0];
var line = $(template).clone();
@ -862,14 +901,10 @@ function addMapping() {
$(line).find("input").each(function() {
$(this).attr("tabindex", tabindex++);
});
$(line).find("button").on('click', delMapping);
$(line).find("button").on('click', delParent);
line.appendTo("#mapping");
}
function delMapping() {
var parent = $(this).parent().parent();
$(parent).remove();
}
<!-- endRemoveIf(!rfm69)-->
// -----------------------------------------------------------------------------
@ -1348,6 +1383,8 @@ function initLightfox(data, relayCount) {
function processData(data) {
if (debug) console.log(data);
// title
if ("app_name" in data) {
var title = data.app_name;
@ -1472,6 +1509,48 @@ function processData(data) {
<!-- endRemoveIf(!rfm69)-->
// ---------------------------------------------------------------------
// RPN Rules
// ---------------------------------------------------------------------
if (key == "rpnRules") {
for (var i in data.rpnRules) {
// add a new row
addRPNRule();
// get group
var line = $("#rpnRules .pure-g")[i];
// fill in the blanks
var rule = data.rpnRules[i];
$("input", line).val(rule).attr("original", rule);
}
return;
}
if (key == "rpnTopics") {
for (var i in data.rpnTopics) {
// add a new row
addRPNTopic();
// get group
var line = $("#rpnTopics .pure-g")[i];
// fill in the blanks
var topic = data.rpnTopics[i];
var name = data.rpnNames[i];
$("input[name='rpnTopic']", line).val(topic).attr("original", topic);
$("input[name='rpnName']", line).val(name).attr("original", name);
}
return;
}
if (key == "rpnNames") return;
// ---------------------------------------------------------------------
// Lights
// ---------------------------------------------------------------------
@ -1580,7 +1659,7 @@ function processData(data) {
// -----------------------------------------------------------------------------
if ("haConfig" === key) {
websock.send("{}");
send("{}");
$("#haConfig")
.append(new Text(value))
.height($("#haConfig")[0].scrollHeight);
@ -1691,7 +1770,7 @@ function processData(data) {
// Web log
if ("weblog" === key) {
websock.send("{}");
send("{}");
msg = value["msg"];
pre = value["pre"];
@ -1909,13 +1988,6 @@ function connectToCurrentURL() {
connectToURL(new URL(window.location));
}
function enableWSLogging() {
var processDataOrig = window.processData;
window.processData = function(data) { console.log(data); processDataOrig(data); }
var sendActionOrig = window.sendAction;
window.sendAction = function(action, data) { console.log(action,data); sendActionOrig(action, data);}
}
$(function() {
initMessages();
@ -1966,9 +2038,13 @@ $(function() {
$(".button-add-light-schedule").on("click", { schType: 2 }, addSchedule);
<!-- endRemoveIf(!light)-->
$(".button-add-rpnrule").on('click', addRPNRule);
$(".button-add-rpntopic").on('click', addRPNTopic);
$(".button-del-parent").on('click', delParent);
<!-- removeIf(!rfm69)-->
$(".button-add-mapping").on('click', addMapping);
$(".button-del-mapping").on('click', delMapping);
$(".button-clear-counts").on('click', doClearCounts);
$(".button-clear-messages").on('click', doClearMessages);
$(".button-clear-filters").on('click', doClearFilters);


+ 76
- 3
code/html/index.html View File

@ -158,6 +158,10 @@
</li>
<!-- endRemoveIf(!rfbridge) -->
<li class="pure-menu-item module module-rpn">
<a href="#" class="pure-menu-link" data="panel-rpn">RULES</a>
</li>
<li class="pure-menu-item module module-sch">
<a href="#" class="pure-menu-link" data="panel-schedule">SCHEDULE</a>
</li>
@ -1407,6 +1411,60 @@
</div>
</form>
<form id="form-rpn" class="pure-form form-settings">
<div class="panel" id="panel-rpn">
<div class="header">
<h1>RULES</h1>
<h2>
Here you can configure advanced rules based on RPN expressions. Check the <a href="https://github.com/xoseperez/espurna/wiki/RPN-Rules" target="_blank">wiki page about the RPN Rules module</a> to know how to use them.
</h2>
</div>
<div class="page">
<fieldset>
<legend>Configuration</legend>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Sticky variables</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="rpnSticky" tabindex="100" /></div>
<div class="pure-u-0 pure-u-lg-1-2"></div>
<div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint">
Sticky variables are persistent, once defined the first time they will be always available until next reboot (they do not persist across reboots).
Non sticky variables are only available during the next rule execution, just after being defined, and then removed. This applies to status, sensor and MQTT variables equally.
Mind that calling a non-existing variable from a rule will make it silently fail.
</div>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4" for="rpnDelay">Execution delay (ms)</label>
<input class="pure-u-1 pure-u-lg-1-4" name="rpnDelay" type="number" min="100" tabindex="101" />
</div>
<legend>Rules</legend>
<div id="rpnRules"></div>
<button type="button" class="pure-button button-add-rpnrule">Add</button>
<legend>MQTT</legend>
<div class="pure-g">
<div class="pure-u-1-2">MQTT Topic</div>
<div class="pure-u-1-3">Variable Name</div>
</div>
<div id="rpnTopics"></div>
<button type="button" class="pure-button button-add-rpntopic">Add</button>
</fieldset>
</div>
</div>
</form>
<form id="form-dbg" class="pure-form">
<div class="panel" id="panel-dbg">
@ -1890,8 +1948,8 @@
<div class="pure-u-1 pure-u-lg-1-4"><label>Pulse time (s)</label></div>
<div class="pure-u-1 pure-u-lg-1-4"><input name="relayTime" class="pure-u-1" type="number" min="0" step="0.1" max="3600" /></div>
</div>
<div class="p-g module module-sch">
<div class="p-u-1 p-u-lg-1-4"><label>Restore last schedule</label></div>
<div class="pure-g module module-sch">
<div class="pure-u-1 pure-u-lg-1-4"><label>Restore last schedule</label></div>
<div><input name="relayLastSch" type="checkbox" /></div>
</div>
<div class="pure-g module module-mqtt">
@ -2002,11 +2060,26 @@
<div class="pure-u-md-1-6 pure-u-1-2"><input name="node" type="text" class="pure-u-11-12" value="" size="8" tabindex="0" placeholder="Node ID" autocomplete="false"></div>
<div class="pure-u-md-1-6 pure-u-1-2"><input name="key" type="text" class="pure-u-11-12" value="" size="8" tabindex="0" placeholder="Key"></div>
<div class="pure-u-md-1-2 pure-u-3-4"><input name="topic" type="text" class="pure-md-11-12 pure-u-23-24" value="" size="8" tabindex="0" placeholder="MQTT Topic"></div>
<div class="pure-u-md-1-6 pure-u-1-4"><button type="button" class="pure-button button-del-mapping pure-u-5-6 pure-u-md-5-6">Del</button></div>
<div class="pure-u-md-1-6 pure-u-1-4"><button type="button" class="pure-button button-del-parent pure-u-5-6 pure-u-md-5-6">Del</button></div>
</div>
</div>
<!-- endRemoveIf(!rfm69) -->
<div id="rpnRuleTemplate" class="template">
<div class="pure-g">
<div class="pure-u-5-6"><input name="rpnRule" type="text" class="pure-u-23-24" value="" tabindex="0" autocomplete="false" /></div>
<div class="pure-u-1-6"><button type="button" class="pure-button button-del-parent pure-u-1">Del</button></div>
</div>
</div>
<div id="rpnTopicTemplate" class="template">
<div class="pure-g">
<div class="pure-u-1-2"><input name="rpnTopic" type="text" class="pure-u-23-24" value="" tabindex="0" autocomplete="false" /></div>
<div class="pure-u-1-3"><input name="rpnName" type="text" class="pure-u-23-24" value="" tabindex="0" autocomplete="false" /></div>
<div class="pure-u-1-6"><button type="button" class="pure-button button-del-parent pure-u-1">Del</button></div>
</div>
</div>
<iframe id="downloader"></iframe>
<input id="uploader" type="file" />


+ 1
- 0
code/platformio.ini View File

@ -122,6 +122,7 @@ lib_deps =
PubSubClient
rc-switch
https://github.com/LowPowerLab/RFM69#1.1.3
https://github.com/xoseperez/rpnlib.git#0.3.0
https://github.com/xoseperez/Time
NewPing
https://github.com/sparkfun/SparkFun_VEML6075_Arduino_Library#V_1.0.3


+ 1
- 0
code/test/build/nondefault.h View File

@ -8,3 +8,4 @@
#define UART_MQTT_SUPPORT 1
#define INFLUXDB_SUPPORT 1
#define OTA_MQTT_SUPPORT 1
#define RPN_RULES_SUPPORT 1

Loading…
Cancel
Save