|
|
@ -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
|