diff --git a/README.md b/README.md
index 0ed0559a..16be500e 100644
--- a/README.md
+++ b/README.md
@@ -4,9 +4,9 @@ ESPurna ("spark" in Catalan) is a custom firmware for ESP8285/ESP8266 based smar
It uses the Arduino Core for ESP8266 framework and a number of 3rd party libraries.
[![version](https://img.shields.io/badge/version-1.13.6--dev-brightgreen.svg)](CHANGELOG.md)
-[![branch](https://img.shields.io/badge/branch-dev-orange.svg)](https://github.com/xoseperez/espurna/tree/dev/)
+[![branch](https://img.shields.io/badge/branch-rules--rpn-orange.svg)](https://github.com/xoseperez/espurna/tree/rules-rpn/)
[![license](https://img.shields.io/github/license/xoseperez/espurna.svg)](LICENSE)
-[![travis](https://travis-ci.org/xoseperez/espurna.svg?branch=dev)](https://travis-ci.org/xoseperez/espurna)
+[![travis](https://travis-ci.org/xoseperez/espurna.svg?branch=rules-rpn)](https://travis-ci.org/xoseperez/espurna)
[![codacy](https://api.codacy.com/project/badge/Grade/c9496e25cf07434cba786b462cb15f49)](https://www.codacy.com/app/xoseperez/espurna/dashboard)
[![downloads](https://img.shields.io/github/downloads/xoseperez/espurna/total.svg)](https://github.com/xoseperez/espurna/releases)
diff --git a/code/espurna/config/dependencies.h b/code/espurna/config/dependencies.h
index 281a2437..a371ae75 100644
--- a/code/espurna/config/dependencies.h
+++ b/code/espurna/config/dependencies.h
@@ -38,6 +38,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
diff --git a/code/espurna/config/general.h b/code/espurna/config/general.h
index 48139989..4ef67dd5 100644
--- a/code/espurna/config/general.h
+++ b/code/espurna/config/general.h
@@ -843,6 +843,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"
#define MQTT_TOPIC_STATUS "status"
@@ -1167,6 +1168,18 @@
#define SCHEDULER_MAX_SCHEDULES 10 // Max schedules alowed
#endif
+// -----------------------------------------------------------------------------
+// RPN RULES
+// -----------------------------------------------------------------------------
+
+#ifndef RPN_RULES_SUPPORT
+#define RPN_RULES_SUPPORT 1 // Enable RPN Rules (?Kb)
+#endif
+
+#ifndef RPN_BUFFER_DELAY
+#define RPN_BUFFER_DELAY 100 // Execute rules after 100ms without messages
+#endif
+
// -----------------------------------------------------------------------------
// NTP
// -----------------------------------------------------------------------------
diff --git a/code/espurna/ntp.ino b/code/espurna/ntp.ino
index de9353e0..103ec6b7 100644
--- a/code/espurna/ntp.ino
+++ b/code/espurna/ntp.ino
@@ -135,6 +135,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());
}
}
diff --git a/code/espurna/rpnrules.ino b/code/espurna/rpnrules.ino
new file mode 100644
index 00000000..ae2c2b0a
--- /dev/null
+++ b/code/espurna/rpnrules.ino
@@ -0,0 +1,117 @@
+/*
+
+NTP MODULE
+
+Copyright (C) 2016-2019 by Xose PĂ©rez
+
+*/
+
+#if RPN_RULES_SUPPORT
+
+#include "rpnlib.h"
+#include
+
+// -----------------------------------------------------------------------------
+// Custom commands
+// -----------------------------------------------------------------------------
+
+rpn_context _rpn_ctxt;
+bool _rpn_run = false;
+unsigned long _rpn_last = 0;
+
+// -----------------------------------------------------------------------------
+
+bool _rpnWebSocketOnReceive(const char * key, JsonVariant& value) {
+ return (strncmp(key, "rpn", 3) == 0);
+}
+
+void _rpnWebSocketOnSend(JsonObject& root) {
+
+ root["rpnVisible"] = 1;
+ JsonArray& rules = root.createNestedArray("rpnRules");
+
+ unsigned char i = 0;
+ while (String rule = getSetting("rule", i, NULL)) {
+ rules.add(rule);
+ }
+
+}
+
+void _rpnConfigure() {
+
+}
+
+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);
+ } else if (BROKER_MSG_TYPE_DATETIME == type) {
+ strncpy(name, topic, sizeof(name));
+ } else {
+ return;
+ }
+
+ rpn_variable_set(_rpn_ctxt, name, atof(payload));
+ _rpn_last = millis();
+ _rpn_run = true;
+
+}
+
+void _rpnInit() {
+
+ // Init context
+ rpn_init(_rpn_ctxt);
+
+ // Add relay operator
+ rpn_operator_set(_rpn_ctxt, "relay", 2, [](rpn_context & ctxt) {
+ float a, b;
+ rpn_stack_pop(ctxt, b); // new status
+ rpn_stack_pop(ctxt, a); // relay number
+ relayStatus(int(a), int(b));
+ return true;
+ });
+
+}
+
+void _rpnRun() {
+
+ unsigned char i = 0;
+ while (String rule = getSetting("rule", i, NULL)) {
+ rpn_stack_clear(_rpn_ctxt);
+ rpn_process(_rpn_ctxt, rule.c_str());
+ }
+
+}
+
+void _rpnLoop() {
+
+ if (_rpn_run && (millis() - _rpn_last > RPN_BUFFER_DELAY)) {
+ _rpnRun();
+ _rpn_run = false;
+ }
+
+}
+
+void rpnSetup() {
+
+ // Init context
+ _rpnInit();
+
+ // Load & cache settings
+ _rpnConfigure();
+
+ // Websockets
+ #if WEB_SUPPORT
+ wsOnSendRegister(_rpnWebSocketOnSend);
+ wsOnReceiveRegister(_rpnWebSocketOnReceive);
+ #endif
+
+ brokerRegister(_rpnBrokerCallback);
+ espurnaRegisterReload(_rpnConfigure);
+ espurnaRegisterLoop(_rpnLoop);
+
+}
+
+#endif
diff --git a/code/platformio.ini b/code/platformio.ini
index 983261f5..602daf89 100644
--- a/code/platformio.ini
+++ b/code/platformio.ini
@@ -99,6 +99,7 @@ lib_deps =
PubSubClient
rc-switch
https://github.com/LowPowerLab/RFM69#1.1.3
+ https://github.com/xoseperez/rpnlib.git#0.0.2
https://github.com/xoseperez/Time
NewPing
https://github.com/sparkfun/SparkFun_VEML6075_Arduino_Library#V_1.0.3