diff --git a/CMakeLists.txt b/CMakeLists.txt index 7b7c74b3..078a818b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,10 @@ set(SOURCE_FILES basic-logic/and/Factory.h basic-logic/and/MyNode.cpp basic-logic/and/MyNode.h + basic-logic/change/Factory.cpp + basic-logic/change/Factory.h + basic-logic/change/MyNode.cpp + basic-logic/change/MyNode.h basic-logic/fallingedge/Factory.cpp basic-logic/fallingedge/Factory.h basic-logic/fallingedge/MyNode.cpp @@ -36,10 +40,18 @@ set(SOURCE_FILES basic-logic/switch/Factory.h basic-logic/switch/MyNode.cpp basic-logic/switch/MyNode.h + basic-logic/variable-switch/Factory.cpp + basic-logic/variable-switch/Factory.h + basic-logic/variable-switch/MyNode.cpp + basic-logic/variable-switch/MyNode.h debug/Factory.cpp debug/Factory.h debug/MyNode.cpp debug/MyNode.h + exec/Factory.cpp + exec/Factory.h + exec/Exec.cpp + exec/Exec.h http/http-in/Factory.cpp http/http-in/Factory.h http/http-in/MyNode.cpp @@ -128,10 +140,10 @@ set(SOURCE_FILES pulsecounter/Factory.h pulsecounter/MyNode.cpp pulsecounter/MyNode.h - runscript/Factory.cpp - runscript/Factory.h - runscript/RunScript.cpp - runscript/RunScript.h + python/Factory.cpp + python/Factory.h + python/Python.cpp + python/Python.h serial/serial-port/Factory.cpp serial/serial-port/Factory.h serial/serial-port/MyNode.cpp @@ -177,6 +189,10 @@ set(SOURCE_FILES timers/on-delay/Factory.h timers/on-delay/MyNode.cpp timers/on-delay/MyNode.h + timers/rate-limiter/Factory.cpp + timers/rate-limiter/Factory.h + timers/rate-limiter/MyNode.cpp + timers/rate-limiter/MyNode.h timers/slow-pwm/Factory.cpp timers/slow-pwm/Factory.h timers/slow-pwm/MyNode.cpp diff --git a/Makefile.am b/Makefile.am index d103778f..04be74e1 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,3 +1,3 @@ AUTOMAKE_OPTIONS = foreign ACLOCAL_AMFLAGS = -I m4 -I cfg -SUBDIRS = average basic-logic comment debug function gpio http influxdb light link modbus mqtt notification parsers passthrough ping pulsecounter presence-light press-pattern runscript serial synchronous storage template timers tls-config tls-server-config variable +SUBDIRS = average basic-logic comment debug exec function gpio http influxdb light link modbus mqtt notification parsers passthrough ping pulsecounter presence-light press-pattern python serial synchronous storage template timers tls-config tls-server-config variable diff --git a/average/MyNode.cpp b/average/MyNode.cpp index 52f7fb37..cba5a60b 100644 --- a/average/MyNode.cpp +++ b/average/MyNode.cpp @@ -155,21 +155,25 @@ void MyNode::worker() } if(_stopThread) break; - double average = 0.0; + if(!_values.empty()) + { + double average = 0.0; + + { + std::lock_guard valuesGuard(_valuesMutex); + for(auto value : _values) + { + average += value; + } + if(!_values.empty()) average /= _values.size(); + _values.clear(); + } + + Flows::PVariable message = std::make_shared(Flows::VariableType::tStruct); + message->structValue->emplace("payload", std::make_shared(_inputIsDouble ? average : std::lround(average))); + output(0, message); + } - { - std::lock_guard valuesGuard(_valuesMutex); - for(auto value : _values) - { - average += value; - } - if(!_values.empty()) average /= _values.size(); - _values.clear(); - } - - Flows::PVariable message = std::make_shared(Flows::VariableType::tStruct); - message->structValue->emplace("payload", std::make_shared(_inputIsDouble ? average : std::lround(average))); - output(0, message); int64_t diff = Flows::HelperFunctions::getTime() - startTime; if(diff <= _interval) sleepingTime = _interval; else sleepingTime = _interval - (diff - _interval); diff --git a/average/average.hni b/average/average.hni index 65638519..a7ae1275 100644 --- a/average/average.hni +++ b/average/average.hni @@ -2,7 +2,8 @@ { "name": "average", "readableName": "average", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 1 } diff --git a/basic-logic/Makefile.am b/basic-logic/Makefile.am index ea26832e..f32862ca 100644 --- a/basic-logic/Makefile.am +++ b/basic-logic/Makefile.am @@ -6,11 +6,14 @@ LIBS += -Wl,-Bdynamic -lhomegear-node libdir = $(localstatedir)/lib/homegear/node-blue/nodes/basic-logic -lib_LTLIBRARIES = and.la fallingedge.la not.la or.la risingedge.la srflipflop.la switch.la +lib_LTLIBRARIES = and.la change.la fallingedge.la not.la or.la risingedge.la srflipflop.la switch.la variable-switch.la and_la_SOURCES = and/Factory.cpp and/MyNode.cpp and_la_LDFLAGS =-module -avoid-version -shared +change_la_SOURCES = change/Factory.cpp change/MyNode.cpp +change_la_LDFLAGS =-module -avoid-version -shared + fallingedge_la_SOURCES = fallingedge/Factory.cpp fallingedge/MyNode.cpp fallingedge_la_LDFLAGS =-module -avoid-version -shared @@ -29,16 +32,21 @@ srflipflop_la_LDFLAGS =-module -avoid-version -shared switch_la_SOURCES = switch/Factory.cpp switch/MyNode.cpp switch_la_LDFLAGS =-module -avoid-version -shared +variable_switch_la_SOURCES = variable-switch/Factory.cpp variable-switch/MyNode.cpp +variable_switch_la_LDFLAGS =-module -avoid-version -shared + logic_ladir = $(libdir) -logic_la_DATA = and/and.hni fallingedge/fallingedge.hni not/not.hni or/or.hni risingedge/risingedge.hni srflipflop/srflipflop.hni switch/switch.hni +logic_la_DATA = and/and.hni change/change.hni fallingedge/fallingedge.hni not/not.hni or/or.hni risingedge/risingedge.hni srflipflop/srflipflop.hni switch/switch.hni variable-switch/variable-switch.hni locale_en_usdir = $(libdir)/locales/en-US -locale_en_us_DATA = and/locales/en-US/and fallingedge/locales/en-US/fallingedge not/locales/en-US/not or/locales/en-US/or risingedge/locales/en-US/risingedge srflipflop/locales/en-US/srflipflop switch/locales/en-US/switch +locale_en_us_DATA = and/locales/en-US/and change/locales/en-US/change fallingedge/locales/en-US/fallingedge not/locales/en-US/not or/locales/en-US/or risingedge/locales/en-US/risingedge srflipflop/locales/en-US/srflipflop switch/locales/en-US/switch variable-switch/locales/en-US/variable-switch install-exec-hook: rm -f $(DESTDIR)$(libdir)/and.la + rm -f $(DESTDIR)$(libdir)/change.la rm -f $(DESTDIR)$(libdir)/fallingedge.la rm -f $(DESTDIR)$(libdir)/not.la rm -f $(DESTDIR)$(libdir)/or.la rm -f $(DESTDIR)$(libdir)/risingedge.la rm -f $(DESTDIR)$(libdir)/srflipflop.la rm -f $(DESTDIR)$(libdir)/switch.la + rm -f $(DESTDIR)$(libdir)/variable-switch.la diff --git a/basic-logic/and/and.hni b/basic-logic/and/and.hni index 3006759c..c7edd8c6 100644 --- a/basic-logic/and/and.hni +++ b/basic-logic/and/and.hni @@ -2,7 +2,8 @@ { "name": "and", "readableName": "And", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 0 } diff --git a/basic-logic/change/Factory.cpp b/basic-logic/change/Factory.cpp new file mode 100644 index 00000000..47cc94ec --- /dev/null +++ b/basic-logic/change/Factory.cpp @@ -0,0 +1,41 @@ +/* Copyright 2013-2019 Homegear GmbH + * + * Homegear is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Homegear is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Homegear. If not, see . + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you + * do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source + * files in the program, then also delete it here. + */ + +#include "Factory.h" +#include "MyNode.h" + +Flows::INode* MyFactory::createNode(std::string path, std::string nodeNamespace, std::string type, const std::atomic_bool* frontendConnected) +{ + return new MyNode::MyNode(path, nodeNamespace, type, frontendConnected); +} + +Flows::NodeFactory* getFactory() +{ + return (Flows::NodeFactory*) (new MyFactory); +} diff --git a/runscript/RunScript.h b/basic-logic/change/Factory.h similarity index 72% rename from runscript/RunScript.h rename to basic-logic/change/Factory.h index ed39964f..6fa76bfb 100644 --- a/runscript/RunScript.h +++ b/basic-logic/change/Factory.h @@ -27,31 +27,18 @@ * files in the program, then also delete it here. */ -#ifndef RUNSCRIPT_H_ -#define RUNSCRIPT_H_ +#ifndef FACTORY_H +#define FACTORY_H -#include -#include -#include +#include +#include "MyNode.h" -namespace RunScript -{ - -class RunScript: public Flows::INode +class MyFactory : Flows::NodeFactory { public: - RunScript(std::string path, std::string nodeNamespace, std::string type, const std::atomic_bool* frontendConnected); - virtual ~RunScript(); - - virtual bool init(Flows::PNodeInfo info); -private: - bool _onBoolean = false; - Flows::PVariable _input1; - bool _input2 = false; - - virtual void input(const Flows::PNodeInfo info, uint32_t index, const Flows::PVariable message); + virtual Flows::INode* createNode(std::string path, std::string nodeNamespace, std::string type, const std::atomic_bool* frontendConnected); }; -} +extern "C" Flows::NodeFactory* getFactory(); #endif diff --git a/basic-logic/change/MyNode.cpp b/basic-logic/change/MyNode.cpp new file mode 100644 index 00000000..8bf57fb6 --- /dev/null +++ b/basic-logic/change/MyNode.cpp @@ -0,0 +1,332 @@ +/* Copyright 2013-2019 Homegear GmbH + * + * Homegear is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Homegear is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Homegear. If not, see . + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you + * do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source + * files in the program, then also delete it here. + */ + +#include +#include "MyNode.h" +#include "../switch/MyNode.h" + +namespace MyNode +{ + +MyNode::MyNode(std::string path, std::string nodeNamespace, std::string type, const std::atomic_bool* frontendConnected) : Flows::INode(path, nodeNamespace, type, frontendConnected) +{ +} + +MyNode::~MyNode() +{ +} + +std::string& MyNode::stringReplace(std::string& haystack, const std::string& search, const std::string& replace) +{ + if(search.empty()) return haystack; + int32_t pos = 0; + while(true) + { + pos = haystack.find(search, pos); + if (pos == (signed)std::string::npos) break; + haystack.replace(pos, search.size(), replace); + pos += replace.size(); + } + return haystack; +} + +MyNode::RuleType MyNode::getRuleTypeFromString(std::string& t) +{ + MyNode::RuleType ruleType = MyNode::RuleType::tSet; + if(t == "set") ruleType = MyNode::RuleType::tSet; + else if(t == "change") ruleType = MyNode::RuleType::tChange; + else if(t == "move") ruleType = MyNode::RuleType::tMove; + else if(t == "delete") ruleType = MyNode::RuleType::tDelete; + return ruleType; +} + +Flows::VariableType MyNode::getValueTypeFromString(std::string& vt) +{ + Flows::VariableType variableType = Flows::VariableType::tVoid; + if(vt == "bool") variableType = Flows::VariableType::tBoolean; + else if(vt == "int") variableType = Flows::VariableType::tInteger64; + else if(vt == "float") variableType = Flows::VariableType::tFloat; + else if(vt == "string") variableType = Flows::VariableType::tString; + else if(vt == "array") variableType = Flows::VariableType::tArray; + else if(vt == "struct") variableType = Flows::VariableType::tStruct; + return variableType; +} + +void MyNode::convertType(Flows::PVariable& value, Flows::VariableType vt) +{ + if(vt == Flows::VariableType::tBoolean) + { + if(value->type == Flows::VariableType::tString) + { + value->setType(Flows::VariableType::tBoolean); + value->booleanValue = (value->stringValue == "true"); + } + } + else if(vt == Flows::VariableType::tInteger) + { + value->setType(Flows::VariableType::tInteger64); + value->integerValue = Flows::Math::getNumber(value->stringValue); + value->integerValue64 = value->integerValue; + } + else if(vt == Flows::VariableType::tInteger64) + { + value->setType(vt); + value->integerValue64 = Flows::Math::getNumber64(value->stringValue); + } + else if(vt == Flows::VariableType::tFloat) + { + value->setType(vt); + value->floatValue = Flows::Math::getDouble(value->stringValue); + } + else if(vt == Flows::VariableType::tArray || vt == Flows::VariableType::tStruct) + { + Flows::JsonDecoder jsonDecoder; + value = jsonDecoder.decode(value->stringValue); + } +} + +bool MyNode::init(Flows::PNodeInfo info) +{ + try + { + auto flowIdIterator = info->info->structValue->find("z"); + if(flowIdIterator != info->info->structValue->end()) _flowId = flowIdIterator->second->stringValue; + else _flowId = "g"; + + Flows::PArray rules; + auto settingsIterator = info->info->structValue->find("rules"); + if(settingsIterator != info->info->structValue->end()) rules = settingsIterator->second->arrayValue; + + _rules.clear(); + if(rules) + { + _rules.reserve(rules->size()); + uint32_t index = 0; + for(auto& ruleStruct : *rules) + { + Rule rule; + + auto typeIterator = ruleStruct->structValue->find("t"); + auto valueIterator = ruleStruct->structValue->find("p"); + auto valueTypeIterator = ruleStruct->structValue->find("pt"); + if(typeIterator == ruleStruct->structValue->end()) continue; + rule.t = getRuleTypeFromString(typeIterator->second->stringValue); + if(valueIterator != ruleStruct->structValue->end() && valueTypeIterator != ruleStruct->structValue->end()) + { + if(valueTypeIterator->second->stringValue == "message") rule.messageProperty = valueIterator->second->stringValue; + else if(valueTypeIterator->second->stringValue == "flow") rule.flowVariable = valueIterator->second->stringValue; + else if(valueTypeIterator->second->stringValue == "global") rule.globalVariable = valueIterator->second->stringValue; + } + + + valueIterator = ruleStruct->structValue->find("from"); + valueTypeIterator = ruleStruct->structValue->find("fromt"); + if(valueIterator != ruleStruct->structValue->end() && valueTypeIterator != ruleStruct->structValue->end()) + { + rule.from = valueIterator->second; + rule.fromt = getValueTypeFromString(valueTypeIterator->second->stringValue); + if(valueTypeIterator->second->stringValue == "message") rule.messagePropertyFrom = rule.from->stringValue; + else if(valueTypeIterator->second->stringValue == "flow") rule.flowVariableFrom = rule.from->stringValue; + else if(valueTypeIterator->second->stringValue == "global") rule.globalVariableFrom = rule.from->stringValue; + convertType(rule.from, rule.fromt); + } + else rule.from = std::make_shared(); + if(valueTypeIterator != ruleStruct->structValue->end() && valueTypeIterator->second->stringValue == "regex") + { + rule.fromRegexSet = true; + rule.fromRegex = std::regex(rule.from->stringValue, std::regex::ECMAScript); + } + + + valueIterator = ruleStruct->structValue->find("to"); + valueTypeIterator = ruleStruct->structValue->find("tot"); + if(valueIterator != ruleStruct->structValue->end() && valueTypeIterator != ruleStruct->structValue->end()) + { + rule.to = valueIterator->second; + rule.tot = getValueTypeFromString(valueTypeIterator->second->stringValue); + if(valueTypeIterator->second->stringValue == "message") rule.messagePropertyTo = rule.to->stringValue; + else if(valueTypeIterator->second->stringValue == "flow") rule.flowVariableTo = rule.to->stringValue; + else if(valueTypeIterator->second->stringValue == "global") rule.globalVariableTo = rule.to->stringValue; + convertType(rule.to, rule.tot); + } + else rule.to = std::make_shared(); + + _rules.push_back(rule); + index++; + } + } + + return true; + } + catch(const std::exception& ex) + { + _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); + } + return false; +} + +void MyNode::applyRule(Rule& rule, Flows::PVariable& value) +{ + try + { + if(!rule.messageProperty.empty()) + { + if(rule.t == RuleType::tDelete) + { + if(!rule.flowVariable.empty()) setFlowData(rule.flowVariable, std::make_shared()); + else if(!rule.globalVariable.empty()) setFlowData(rule.globalVariable, std::make_shared()); + else if(!rule.messageProperty.empty()) value->structValue->erase(rule.messageProperty); + } + else if(rule.t == RuleType::tSet) + { + if(!rule.flowVariableTo.empty()) rule.to = getFlowData(rule.flowVariableTo); + else if(!rule.globalVariableTo.empty()) rule.to = getGlobalData(rule.globalVariableTo); + else if(!rule.messagePropertyTo.empty()) + { + auto propertyIterator = value->structValue->find(rule.messagePropertyTo); + if(propertyIterator != value->structValue->end()) rule.to = propertyIterator->second; + else rule.to = std::make_shared(); + } + + if(!rule.flowVariable.empty()) setFlowData(rule.flowVariable, rule.to); + else if(!rule.globalVariable.empty()) setFlowData(rule.globalVariable, rule.to); + else if(!rule.messageProperty.empty()) (*value->structValue)[rule.messageProperty] = rule.to; + } + else if(rule.t == RuleType::tMove) + { + Flows::PVariable currentValue; + + //{{{ Set + if(!rule.flowVariable.empty()) currentValue = getFlowData(rule.flowVariable); + else if(!rule.globalVariable.empty()) currentValue = getGlobalData(rule.globalVariable); + else if(!rule.messageProperty.empty()) + { + auto propertyIterator = value->structValue->find(rule.messageProperty); + if(propertyIterator != value->structValue->end()) currentValue = propertyIterator->second; + else currentValue = std::make_shared(); + } + else currentValue = std::make_shared(); + + if(!rule.flowVariableTo.empty()) setFlowData(rule.flowVariableTo, currentValue); + else if(!rule.globalVariableTo.empty()) setFlowData(rule.globalVariableTo, currentValue); + else if(!rule.messagePropertyTo.empty()) (*value->structValue)[rule.messagePropertyTo] = currentValue; + //}}} + + //{{{ Delete + if(!rule.flowVariable.empty()) setFlowData(rule.flowVariable, std::make_shared()); + else if(!rule.globalVariable.empty()) setFlowData(rule.globalVariable, std::make_shared()); + else if(!rule.messageProperty.empty()) value->structValue->erase(rule.messageProperty); + //}}} + } + else if(rule.t == RuleType::tChange) + { + Flows::PVariable currentValue; + + if(!rule.flowVariable.empty()) currentValue = getFlowData(rule.flowVariable); + else if(!rule.globalVariable.empty()) currentValue = getGlobalData(rule.globalVariable); + else if(!rule.messageProperty.empty()) + { + auto propertyIterator = value->structValue->find(rule.messageProperty); + if(propertyIterator != value->structValue->end()) currentValue = propertyIterator->second; + else currentValue = std::make_shared(); + } + else currentValue = std::make_shared(); + + if(rule.fromRegexSet) + { + if(currentValue->type != Flows::VariableType::tString) + { + currentValue->stringValue = currentValue->toString(); + currentValue->type = Flows::VariableType::tString; + } + + if(rule.tot != Flows::VariableType::tString) + { + if(!std::regex_match(currentValue->stringValue, rule.fromRegex)) return; + currentValue = rule.to; + } + else currentValue->stringValue = std::regex_replace(currentValue->stringValue, rule.fromRegex, rule.to->toString()); + } + else + { + if(rule.fromt == Flows::VariableType::tString) + { + if(currentValue->type != Flows::VariableType::tString) + { + currentValue->stringValue = currentValue->toString(); + currentValue->type = Flows::VariableType::tString; + } + + if(currentValue->stringValue.find(rule.from->stringValue) == std::string::npos) return; + + if(rule.tot != Flows::VariableType::tString) currentValue = rule.to; + else stringReplace(currentValue->stringValue, rule.from->stringValue, rule.to->toString()); + } + else + { + if(currentValue->type != rule.from->type) return; + + if(currentValue->type == Flows::VariableType::tBoolean && currentValue->booleanValue == rule.from->booleanValue) currentValue = rule.to; + else if(currentValue->type == Flows::VariableType::tInteger64 && currentValue->integerValue64 == rule.from->integerValue64) currentValue = rule.to; + else if(currentValue->type == Flows::VariableType::tFloat && currentValue->floatValue == rule.from->floatValue) currentValue = rule.to; + } + } + + if(!rule.flowVariable.empty()) setFlowData(rule.flowVariable, currentValue); + else if(!rule.globalVariable.empty()) setFlowData(rule.globalVariable, currentValue); + else if(!rule.messageProperty.empty()) (*value->structValue)[rule.messageProperty] = currentValue; + } + } + } + catch(const std::exception& ex) + { + _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); + } +} + +void MyNode::input(const Flows::PNodeInfo info, uint32_t index, const Flows::PVariable message) +{ + try + { + Flows::PVariable myMessage = std::make_shared(); + *myMessage = *message; + + for(auto& rule : _rules) + { + applyRule(rule, myMessage); + output(0, myMessage); + } + } + catch(const std::exception& ex) + { + _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); + } +} + +} diff --git a/basic-logic/change/MyNode.h b/basic-logic/change/MyNode.h new file mode 100644 index 00000000..83c0f6d2 --- /dev/null +++ b/basic-logic/change/MyNode.h @@ -0,0 +1,91 @@ +/* Copyright 2013-2019 Homegear GmbH + * + * Homegear is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Homegear is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Homegear. If not, see . + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you + * do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source + * files in the program, then also delete it here. + */ + +#ifndef MYNODE_H_ +#define MYNODE_H_ + +#include +#include +#include +#include + +namespace MyNode +{ + +class MyNode: public Flows::INode +{ +public: + enum class RuleType + { + tSet, + tChange, + tMove, + tDelete + }; + + MyNode(std::string path, std::string nodeNamespace, std::string type, const std::atomic_bool* frontendConnected); + virtual ~MyNode(); + + virtual bool init(Flows::PNodeInfo info); +private: + struct Rule + { + RuleType t; + std::string messageProperty; + std::string flowVariable; + std::string globalVariable; + Flows::PVariable from; + Flows::VariableType fromt; + std::string messagePropertyFrom; + std::string flowVariableFrom; + std::string globalVariableFrom; + bool fromRegexSet = false; + std::regex fromRegex; + Flows::PVariable to; + Flows::VariableType tot; + std::string messagePropertyTo; + std::string flowVariableTo; + std::string globalVariableTo; + }; + + typedef std::string Operator; + + std::vector _rules; + + std::string& stringReplace(std::string& haystack, const std::string& search, const std::string& replace); + RuleType getRuleTypeFromString(std::string& t); + Flows::VariableType getValueTypeFromString(std::string& vt); + void convertType(Flows::PVariable& value, Flows::VariableType vt); + void applyRule(Rule& rule, Flows::PVariable& value); + virtual void input(const Flows::PNodeInfo info, uint32_t index, const Flows::PVariable message); +}; + +} + +#endif diff --git a/basic-logic/change/change.hni b/basic-logic/change/change.hni new file mode 100644 index 00000000..bd9439f6 --- /dev/null +++ b/basic-logic/change/change.hni @@ -0,0 +1,235 @@ + + + + + diff --git a/basic-logic/change/locales/en-US/change b/basic-logic/change/locales/en-US/change new file mode 100644 index 00000000..0267e5fc --- /dev/null +++ b/basic-logic/change/locales/en-US/change @@ -0,0 +1,35 @@ +{ + "basic-logic/change.hni": { + "change": { + "label": { + "rules": "Rules", + "rule": "rule", + "set": "set __property__", + "change": "change __property__", + "delete": "delete __property__", + "move": "move __property__", + "changeCount": "change: __count__ rules", + "regex": "Use regular expressions" + }, + "action": { + "set": "Set", + "change": "Change", + "delete": "Delete", + "move": "Move", + "to": "to", + "search": "Search for", + "replace": "Replace with" + }, + "errors": { + "invalid-from": "Invalid 'from' property: __error__", + "invalid-json": "Invalid 'to' JSON property", + "invalid-expr": "Invalid JSONata expression: __error__" + }, + "message": "message.", + "flow": "flow.", + "global": "global.", + "paletteHelp": "

Set, change, delete or move properties of a message, flow context or global context.

", + "help": "

Set, change, delete or move properties of a message, flow context or global context.

The node can specify multiple rules that will be applied in the order they are defined.

Details

The available operations are:

Set
set a property. The value can be a variety of different types, or can be taken from an existing message or context property.
Change
search & replace parts of the property. If regular expressions are enabled, the \"replace with\" property can include capture groups, for example $1. Replace will only change the type if there is a complete match.
Delete
delete a property.
Move
move or rename a property.
" + } + } +} \ No newline at end of file diff --git a/basic-logic/fallingedge/MyNode.cpp b/basic-logic/fallingedge/MyNode.cpp index a6c81920..20cabdc2 100644 --- a/basic-logic/fallingedge/MyNode.cpp +++ b/basic-logic/fallingedge/MyNode.cpp @@ -69,7 +69,9 @@ void MyNode::input(Flows::PNodeInfo info, uint32_t index, Flows::PVariable messa bool value = *input; if(!value && _lastInput) { - output(0, message); + Flows::PVariable outputMessage = std::make_shared(Flows::VariableType::tStruct); + outputMessage->structValue->emplace("payload", std::make_shared(true)); + output(0, outputMessage); } _lastInput = value; diff --git a/basic-logic/fallingedge/fallingedge.hni b/basic-logic/fallingedge/fallingedge.hni index 7a3d25cd..1830e5bb 100644 --- a/basic-logic/fallingedge/fallingedge.hni +++ b/basic-logic/fallingedge/fallingedge.hni @@ -2,7 +2,8 @@ { "name": "fallingedge", "readableName": "FallingEdge", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 0 } diff --git a/basic-logic/not/not.hni b/basic-logic/not/not.hni index c8239f2a..1a1b4ab7 100644 --- a/basic-logic/not/not.hni +++ b/basic-logic/not/not.hni @@ -2,7 +2,8 @@ { "name": "not", "readableName": "Not", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 0 } diff --git a/basic-logic/or/or.hni b/basic-logic/or/or.hni index 02cfc997..c5237869 100644 --- a/basic-logic/or/or.hni +++ b/basic-logic/or/or.hni @@ -2,7 +2,8 @@ { "name": "or", "readableName": "Or", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 0 } diff --git a/basic-logic/risingedge/risingedge.hni b/basic-logic/risingedge/risingedge.hni index dfaed1e9..e53c5d6d 100644 --- a/basic-logic/risingedge/risingedge.hni +++ b/basic-logic/risingedge/risingedge.hni @@ -2,7 +2,8 @@ { "name": "risingedge", "readableName": "RisingEdge", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 0 } diff --git a/basic-logic/srflipflop/srflipflop.hni b/basic-logic/srflipflop/srflipflop.hni index 39054776..cf944609 100644 --- a/basic-logic/srflipflop/srflipflop.hni +++ b/basic-logic/srflipflop/srflipflop.hni @@ -2,7 +2,8 @@ { "name": "srflipflop", "readableName": "Set-Reset-Flip-Flop", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 0 } diff --git a/basic-logic/switch/Factory.cpp b/basic-logic/switch/Factory.cpp index 842c28e3..47cc94ec 100644 --- a/basic-logic/switch/Factory.cpp +++ b/basic-logic/switch/Factory.cpp @@ -29,7 +29,6 @@ #include "Factory.h" #include "MyNode.h" -#include "../config.h" Flows::INode* MyFactory::createNode(std::string path, std::string nodeNamespace, std::string type, const std::atomic_bool* frontendConnected) { diff --git a/basic-logic/switch/MyNode.cpp b/basic-logic/switch/MyNode.cpp index c902101f..95710c0c 100644 --- a/basic-logic/switch/MyNode.cpp +++ b/basic-logic/switch/MyNode.cpp @@ -75,7 +75,15 @@ Flows::VariableType MyNode::getValueTypeFromString(std::string& vt) void MyNode::convertType(Flows::PVariable& value, Flows::VariableType vt) { - if(vt == Flows::VariableType::tInteger) + if(vt == Flows::VariableType::tBoolean) + { + if(value->type == Flows::VariableType::tString) + { + value->setType(Flows::VariableType::tBoolean); + value->booleanValue = (value->stringValue == "true"); + } + } + else if(vt == Flows::VariableType::tInteger) { value->setType(Flows::VariableType::tInteger64); value->integerValue = Flows::Math::getNumber(value->stringValue); @@ -291,10 +299,10 @@ bool MyNode::match(Rule& rule, Flows::PVariable& value) switch(rule.t) { case RuleType::tEq: - if(value->type == rule.vt && *value == *rule.v) return true; + if(*value == *rule.v) return true; return false; case RuleType::tNeq: - if(value->type == rule.vt && *value == *rule.v) return false; + if(*value == *rule.v) return false; return true; case RuleType::tLt: if(value->type == rule.vt && *value < *rule.v) return true; diff --git a/basic-logic/switch/switch.hni b/basic-logic/switch/switch.hni index cac32465..353a0d53 100644 --- a/basic-logic/switch/switch.hni +++ b/basic-logic/switch/switch.hni @@ -2,7 +2,8 @@ { "name": "switch", "readableName": "Switch", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 0 } diff --git a/basic-logic/variable-switch/Factory.cpp b/basic-logic/variable-switch/Factory.cpp new file mode 100644 index 00000000..47cc94ec --- /dev/null +++ b/basic-logic/variable-switch/Factory.cpp @@ -0,0 +1,41 @@ +/* Copyright 2013-2019 Homegear GmbH + * + * Homegear is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Homegear is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Homegear. If not, see . + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you + * do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source + * files in the program, then also delete it here. + */ + +#include "Factory.h" +#include "MyNode.h" + +Flows::INode* MyFactory::createNode(std::string path, std::string nodeNamespace, std::string type, const std::atomic_bool* frontendConnected) +{ + return new MyNode::MyNode(path, nodeNamespace, type, frontendConnected); +} + +Flows::NodeFactory* getFactory() +{ + return (Flows::NodeFactory*) (new MyFactory); +} diff --git a/basic-logic/variable-switch/Factory.h b/basic-logic/variable-switch/Factory.h new file mode 100644 index 00000000..6fa76bfb --- /dev/null +++ b/basic-logic/variable-switch/Factory.h @@ -0,0 +1,44 @@ +/* Copyright 2013-2019 Homegear GmbH + * + * Homegear is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Homegear is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Homegear. If not, see . + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you + * do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source + * files in the program, then also delete it here. + */ + +#ifndef FACTORY_H +#define FACTORY_H + +#include +#include "MyNode.h" + +class MyFactory : Flows::NodeFactory +{ +public: + virtual Flows::INode* createNode(std::string path, std::string nodeNamespace, std::string type, const std::atomic_bool* frontendConnected); +}; + +extern "C" Flows::NodeFactory* getFactory(); + +#endif diff --git a/basic-logic/variable-switch/MyNode.cpp b/basic-logic/variable-switch/MyNode.cpp new file mode 100644 index 00000000..d070a77a --- /dev/null +++ b/basic-logic/variable-switch/MyNode.cpp @@ -0,0 +1,501 @@ +/* Copyright 2013-2019 Homegear GmbH + * + * Homegear is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Homegear is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Homegear. If not, see . + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you + * do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source + * files in the program, then also delete it here. + */ + +#include +#include "MyNode.h" + +namespace MyNode +{ + +MyNode::MyNode(std::string path, std::string nodeNamespace, std::string type, const std::atomic_bool* frontendConnected) : Flows::INode(path, nodeNamespace, type, frontendConnected) +{ +} + +MyNode::~MyNode() +{ +} + +MyNode::RuleType MyNode::getRuleTypeFromString(std::string& t) +{ + MyNode::RuleType ruleType = MyNode::RuleType::tEq; + if(t == "eq") ruleType = MyNode::RuleType::tEq; + else if(t == "neq") ruleType = MyNode::RuleType::tNeq; + else if(t == "lt") ruleType = MyNode::RuleType::tLt; + else if(t == "lte") ruleType = MyNode::RuleType::tLte; + else if(t == "gt") ruleType = MyNode::RuleType::tGt; + else if(t == "gte") ruleType = MyNode::RuleType::tGte; + else if(t == "btwn") ruleType = MyNode::RuleType::tBtwn; + else if(t == "cont") ruleType = MyNode::RuleType::tCont; + else if(t == "regex") ruleType = MyNode::RuleType::tRegex; + else if(t == "true") ruleType = MyNode::RuleType::tTrue; + else if(t == "false") ruleType = MyNode::RuleType::tFalse; + else if(t == "null") ruleType = MyNode::RuleType::tNull; + else if(t == "nnull") ruleType = MyNode::RuleType::tNnull; + else if(t == "else") ruleType = MyNode::RuleType::tElse; + return ruleType; +} + +Flows::VariableType MyNode::getValueTypeFromString(std::string& vt) +{ + Flows::VariableType variableType = Flows::VariableType::tVoid; + if(vt == "bool") variableType = Flows::VariableType::tBoolean; + else if(vt == "int") variableType = Flows::VariableType::tInteger64; + else if(vt == "float") variableType = Flows::VariableType::tFloat; + else if(vt == "string") variableType = Flows::VariableType::tString; + else if(vt == "array") variableType = Flows::VariableType::tArray; + else if(vt == "struct") variableType = Flows::VariableType::tStruct; + return variableType; +} + +void MyNode::convertType(Flows::PVariable& value, Flows::VariableType vt) +{ + if(vt == Flows::VariableType::tInteger) + { + value->setType(Flows::VariableType::tInteger64); + value->integerValue = Flows::Math::getNumber(value->stringValue); + value->integerValue64 = value->integerValue; + } + else if(vt == Flows::VariableType::tInteger64) + { + value->setType(vt); + value->integerValue64 = Flows::Math::getNumber64(value->stringValue); + } + else if(vt == Flows::VariableType::tFloat) + { + value->setType(vt); + value->floatValue = Flows::Math::getDouble(value->stringValue); + } + else if(vt == Flows::VariableType::tArray || vt == Flows::VariableType::tStruct) + { + Flows::JsonDecoder jsonDecoder; + value = jsonDecoder.decode(value->stringValue); + } +} + +bool MyNode::init(Flows::PNodeInfo info) +{ + try + { + auto flowIdIterator = info->info->structValue->find("z"); + if(flowIdIterator != info->info->structValue->end()) _flowId = flowIdIterator->second->stringValue; + else _flowId = "g"; + + std::string variableType = "device"; + auto settingsIterator = info->info->structValue->find("variabletype"); + if(settingsIterator != info->info->structValue->end()) variableType = settingsIterator->second->stringValue; + + if(variableType == "device") _variableType = VariableType::device; + else if(variableType == "metadata") _variableType = VariableType::metadata; + else if(variableType == "system") _variableType = VariableType::system; + else if(variableType == "flow") _variableType = VariableType::flow; + else if(variableType == "global") _variableType = VariableType::global; + + if(_variableType == VariableType::device || _variableType == VariableType::metadata) + { + settingsIterator = info->info->structValue->find("peerid"); + if(settingsIterator != info->info->structValue->end()) _peerId = Flows::Math::getNumber64(settingsIterator->second->stringValue); + } + + if(_variableType == VariableType::device) + { + settingsIterator = info->info->structValue->find("channel"); + if(settingsIterator != info->info->structValue->end()) _channel = Flows::Math::getNumber(settingsIterator->second->stringValue); + } + + settingsIterator = info->info->structValue->find("variable"); + if(settingsIterator != info->info->structValue->end()) _variable = settingsIterator->second->stringValue; + + if(variableType == "device") _variableType = VariableType::device; + else if(variableType == "metadata") _variableType = VariableType::metadata; + else if(variableType == "system") _variableType = VariableType::system; + else if(variableType == "flow") _variableType = VariableType::flow; + else if(variableType == "global") _variableType = VariableType::global; + + settingsIterator = info->info->structValue->find("checkall"); + if(settingsIterator != info->info->structValue->end()) _checkAll = settingsIterator->second->stringValue == "true" || settingsIterator->second->booleanValue; + + settingsIterator = info->info->structValue->find("output-true"); + if(settingsIterator != info->info->structValue->end()) _outputTrue = settingsIterator->second->booleanValue; + + if(_outputTrue) + { + settingsIterator = info->info->structValue->find("output-false"); + if(settingsIterator != info->info->structValue->end()) _outputFalse = settingsIterator->second->booleanValue; + } + else _outputFalse = false; + + settingsIterator = info->info->structValue->find("changes-only"); + if(settingsIterator != info->info->structValue->end()) _changesOnly = settingsIterator->second->booleanValue; + + Flows::PArray rules; + settingsIterator = info->info->structValue->find("rules"); + if(settingsIterator != info->info->structValue->end()) rules = settingsIterator->second->arrayValue; + + _rules.clear(); + if(rules) + { + _rules.reserve(rules->size()); + uint32_t index = 0; + for(auto& ruleStruct : *rules) + { + auto typeIterator = ruleStruct->structValue->find("t"); + auto valueIterator = ruleStruct->structValue->find("v"); + auto valueTypeIterator = ruleStruct->structValue->find("vt"); + + if(typeIterator == ruleStruct->structValue->end()) continue; + + Rule rule; + rule.previousOutput = getNodeData("previousOutputValue" + std::to_string(index)); + rule.t = getRuleTypeFromString(typeIterator->second->stringValue); + + if(valueIterator != ruleStruct->structValue->end() && valueTypeIterator != ruleStruct->structValue->end()) + { + rule.v = valueIterator->second; + rule.vt = getValueTypeFromString(valueTypeIterator->second->stringValue); + if(valueTypeIterator->second->stringValue == "prev") rule.previousValue = true; + else if(valueTypeIterator->second->stringValue == "input") rule.input = true; + else if(valueTypeIterator->second->stringValue == "flow") rule.flowVariable = rule.v->stringValue; + else if(valueTypeIterator->second->stringValue == "global") rule.globalVariable = rule.v->stringValue; + convertType(rule.v, rule.vt); + } + else rule.v = std::make_shared(); + + valueIterator = ruleStruct->structValue->find("v2"); + valueTypeIterator = ruleStruct->structValue->find("v2t"); + + if(valueIterator != ruleStruct->structValue->end() && valueTypeIterator != ruleStruct->structValue->end()) + { + rule.v2 = valueIterator->second; + rule.v2t = getValueTypeFromString(valueTypeIterator->second->stringValue); + if(valueTypeIterator->second->stringValue == "prev") rule.previousValue2 = true; + else if(valueTypeIterator->second->stringValue == "input") rule.input2 = true; + else if(valueTypeIterator->second->stringValue == "flow") rule.flowVariable2 = rule.v2->stringValue; + else if(valueTypeIterator->second->stringValue == "global") rule.globalVariable2 = rule.v2->stringValue; + convertType(rule.v2, rule.v2t); + } + else rule.v2 = std::make_shared(); + + if(rule.t == RuleType::tRegex) + { + auto caseIterator = ruleStruct->structValue->find("case"); + if(caseIterator != ruleStruct->structValue->end()) rule.ignoreCase = caseIterator->second->booleanValue; + + rule.regex = std::regex(rule.v->stringValue, rule.ignoreCase ? std::regex::icase : std::regex::ECMAScript); + } + + _rules.emplace_back(std::move(rule)); + index++; + } + } + + _previousValue = getNodeData("previousValue"); + _previousInputValue = getNodeData("previousInputValue"); + + return true; + } + catch(const std::exception& ex) + { + _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); + } + catch(...) + { + _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); + } + return false; +} + +bool MyNode::isTrue(Flows::PVariable& value) +{ + if(value->type != Flows::VariableType::tBoolean) + { + switch(value->type) + { + case Flows::VariableType::tArray: + return !value->arrayValue->empty(); + case Flows::VariableType::tBase64: + return !value->stringValue.empty(); + case Flows::VariableType::tBinary: + return !value->binaryValue.empty(); + case Flows::VariableType::tBoolean: + break; + case Flows::VariableType::tFloat: + return value->floatValue; + case Flows::VariableType::tInteger: + return value->integerValue; + case Flows::VariableType::tInteger64: + return value->integerValue64; + case Flows::VariableType::tString: + return !value->stringValue.empty(); + case Flows::VariableType::tStruct: + return !value->structValue->empty(); + case Flows::VariableType::tVariant: + break; + case Flows::VariableType::tVoid: + return false; + } + } + return value->booleanValue; +} + +bool MyNode::match(Rule& rule, Flows::PVariable& value) +{ + try + { + if(value->type == Flows::VariableType::tInteger) + { + value->setType(Flows::VariableType::tInteger64); + value->integerValue64 = value->integerValue; + } + if(rule.previousValue) + { + rule.v = _previousValue; + rule.vt = _previousValue->type; + } + if(rule.previousValue2) + { + rule.v2 = _previousValue; + rule.v2t = _previousValue->type; + } + if(rule.input) + { + rule.v = _previousInputValue; + rule.vt = _previousInputValue->type; + + if(rule.t == RuleType::tRegex) rule.regex = std::regex(rule.v->stringValue, rule.ignoreCase ? std::regex::icase : std::regex::ECMAScript); + } + if(rule.input2) + { + rule.v2 = _previousInputValue; + rule.v2t = _previousInputValue->type; + } + if(!rule.flowVariable.empty()) + { + rule.v = getFlowData(rule.flowVariable); + rule.vt = rule.v->type; + } + if(!rule.flowVariable2.empty()) + { + rule.v2 = getFlowData(rule.flowVariable2); + rule.v2t = rule.v->type; + } + if(!rule.globalVariable.empty()) + { + rule.v = getGlobalData(rule.globalVariable); + rule.vt = rule.v->type; + } + if(!rule.globalVariable2.empty()) + { + rule.v2 = getGlobalData(rule.globalVariable2); + rule.v2t = rule.v2->type; + } + + switch(rule.t) + { + case RuleType::tEq: + if(value->type == rule.vt && *value == *rule.v) return true; + return false; + case RuleType::tNeq: + if(value->type == rule.vt && *value == *rule.v) return false; + return true; + case RuleType::tLt: + if(value->type == rule.vt && *value < *rule.v) return true; + return false; + case RuleType::tLte: + if(value->type == rule.vt && *value <= *rule.v) return true; + return false; + case RuleType::tGt: + if(value->type == rule.vt && *value > *rule.v) return true; + return false; + case RuleType::tGte: + if(value->type == rule.vt && *value >= *rule.v) return true; + return false; + case RuleType::tBtwn: + if(rule.vt == rule.v2t && rule.vt == value->type) + { + if(rule.vt == Flows::VariableType::tInteger) return value->integerValue >= rule.v->integerValue && value->integerValue <= rule.v2->integerValue; + else if(rule.vt == Flows::VariableType::tInteger64) return value->integerValue64 >= rule.v->integerValue64 && value->integerValue64 <= rule.v2->integerValue64; + else if(rule.vt == Flows::VariableType::tFloat) return value->floatValue >= rule.v->floatValue && value->floatValue <= rule.v2->floatValue; + } + else + { + if(rule.vt == Flows::VariableType::tInteger) rule.v->floatValue = rule.v->integerValue; + if(rule.vt == Flows::VariableType::tInteger64) rule.v->floatValue = rule.v->integerValue64; + if(rule.v2t == Flows::VariableType::tInteger) rule.v2->floatValue = rule.v2->integerValue; + if(rule.v2t == Flows::VariableType::tInteger64) rule.v2->floatValue = rule.v2->integerValue64; + if(value->type == Flows::VariableType::tInteger) value->floatValue = value->integerValue; + if(value->type == Flows::VariableType::tInteger64) value->floatValue = value->integerValue64; + return value->floatValue >= rule.v->floatValue && value->floatValue <= rule.v2->floatValue; + } + return false; + case RuleType::tCont: + return value->stringValue.find(rule.v->toString()) != std::string::npos; + case RuleType::tRegex: + if(value->type != Flows::VariableType::tString) value->stringValue = value->toString(); + return std::regex_match(value->stringValue, rule.regex); + case RuleType::tTrue: + return isTrue(value); + case RuleType::tFalse: + return !isTrue(value); + case RuleType::tNull: + return value->type == Flows::VariableType::tVoid; + case RuleType::tNnull: + return value->type != Flows::VariableType::tVoid; + case RuleType::tElse: + return true; + } + } + catch(const std::exception& ex) + { + _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); + } + catch(...) + { + _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); + } + return false; +} + +Flows::PVariable MyNode::getCurrentValue() +{ + try + { + if(_variableType == VariableType::device || _variableType == VariableType::metadata || _variableType == VariableType::system) + { + Flows::PArray parameters = std::make_shared(); + parameters->reserve(3); + parameters->push_back(std::make_shared(_peerId)); + parameters->push_back(std::make_shared(_channel)); + parameters->push_back(std::make_shared(_variable)); + auto payload = invoke("getValue", parameters); + if(payload->errorStruct) + { + _out->printError("Error: Could not get value of variable: (Peer ID: " + std::to_string(_peerId) + ", channel: " + std::to_string(_channel) + ", name: " + _variable + ")."); + } + else return payload; + } + else if(_variableType == VariableType::flow) + { + auto result = getFlowData(_variable); + if(result->errorStruct) + { + _out->printError("Error: Could not get value of flow variable " + _variable + "."); + } + else return result; + } + else if(_variableType == VariableType::global) + { + auto result = getGlobalData(_variable); + if(result->errorStruct) + { + _out->printError("Error: Could not get value of global variable " + _variable + "."); + } + else return result; + } + } + catch(const std::exception& ex) + { + _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); + } + return Flows::PVariable(); +} + +void MyNode::input(const Flows::PNodeInfo info, uint32_t index, const Flows::PVariable message) +{ + try + { + Flows::PVariable inputValue = message->structValue->at("payload"); + Flows::PVariable currentValue = getCurrentValue(); + if(!currentValue) return; + + for(uint32_t i = 0; i < _rules.size(); i++) + { + if(match(_rules.at(i), currentValue)) + { + if(_outputTrue) + { + if(!_changesOnly || !_rules.at(i).previousOutput->booleanValue) + { + Flows::PVariable trueMessage = std::make_shared(Flows::VariableType::tStruct); + Flows::PVariable trueValue = std::make_shared(true); + trueMessage->structValue->emplace("payload", trueValue); + _rules.at(i).previousOutput = trueValue; + setNodeData("previousOutputValue" + std::to_string(i), trueValue); + output(i, trueMessage); + } + } + else + { + if(!_changesOnly || *(_rules.at(i).previousOutput) != *inputValue) + { + _rules.at(i).previousOutput = inputValue; + setNodeData("previousOutputValue" + std::to_string(i), inputValue); + output(i, message); + } + } + if(!_checkAll) break; + } + else if(_outputFalse) + { + if(!_changesOnly || _rules.at(i).previousOutput->booleanValue) + { + Flows::PVariable falseValue = std::make_shared(false); + _rules.at(i).previousOutput = falseValue; + setNodeData("previousOutputValue" + std::to_string(i), falseValue); + Flows::PVariable falseMessage = std::make_shared(Flows::VariableType::tStruct); + falseMessage->structValue->emplace("payload", falseValue); + output(i, falseMessage); + } + } + else + { + _rules.at(i).previousOutput = std::make_shared(); + setNodeData("previousOutputValue" + std::to_string(i), _rules.at(i).previousOutput); + } + } + + _previousValue = currentValue; + setNodeData("previousValue", _previousInputValue); + + if(index == 0) + { + _previousInputValue = inputValue; + setNodeData("previousInputValue", _previousInputValue); + } + } + catch(const std::exception& ex) + { + _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); + } + catch(...) + { + _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); + } +} + +} diff --git a/basic-logic/variable-switch/MyNode.h b/basic-logic/variable-switch/MyNode.h new file mode 100644 index 00000000..839cb4bd --- /dev/null +++ b/basic-logic/variable-switch/MyNode.h @@ -0,0 +1,122 @@ +/* Copyright 2013-2019 Homegear GmbH + * + * Homegear is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Homegear is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Homegear. If not, see . + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you + * do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source + * files in the program, then also delete it here. + */ + +#ifndef MYNODE_H_ +#define MYNODE_H_ + +#include +#include +#include +#include + +namespace MyNode +{ + +class MyNode: public Flows::INode +{ +public: + enum class VariableType + { + device, + metadata, + system, + flow, + global + }; + + enum class RuleType + { + tEq, + tNeq, + tLt, + tLte, + tGt, + tGte, + tBtwn, + tCont, + tRegex, + tTrue, + tFalse, + tNull, + tNnull, + tElse + }; + + MyNode(std::string path, std::string nodeNamespace, std::string type, const std::atomic_bool* frontendConnected); + virtual ~MyNode(); + + virtual bool init(Flows::PNodeInfo info); +private: + struct Rule + { + RuleType t; + Flows::PVariable v; + Flows::VariableType vt; + Flows::PVariable previousOutput; + bool previousValue = false; + bool input = false; + std::string flowVariable; + std::string globalVariable; + bool ignoreCase = false; + Flows::PVariable v2; + Flows::VariableType v2t; + bool previousValue2 = false; + bool input2 = false; + std::string flowVariable2; + std::string globalVariable2; + std::regex regex; + }; + + typedef std::string Operator; + + VariableType _variableType = VariableType::device; + uint64_t _peerId = 0; + int32_t _channel = -1; + std::string _variable; + + Flows::PVariable _previousValue; + Flows::PVariable _previousInputValue; + std::vector _rules; + bool _changesOnly = false; + bool _outputTrue = false; + bool _outputFalse = false; + bool _checkAll = true; + + RuleType getRuleTypeFromString(std::string& t); + Flows::VariableType getValueTypeFromString(std::string& vt); + void convertType(Flows::PVariable& value, Flows::VariableType vt); + bool isTrue(Flows::PVariable& value); + bool match(Rule& rule, Flows::PVariable& value); + Flows::PVariable getCurrentValue(); + virtual void input(const Flows::PNodeInfo info, uint32_t index, const Flows::PVariable message); +}; + +} + +#endif diff --git a/basic-logic/variable-switch/locales/en-US/variable-switch b/basic-logic/variable-switch/locales/en-US/variable-switch new file mode 100644 index 00000000..7b878658 --- /dev/null +++ b/basic-logic/variable-switch/locales/en-US/variable-switch @@ -0,0 +1,47 @@ +{ + "basic-logic/variable-switch.hni": { + "variable-switch": { + "label": { + "variabletype": "Variable type", + "devicevariable": "Device variable", + "metadata": "Metadata", + "systemvariable": "System variable", + "flowvariable": "Flow variable", + "globalvariable": "Global variable", + "family": "Family", + "peerid": "Peer ID", + "channel": "Channel", + "variable": "Variable", + "createvariable": "Create variable", + "typeandvalue": "Value", + "create": "Create", + "createnewvariable": "Create new variable", + "changes-only": "Forward changes of output only", + "output-true": "Output \"true\" instead of forwarding the message", + "output-false": "Output \"false\", too", + "rule": "rule" + }, + "and": "and", + "checkall": "checking all rules", + "stopfirst": "stopping after first match", + "ignorecase": "ignore case", + "rules": { + "btwn":"is between", + "cont":"contains", + "regex":"matches regex", + "true":"is true", + "false":"is false", + "null":"is null", + "nnull":"is not null", + "else":"otherwise" + }, + "previous": "previous value", + "input": "input", + "flow": "flow.", + "global": "global.", + "paletteHelp": "

Route messages based on the value of a variable.

", + "help": "

Route messages based on the value of a variable.

Details

When a message arrives, the node will evaluate each of the defined rules and forward the message to the corresponding outputs of any matching rules.

Optionally, the node can be set to stop evaluating rules once it finds one that matches.

Also optionally the node can output true and false instead of forwarding the input payload.

", + "input1Description": "The input triggering the comparison." + } + } +} \ No newline at end of file diff --git a/basic-logic/variable-switch/variable-switch.hni b/basic-logic/variable-switch/variable-switch.hni new file mode 100644 index 00000000..69c43553 --- /dev/null +++ b/basic-logic/variable-switch/variable-switch.hni @@ -0,0 +1,782 @@ + + + + + diff --git a/comment/comment.hni b/comment/comment.hni index 0c3ef07b..65c10c85 100644 --- a/comment/comment.hni +++ b/comment/comment.hni @@ -2,7 +2,8 @@ { "name": "comment", "readableName": "Comment", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 0 } diff --git a/configure.ac b/configure.ac index ef08f59a..5a261888 100644 --- a/configure.ac +++ b/configure.ac @@ -62,4 +62,4 @@ esac #AC_ARG_ENABLE(debug, AS_HELP_STRING([--enable-debug], [enable debugging, default: no]), [case "${enableval}" in yes) debug=true ;; no) debug=false ;; *) AC_MSG_ERROR([bad value ${enableval} for --enable-debug]) ;; esac], [debug=false]) #AM_CONDITIONAL(DEBUG, test x"$debug" = x"true") -AC_OUTPUT(Makefile average/Makefile basic-logic/Makefile comment/Makefile debug/Makefile function/Makefile gpio/Makefile http/Makefile influxdb/Makefile light/Makefile link/Makefile modbus/Makefile mqtt/Makefile notification/Makefile parsers/Makefile passthrough/Makefile ping/Makefile presence-light/Makefile press-pattern/Makefile pulsecounter/Makefile runscript/Makefile serial/Makefile synchronous/Makefile storage/Makefile template/Makefile timers/Makefile tls-config/Makefile tls-server-config/Makefile variable/Makefile) +AC_OUTPUT(Makefile average/Makefile basic-logic/Makefile comment/Makefile debug/Makefile exec/Makefile function/Makefile gpio/Makefile http/Makefile influxdb/Makefile light/Makefile link/Makefile modbus/Makefile mqtt/Makefile notification/Makefile parsers/Makefile passthrough/Makefile ping/Makefile presence-light/Makefile press-pattern/Makefile pulsecounter/Makefile python/Makefile serial/Makefile synchronous/Makefile storage/Makefile template/Makefile timers/Makefile tls-config/Makefile tls-server-config/Makefile variable/Makefile) diff --git a/debian/control b/debian/control index 591b602c..34502e49 100644 --- a/debian/control +++ b/debian/control @@ -8,7 +8,7 @@ Homepage: https://homegear.eu Package: homegear-nodes-core Architecture: any -Depends: ${shlibs:Depends}, ${misc:Depends}, libhomegear-node +Depends: ${shlibs:Depends}, ${misc:Depends}, libhomegear-node, python3-homegear Description: Core nodes for Homegear's Node-BLUE Homegear is a program to interface your home automation software with your smart home devices. diff --git a/debian/postinst b/debian/postinst index 02e705c7..648e075e 100644 --- a/debian/postinst +++ b/debian/postinst @@ -21,6 +21,7 @@ case $1 in rm -Rf /var/lib/homegear/node-blue/nodes/basic-logic/locales/en-US/greater-than rm -Rf /var/lib/homegear/node-blue/nodes/basic-logic/locales/en-US/less-or-equal rm -Rf /var/lib/homegear/node-blue/nodes/basic-logic/locales/en-US/less-than + rm -Rf /var/lib/homegear/node-blue/nodes/runscript rm -Rf /var/lib/homegear/node-blue/nodes/sequence chown -R homegear:homegear /var/lib/homegear/node-blue/nodes chmod -R 550 /var/lib/homegear/node-blue/nodes diff --git a/debug/MyNode.cpp b/debug/MyNode.cpp index 92c2cc3d..0e18c23f 100644 --- a/debug/MyNode.cpp +++ b/debug/MyNode.cpp @@ -69,7 +69,7 @@ bool MyNode::init(Flows::PNodeInfo info) return false; } -void MyNode::setNodeVariable(std::string variable, Flows::PVariable value) +void MyNode::setNodeVariable(const std::string& variable, Flows::PVariable value) { try { @@ -91,7 +91,7 @@ std::string MyNode::stripNonPrintable(const std::string& s) strippedString.reserve(s.size()); for(std::string::const_iterator i = s.begin(); i != s.end(); ++i) { - if(std::isprint(*i)) strippedString.push_back(*i); + if(std::isprint(*i, std::locale("en_US.UTF-8"))) strippedString.push_back(*i); } return strippedString; } diff --git a/debug/MyNode.h b/debug/MyNode.h index 26c1a16d..2a105167 100644 --- a/debug/MyNode.h +++ b/debug/MyNode.h @@ -42,7 +42,7 @@ class MyNode: public Flows::INode virtual ~MyNode(); virtual bool init(Flows::PNodeInfo info); - virtual void setNodeVariable(std::string variable, Flows::PVariable value); + virtual void setNodeVariable(const std::string& variable, Flows::PVariable value); private: bool _active = true; bool _hg = false; diff --git a/debug/debug.hni b/debug/debug.hni index e8e46510..36e165c3 100644 --- a/debug/debug.hni +++ b/debug/debug.hni @@ -2,7 +2,8 @@ { "name": "debug", "readableName": "Debug", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 0 } diff --git a/exec/Exec.cpp b/exec/Exec.cpp new file mode 100644 index 00000000..dd16ba7d --- /dev/null +++ b/exec/Exec.cpp @@ -0,0 +1,480 @@ +/* Copyright 2013-2019 Homegear GmbH + * + * Homegear is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Homegear is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Homegear. If not, see . + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you + * do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source + * files in the program, then also delete it here. + */ + +#include "Exec.h" +#include "homegear-base/Managers/ProcessManager.h" +#include + +namespace Exec +{ + +Exec::Exec(std::string path, std::string nodeNamespace, std::string type, const std::atomic_bool* frontendConnected) : Flows::INode(path, nodeNamespace, type, frontendConnected) +{ +} + +Exec::~Exec() +{ + _autostart = false; + if(_pid != -1) kill(_pid, 9); + if(_execThread.joinable()) _execThread.join(); + if(_errorThread.joinable()) _errorThread.join(); + if(_callbackHandlerId != -1) BaseLib::ProcessManager::unregisterCallbackHandler(_callbackHandlerId); +} + +bool Exec::init(Flows::PNodeInfo info) +{ + try + { + auto settingsIterator = info->info->structValue->find("filename"); + if(settingsIterator != info->info->structValue->end()) _filename = settingsIterator->second->stringValue; + + settingsIterator = info->info->structValue->find("arguments"); + if(settingsIterator != info->info->structValue->end()) _arguments = settingsIterator->second->stringValue; + + settingsIterator = info->info->structValue->find("autostart"); + if(settingsIterator != info->info->structValue->end()) _autostart = settingsIterator->second->booleanValue; + + settingsIterator = info->info->structValue->find("collect-output"); + if(settingsIterator != info->info->structValue->end()) _collectOutput = settingsIterator->second->booleanValue; + + return true; + } + catch(const std::exception& ex) + { + _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); + } + return false; +} + +bool Exec::start() +{ + try + { + _callbackHandlerId = BaseLib::ProcessManager::registerCallbackHandler(std::function(std::bind(&Exec::sigchildHandler, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4))); + + if(_autostart) startProgram(); + + return true; + } + catch(const std::exception& ex) + { + _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); + } + return false; +} + +void Exec::stop() +{ + try + { + _autostart = false; + if(_pid != -1) kill(_pid, 15); + } + catch(const std::exception& ex) + { + _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); + } +} + +void Exec::waitForStop() +{ + try + { + if(_pid != -1) kill(_pid, 15); + for(int32_t i = 0; i < 600; i++) + { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + if(_pid == -1) break; + } + if(_pid != -1) + { + _out->printError("Error: Process did not finish within 60 seconds. Killing it."); + kill(_pid, 9); + close(_stdIn); + close(_stdOut); + close(_stdErr); + _stdIn = -1; + _stdOut = -1; + _stdErr = -1; + } + if(_execThread.joinable()) _execThread.join(); + if(_errorThread.joinable()) _errorThread.join(); + BaseLib::ProcessManager::unregisterCallbackHandler(_callbackHandlerId); + } + catch(const std::exception& ex) + { + _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); + } +} + +void Exec::input(const Flows::PNodeInfo info, uint32_t index, const Flows::PVariable message) +{ + try + { + if(index == 0) + { + if(_pid == -1) startProgram(); + } + else if(index == 1) + { + if(_pid != -1) kill(_pid, message->structValue->at("payload")->integerValue64); + } + else if(index == 2) + { + if(_stdIn != -1) + { + size_t totalBytesWritten = 0; + while(totalBytesWritten < message->structValue->at("payload")->stringValue.size()) + { + int bytesWritten = -1; + do + { + bytesWritten = write(_stdIn, message->structValue->at("payload")->stringValue.data(), message->structValue->at("payload")->stringValue.size()); + } while(bytesWritten == -1 && (errno == EAGAIN || errno == EINTR)); + if(bytesWritten <= 0) + { + _out->printWarning("Warning: Could not write to STDIN: " + std::string(strerror(errno))); + break; + } + totalBytesWritten += bytesWritten; + } + } + } + } + catch(const std::exception& ex) + { + _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); + } +} + +void Exec::startProgram() +{ + try + { + if(_filename.empty()) + { + _out->printError("Error: filename is not set."); + return; + } + + { + std::lock_guard bufferGuard(_bufferMutex); + _bufferOut.clear(); + _bufferErr.clear(); + } + + if(_execThread.joinable()) _execThread.join(); + if(_errorThread.joinable()) _errorThread.join(); + _execThread = std::thread(&Exec::execThread, this); + } + catch(const std::exception& ex) + { + _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); + } +} + +int32_t Exec::getMaxFd() +{ + struct rlimit limits{}; + if(getrlimit(RLIMIT_NOFILE, &limits) == -1 || limits.rlim_cur >= INT32_MAX) + { + return 1024; + } + return limits.rlim_cur; +} + +void Exec::execThread() +{ + try + { + do + { + if(_stdErr != -1) + { + close(_stdErr); + _stdErr = -1; + } + if(_errorThread.joinable()) _errorThread.join(); + + int stdIn = -1; + int stdOut = -1; + int stdErr = -1; + _pid = BaseLib::ProcessManager::systemp(_filename, BaseLib::ProcessManager::splitArguments(_arguments), getMaxFd(), stdIn, stdOut, stdErr); + _stdIn = stdIn; + _stdOut = stdOut; + _stdErr = stdErr; + + _errorThread = std::thread(&Exec::errorThread, this); + + std::array buffer{}; + std::string bufferOut; + while(_stdOut != -1) + { + if(!_collectOutput) bufferOut.clear(); + auto bytesRead = 0; + do + { + bytesRead = read(_stdOut, buffer.data(), buffer.size()); + if(bytesRead > 0) + { + if(_collectOutput) + { + std::lock_guard bufferGuard(_bufferMutex); + _bufferOut.insert(_bufferOut.end(), buffer.begin(), buffer.begin() + bytesRead); + } + else bufferOut.insert(bufferOut.end(), buffer.begin(), buffer.begin() + bytesRead); + } + } while(bytesRead > 0); + + if(!_collectOutput && !bufferOut.empty()) + { + auto outputVector = BaseLib::HelperFunctions::splitAll(bufferOut, '\n'); + + Flows::PVariable message = std::make_shared(Flows::VariableType::tStruct); + Flows::PVariable outputArray = std::make_shared(Flows::VariableType::tArray); + outputArray->arrayValue->reserve(outputVector.size()); + for(int32_t i = 0; i < (signed)outputVector.size(); i++) + { + if(i == (signed)outputVector.size() - 1 && outputVector[i].empty()) continue; + outputArray->arrayValue->emplace_back(std::make_shared(std::move(outputVector[i]))); + } + message->structValue->emplace("payload", outputArray); + output(1, message); + } + } + + if(_collectOutput) + { + std::lock_guard bufferGuard(_bufferMutex); + auto outputVector = BaseLib::HelperFunctions::splitAll(_bufferOut, '\n'); + + Flows::PVariable message = std::make_shared(Flows::VariableType::tStruct); + Flows::PVariable outputArray = std::make_shared(Flows::VariableType::tArray); + outputArray->arrayValue->reserve(outputVector.size()); + for(int32_t i = 0; i < (signed)outputVector.size(); i++) + { + if(i == (signed)outputVector.size() - 1 && outputVector[i].empty()) continue; + outputArray->arrayValue->emplace_back(std::make_shared(std::move(outputVector[i]))); + } + message->structValue->emplace("payload", outputArray); + output(1, message); + } + + if(_autostart) std::this_thread::sleep_for(std::chrono::seconds(1)); + } + while(_autostart); + } + catch(const std::exception& ex) + { + _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); + } +} + +void Exec::errorThread() +{ + try + { + std::array buffer{}; + std::string bufferErr; + while(_stdErr != -1) + { + int32_t bytesRead = 0; + if(!_collectOutput) bufferErr.clear(); + do + { + bytesRead = read(_stdErr, buffer.data(), buffer.size()); + if(bytesRead > 0) + { + if(_collectOutput) + { + std::lock_guard bufferGuard(_bufferMutex); + _bufferErr.insert(_bufferErr.end(), buffer.begin(), buffer.begin() + bytesRead); + } + else bufferErr.insert(bufferErr.end(), buffer.begin(), buffer.begin() + bytesRead); + } + } while(bytesRead > 0); + + if(!_collectOutput && !bufferErr.empty()) + { + auto outputVector = BaseLib::HelperFunctions::splitAll(bufferErr, '\n'); + + Flows::PVariable message = std::make_shared(Flows::VariableType::tStruct); + Flows::PVariable outputArray = std::make_shared(Flows::VariableType::tArray); + outputArray->arrayValue->reserve(outputVector.size()); + for(int32_t i = 0; i < (signed)outputVector.size(); i++) + { + if(i == (signed)outputVector.size() - 1 && outputVector[i].empty()) continue; + outputArray->arrayValue->emplace_back(std::make_shared(std::move(outputVector[i]))); + } + message->structValue->emplace("payload", outputArray); + output(2, message); + } + } + + if(_collectOutput) + { + std::lock_guard bufferGuard(_bufferMutex); + if(!_bufferErr.empty()) + { + auto outputVector = BaseLib::HelperFunctions::splitAll(_bufferErr, '\n'); + + Flows::PVariable message = std::make_shared(Flows::VariableType::tStruct); + Flows::PVariable outputArray = std::make_shared(Flows::VariableType::tArray); + outputArray->arrayValue->reserve(outputVector.size()); + for(int32_t i = 0; i < (signed)outputVector.size(); i++) + { + if(i == (signed)outputVector.size() - 1 && outputVector[i].empty()) continue; + outputArray->arrayValue->emplace_back(std::make_shared(std::move(outputVector[i]))); + } + message->structValue->emplace("payload", outputArray); + output(2, message); + } + } + } + catch(const std::exception& ex) + { + _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); + } +} + +void Exec::sigchildHandler(pid_t pid, int exitCode, int signal, bool coreDumped) +{ + try + { + if(pid == _pid) + { + close(_stdIn); + close(_stdOut); + close(_stdErr); + _stdIn = -1; + _stdOut = -1; + _stdErr = -1; + _pid = -1; + Flows::PVariable message = std::make_shared(Flows::VariableType::tStruct); + message->structValue->emplace("coreDumped", std::make_shared(coreDumped)); + message->structValue->emplace("signal", std::make_shared(signal)); + message->structValue->emplace("payload", std::make_shared(exitCode)); + if(_collectOutput) + { + std::lock_guard bufferGuard(_bufferMutex); + + { + auto outputVector = BaseLib::HelperFunctions::splitAll(_bufferOut, '\n'); + + Flows::PVariable message = std::make_shared(Flows::VariableType::tStruct); + Flows::PVariable outputArray = std::make_shared(Flows::VariableType::tArray); + outputArray->arrayValue->reserve(outputVector.size()); + for(int32_t i = 0; i < (signed)outputVector.size(); i++) + { + if(i == (signed)outputVector.size() - 1 && outputVector[i].empty()) continue; + outputArray->arrayValue->emplace_back(std::make_shared(std::move(outputVector[i]))); + } + message->structValue->emplace("stdout", outputArray); + } + + if(!_bufferErr.empty()) + { + auto outputVector = BaseLib::HelperFunctions::splitAll(_bufferErr, '\n'); + + Flows::PVariable message = std::make_shared(Flows::VariableType::tStruct); + Flows::PVariable outputArray = std::make_shared(Flows::VariableType::tArray); + outputArray->arrayValue->reserve(outputVector.size()); + for(int32_t i = 0; i < (signed)outputVector.size(); i++) + { + if(i == (signed)outputVector.size() - 1 && outputVector[i].empty()) continue; + outputArray->arrayValue->emplace_back(std::make_shared(std::move(outputVector[i]))); + } + message->structValue->emplace("stderr", outputArray); + } + } + output(0, message); + } + } + catch(const std::exception& ex) + { + _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); + } +} + +int32_t Exec::read(std::atomic_int& fd, uint8_t* buffer, int32_t bufferSize) +{ + if(fd == -1) return 0; + + timeval timeout{}; + timeout.tv_sec = 0; + timeout.tv_usec = 100000; + fd_set readFileDescriptor{}; + FD_ZERO(&readFileDescriptor); + int32_t nfds = fd + 1; + if(nfds <= 0) + { + close(fd); + fd = -1; + return -1; + } + FD_SET(fd, &readFileDescriptor); + auto bytesRead = select(nfds, &readFileDescriptor, nullptr, nullptr, &timeout); + if(bytesRead == 0) + { + return 0; + } + if(bytesRead != 1) + { + close(fd); + fd = -1; + return -1; + } + do + { + bytesRead = ::read(fd, buffer, bufferSize); + } while(bytesRead < 0 && (errno == EAGAIN || errno == EINTR)); + if(bytesRead <= 0) + { + if(bytesRead == -1) + { + if(errno == ETIMEDOUT) return 0; + else + { + close(fd); + fd = -1; + return -1; + } + } + else + { + close(fd); + fd = -1; + return -1; + } + } + if(bytesRead > bufferSize) bytesRead = bufferSize; + return bytesRead; +} + +} diff --git a/exec/Exec.h b/exec/Exec.h new file mode 100644 index 00000000..06734786 --- /dev/null +++ b/exec/Exec.h @@ -0,0 +1,77 @@ +/* Copyright 2013-2019 Homegear GmbH + * + * Homegear is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Homegear is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Homegear. If not, see . + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you + * do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source + * files in the program, then also delete it here. + */ + +#ifndef RUNSCRIPT_H_ +#define RUNSCRIPT_H_ + +#include +#include +#include + +namespace Exec +{ + +class Exec : public Flows::INode +{ +public: + Exec(std::string path, std::string nodeNamespace, std::string type, const std::atomic_bool* frontendConnected); + virtual ~Exec(); + + virtual bool init(Flows::PNodeInfo info); + virtual bool start(); + virtual void stop(); + virtual void waitForStop(); +private: + int32_t _callbackHandlerId = -1; + std::string _filename; + std::string _arguments; + std::atomic_bool _autostart{false}; + bool _collectOutput = false; + std::thread _execThread; + std::thread _errorThread; + std::mutex _bufferMutex; + std::string _bufferOut; + std::string _bufferErr; + std::atomic_int _pid{-1}; + std::atomic_int _stdIn{-1}; + std::atomic_int _stdOut{-1}; + std::atomic_int _stdErr{-1}; + + virtual void input(const Flows::PNodeInfo info, uint32_t index, const Flows::PVariable message); + void startProgram(); + int32_t getMaxFd(); + void sigchildHandler(pid_t pid, int exitCode, int signal, bool coreDumped); + void execThread(); + void errorThread(); + int32_t read(std::atomic_int& fd, uint8_t* buffer, int32_t bufferSize); +}; + +} + +#endif diff --git a/runscript/Factory.cpp b/exec/Factory.cpp similarity index 94% rename from runscript/Factory.cpp rename to exec/Factory.cpp index bdced55d..b1012835 100644 --- a/runscript/Factory.cpp +++ b/exec/Factory.cpp @@ -28,12 +28,12 @@ */ #include "Factory.h" -#include "RunScript.h" +#include "Exec.h" #include "../config.h" Flows::INode* MyFactory::createNode(std::string path, std::string nodeNamespace, std::string type, const std::atomic_bool* frontendConnected) { - return new RunScript::RunScript(path, nodeNamespace, type, frontendConnected); + return new Exec::Exec(path, nodeNamespace, type, frontendConnected); } Flows::NodeFactory* getFactory() diff --git a/runscript/Factory.h b/exec/Factory.h similarity index 100% rename from runscript/Factory.h rename to exec/Factory.h diff --git a/exec/Makefile.am b/exec/Makefile.am new file mode 100644 index 00000000..c62d84ca --- /dev/null +++ b/exec/Makefile.am @@ -0,0 +1,16 @@ +AUTOMAKE_OPTIONS = subdir-objects + +AM_CPPFLAGS = -Wall -std=c++11 -DFORTIFY_SOURCE=2 -DGCRYPT_NO_DEPRECATED +AM_LDFLAGS = -Wl,-rpath=/lib/homegear -Wl,-rpath=/usr/lib/homegear -Wl,-rpath=/usr/local/lib/homegear +LIBS += -Wl,-Bdynamic -lhomegear-node -lhomegear-base + +libdir = $(localstatedir)/lib/homegear/node-blue/nodes/exec +lib_LTLIBRARIES = exec.la +exec_la_SOURCES = Factory.cpp Exec.cpp +exec_la_LDFLAGS =-module -avoid-version -shared +exec_ladir = $(libdir) +exec_la_DATA = exec.hni +locale_en_usdir = $(libdir)/locales/en-US +locale_en_us_DATA = locales/en-US/exec +install-exec-hook: + rm -f $(DESTDIR)$(libdir)/exec.la diff --git a/exec/exec.hni b/exec/exec.hni new file mode 100644 index 00000000..d9dbb763 --- /dev/null +++ b/exec/exec.hni @@ -0,0 +1,83 @@ + + + diff --git a/exec/locales/en-US/exec b/exec/locales/en-US/exec new file mode 100644 index 00000000..9d6341d1 --- /dev/null +++ b/exec/locales/en-US/exec @@ -0,0 +1,21 @@ +{ + "exec/exec.hni": { + "exec": { + "label": { + "filename": "Filename", + "arguments": "Arguments", + "autostart": "Start program on startup and restart it if it exits.", + "collect-output": "Wait to send output until process exits.", + "name": "Name" + }, + "paletteHelp": "

This node executes a program.

", + "help": "

This node executes a program. EXEC or the autostart option starts the program. The program is allowed to run indefinitely. When the autostart option is enabled, the program is restarted 1 second after it finishes. You can send input to the program throuth IN and the program's output is sent to OUT or ERR as an array split on new line characters. When the program finishes, the exit code is output on CODE. You can also send signals to the program through SIG.

", + "input1Description": "Execute the program. If it is already running, the input is ignored.", + "input2Description": "This signal is passed to the running program.", + "input3Description": "This input is sent to stdin of the program.", + "output1Description": "Outputs the exit code and some additional information when the process finishes.", + "output2Description": "stdout of the program.", + "output3Description": "stderr of the program." + } + } +} \ No newline at end of file diff --git a/function/function.hni b/function/function.hni index 4a24c746..ed597e5c 100644 --- a/function/function.hni +++ b/function/function.hni @@ -2,7 +2,8 @@ { "name": "function", "readableName": "function", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 0 } @@ -46,7 +47,7 @@ diff --git a/gpio/gpio-out/gpio-out.hni b/gpio/gpio-out/gpio-out.hni index b28200d4..7d6da6d6 100644 --- a/gpio/gpio-out/gpio-out.hni +++ b/gpio/gpio-out/gpio-out.hni @@ -2,7 +2,8 @@ { "name": "gpio-out", "readableName": "GPIO output", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 0 } diff --git a/http/http-in/MyNode.cpp b/http/http-in/MyNode.cpp index 674608c5..666b2794 100644 --- a/http/http-in/MyNode.cpp +++ b/http/http-in/MyNode.cpp @@ -124,7 +124,7 @@ std::vector MyNode::splitAll(std::string string, char delimiter) try { if(parameters->size() != 8) return Flows::Variable::createError(-1, "Method expects exactly 8 parameters. " + std::to_string(parameters->size()) + " given."); - if(parameters->at(0)->type != Flows::VariableType::tInteger) return Flows::Variable::createError(-1, "Parameter 1 is not of type integer."); + if(parameters->at(0)->type != Flows::VariableType::tInteger && parameters->at(0)->type != Flows::VariableType::tInteger64) return Flows::Variable::createError(-1, "Parameter 1 is not of type integer."); if(parameters->at(1)->type != Flows::VariableType::tString) return Flows::Variable::createError(-1, "Parameter 2 is not of type string."); if(parameters->at(2)->type != Flows::VariableType::tString) return Flows::Variable::createError(-1, "Parameter 3 is not of type string."); if(parameters->at(3)->type != Flows::VariableType::tStruct) return Flows::Variable::createError(-1, "Parameter 4 is not of type string."); diff --git a/http/http-in/http-in.hni b/http/http-in/http-in.hni index 1fd919ac..47f2069a 100644 --- a/http/http-in/http-in.hni +++ b/http/http-in/http-in.hni @@ -2,7 +2,8 @@ { "name": "http-in", "readableName": "HTTP input", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 0 } diff --git a/http/http-request/MyNode.cpp b/http/http-request/MyNode.cpp index 4e0db672..d6e9ed77 100644 --- a/http/http-request/MyNode.cpp +++ b/http/http-request/MyNode.cpp @@ -141,7 +141,9 @@ void MyNode::setUrl(std::string& url) } _caData = getConfigParameter(_tlsNode, "cadata.password")->stringValue; _certData = getConfigParameter(_tlsNode, "certdata.password")->stringValue; - _keyData = getConfigParameter(_tlsNode, "keydata.password")->stringValue; + auto keyData = getConfigParameter(_tlsNode, "keydata.password")->stringValue; + _keyData = std::make_shared>(); + _keyData->insert(_keyData->end(), keyData.begin(), keyData.end()); _caPath = getConfigParameter(_tlsNode, "ca")->stringValue; _certPath = getConfigParameter(_tlsNode, "cert")->stringValue; _keyPath = getConfigParameter(_tlsNode, "key")->stringValue; diff --git a/http/http-request/MyNode.h b/http/http-request/MyNode.h index 0a91d93b..541f9976 100644 --- a/http/http-request/MyNode.h +++ b/http/http-request/MyNode.h @@ -33,6 +33,7 @@ #include #include #include +#include namespace MyNode { @@ -66,7 +67,7 @@ class MyNode: public Flows::INode std::string _certPath; std::string _certData; std::string _keyPath; - std::string _keyData; + std::shared_ptr> _keyData; bool _verifyCertificate = true; ReturnType _returnType; diff --git a/http/http-request/http-request.hni b/http/http-request/http-request.hni index ab3135be..9d9d6006 100644 --- a/http/http-request/http-request.hni +++ b/http/http-request/http-request.hni @@ -2,7 +2,8 @@ { "name": "http-request", "readableName": "HTTP request", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 0 } diff --git a/http/http-response/http-response.hni b/http/http-response/http-response.hni index 6276284c..4f25335d 100644 --- a/http/http-response/http-response.hni +++ b/http/http-response/http-response.hni @@ -2,7 +2,8 @@ { "name": "http-response", "readableName": "HTTP response", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 0 } diff --git a/http/http-server/MyNode.cpp b/http/http-server/MyNode.cpp index b9190cce..e6215db0 100644 --- a/http/http-server/MyNode.cpp +++ b/http/http-server/MyNode.cpp @@ -28,6 +28,7 @@ */ #include "MyNode.h" +#include namespace MyNode { @@ -101,7 +102,10 @@ bool MyNode::start() certificateInfo->certFile = getConfigParameter(tlsNodeId, "cert")->stringValue; certificateInfo->certData = getConfigParameter(tlsNodeId, "certdata.password")->stringValue; certificateInfo->keyFile = getConfigParameter(tlsNodeId, "key")->stringValue; - certificateInfo->keyData = getConfigParameter(tlsNodeId, "keydata.password")->stringValue; + auto keyData = getConfigParameter(tlsNodeId, "keydata.password")->stringValue; + auto secureKeyData = std::make_shared>(); + secureKeyData->insert(secureKeyData->end(), keyData.begin(), keyData.end()); + certificateInfo->keyData = secureKeyData; serverInfo.certificates.emplace("*", certificateInfo); serverInfo.dhParamData = getConfigParameter(tlsNodeId, "dhdata.password")->stringValue; serverInfo.dhParamFile = getConfigParameter(tlsNodeId, "dh")->stringValue; @@ -120,7 +124,7 @@ bool MyNode::start() } catch(BaseLib::Exception& ex) { - _out->printError("Error starting server: " + ex.what()); + _out->printError("Error starting server: " + std::string(ex.what())); return false; } @@ -290,6 +294,7 @@ void MyNode::packetReceived(int32_t clientId, BaseLib::Http http) std::lock_guard nodesGuard(_nodesMutex); for(auto& node : _nodes) { + auto methodIterator = node.second.find(http.getHeader().method); if(methodIterator == node.second.end()) continue; if(std::regex_match(http.getHeader().path, methodIterator->second.pathRegex)) diff --git a/http/http-server/http-server.hni b/http/http-server/http-server.hni index 551d330e..a5a960d5 100644 --- a/http/http-server/http-server.hni +++ b/http/http-server/http-server.hni @@ -2,7 +2,8 @@ { "name": "http-server", "readableName": "HTTP server", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 1 } diff --git a/influxdb/influxdb.hni b/influxdb/influxdb.hni index 950757af..c523da60 100644 --- a/influxdb/influxdb.hni +++ b/influxdb/influxdb.hni @@ -2,7 +2,8 @@ { "name": "influxdb", "readableName": "InfluxDB", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 0 } diff --git a/light/light.hni b/light/light.hni index a8bfbfbe..1d088b41 100644 --- a/light/light.hni +++ b/light/light.hni @@ -2,7 +2,8 @@ { "name": "light", "readableName": "Light", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 1 } diff --git a/link/link-in/link-in.hni b/link/link-in/link-in.hni index ee29ea44..2e89446b 100644 --- a/link/link-in/link-in.hni +++ b/link/link-in/link-in.hni @@ -2,7 +2,8 @@ { "name": "link-in", "readableName": "Link in", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 0 } diff --git a/link/link-out/link-out.hni b/link/link-out/link-out.hni index 944f073c..480b8a6d 100644 --- a/link/link-out/link-out.hni +++ b/link/link-out/link-out.hni @@ -2,7 +2,8 @@ { "name": "link-out", "readableName": "Link out", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 1 } diff --git a/modbus/modbus-host/Modbus.cpp b/modbus/modbus-host/Modbus.cpp index 6763c292..ff4a6586 100644 --- a/modbus/modbus-host/Modbus.cpp +++ b/modbus/modbus-host/Modbus.cpp @@ -130,10 +130,6 @@ Modbus::Modbus(std::shared_ptr bl, std::shared_ptrprintEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); } - catch(BaseLib::Exception& ex) - { - _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); - } catch(...) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); @@ -152,10 +148,6 @@ Modbus::~Modbus() { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); } - catch(BaseLib::Exception& ex) - { - _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); - } catch(...) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); @@ -175,10 +167,6 @@ void Modbus::start() { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); } - catch(BaseLib::Exception& ex) - { - _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); - } catch(...) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); @@ -195,10 +183,6 @@ void Modbus::stop() { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); } - catch(BaseLib::Exception& ex) - { - _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); - } catch(...) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); @@ -217,10 +201,6 @@ void Modbus::waitForStop() { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); } - catch(BaseLib::Exception& ex) - { - _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); - } catch(...) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); @@ -235,7 +215,7 @@ void Modbus::readWriteRegister(std::shared_ptr& info) { _modbus->readHoldingRegisters(info->start, info->buffer1, info->count); } - catch(BaseLib::Exception& ex) + catch(std::exception& ex) { _out->printError("Error reading from Modbus registers " + std::to_string(info->start) + " to " + std::to_string(info->end) + ": " + ex.what()); } @@ -262,10 +242,6 @@ void Modbus::readWriteRegister(std::shared_ptr& info) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); } - catch(BaseLib::Exception& ex) - { - _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); - } catch(...) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); @@ -280,7 +256,7 @@ void Modbus::readWriteCoil(std::shared_ptr& info) { _modbus->readCoils(info->start, info->buffer1, info->count); } - catch(BaseLib::Exception& ex) + catch(std::exception& ex) { _out->printError("Error reading from Modbus coils " + std::to_string(info->start) + " to " + std::to_string(info->end) + ": " + ex.what()); } @@ -307,10 +283,6 @@ void Modbus::readWriteCoil(std::shared_ptr& info) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); } - catch(BaseLib::Exception& ex) - { - _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); - } catch(...) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); @@ -351,7 +323,7 @@ void Modbus::listen() { _modbus->writeMultipleRegisters(registerElement->start, registerElement->buffer1, registerElement->count); } - catch(BaseLib::Exception& ex) + catch(std::exception& ex) { _out->printError("Error writing to Modbus registers " + std::to_string(registerElement->start) + " to " + std::to_string(registerElement->end) + ": " + ex.what() + " - Disconnecting..."); disconnect(); @@ -389,7 +361,7 @@ void Modbus::listen() { _modbus->readHoldingRegisters(registerElement->start, registerElement->buffer2, registerElement->count); } - catch(BaseLib::Exception& ex) + catch(std::exception& ex) { _out->printError("Error reading from Modbus holding registers " + std::to_string(registerElement->start) + " to " + std::to_string(registerElement->end) + ": " + ex.what() + " - Disconnecting..."); disconnect(); @@ -529,7 +501,7 @@ void Modbus::listen() { _modbus->readInputRegisters(registerElement->start, registerElement->buffer2, registerElement->count); } - catch(BaseLib::Exception& ex) + catch(std::exception& ex) { _out->printError("Error reading from Modbus input registers " + std::to_string(registerElement->start) + " to " + std::to_string(registerElement->end) + ": " + ex.what() + " - Disconnecting..."); disconnect(); @@ -673,7 +645,7 @@ void Modbus::listen() { _modbus->writeMultipleCoils(coilElement->start, coilElement->buffer1, coilElement->count); } - catch(BaseLib::Exception& ex) + catch(std::exception& ex) { _out->printError("Error writing Modbus coils " + std::to_string(coilElement->start) + " to " + std::to_string(coilElement->end) + ": " + ex.what() + " - Disconnecting..."); disconnect(); @@ -711,7 +683,7 @@ void Modbus::listen() { _modbus->readCoils(coilElement->start, coilElement->buffer2, coilElement->count); } - catch(BaseLib::Exception& ex) + catch(std::exception& ex) { _out->printError("Error reading from Modbus coils " + std::to_string(coilElement->start) + " to " + std::to_string(coilElement->end) + ": " + ex.what() + " - Disconnecting..."); disconnect(); @@ -781,7 +753,7 @@ void Modbus::listen() { _modbus->readDiscreteInputs(discreteInputElement->start, discreteInputElement->buffer2, discreteInputElement->count); } - catch(BaseLib::Exception& ex) + catch(std::exception& ex) { _out->printError("Error reading from Modbus discrete inputs " + std::to_string(discreteInputElement->start) + " to " + std::to_string(discreteInputElement->end) + ": " + ex.what() + " - Disconnecting..."); disconnect(); @@ -862,10 +834,6 @@ void Modbus::listen() { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); } - catch(BaseLib::Exception& ex) - { - _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); - } catch(...) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); @@ -906,10 +874,6 @@ void Modbus::setConnectionState(bool connected) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); } - catch(BaseLib::Exception& ex) - { - _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); - } catch(...) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); @@ -975,10 +939,6 @@ void Modbus::connect() { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); } - catch(BaseLib::Exception& ex) - { - _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); - } catch(...) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); @@ -998,10 +958,6 @@ void Modbus::disconnect() { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); } - catch(BaseLib::Exception& ex) - { - _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); - } catch(...) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); @@ -1051,10 +1007,6 @@ void Modbus::registerNode(std::string& node, ModbusType type, uint32_t startRegi { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); } - catch(BaseLib::Exception& ex) - { - _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); - } catch(...) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); @@ -1102,10 +1054,6 @@ void Modbus::registerNode(std::string& node, ModbusType type, uint32_t startCoil { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); } - catch(BaseLib::Exception& ex) - { - _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); - } catch(...) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); @@ -1194,10 +1142,6 @@ void Modbus::writeRegisters(uint32_t startRegister, uint32_t count, bool invertB { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); } - catch(BaseLib::Exception& ex) - { - _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); - } catch(...) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); @@ -1239,10 +1183,6 @@ void Modbus::writeCoils(uint32_t startCoil, uint32_t count, bool retry, std::vec { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); } - catch(BaseLib::Exception& ex) - { - _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); - } catch(...) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); diff --git a/modbus/modbus-host/modbus-host.hni b/modbus/modbus-host/modbus-host.hni index 80f1d6ad..3dba016d 100644 --- a/modbus/modbus-host/modbus-host.hni +++ b/modbus/modbus-host/modbus-host.hni @@ -2,7 +2,8 @@ { "name": "modbus-host", "readableName": "Modbus host", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 1 } diff --git a/modbus/modbus-in/modbus-in.hni b/modbus/modbus-in/modbus-in.hni index 710ce92e..02e3199b 100644 --- a/modbus/modbus-in/modbus-in.hni +++ b/modbus/modbus-in/modbus-in.hni @@ -2,7 +2,8 @@ { "name": "modbus-in", "readableName": "Modbus input", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 0 } diff --git a/modbus/modbus-out/modbus-out.hni b/modbus/modbus-out/modbus-out.hni index 8797f7a3..a51cf71a 100644 --- a/modbus/modbus-out/modbus-out.hni +++ b/modbus/modbus-out/modbus-out.hni @@ -2,7 +2,8 @@ { "name": "modbus-out", "readableName": "Modbus output", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 0 } diff --git a/mqtt/mqtt-broker/Mqtt.cpp b/mqtt/mqtt-broker/Mqtt.cpp index 1aabbbe0..1e9ed035 100644 --- a/mqtt/mqtt-broker/Mqtt.cpp +++ b/mqtt/mqtt-broker/Mqtt.cpp @@ -51,10 +51,6 @@ Mqtt::Mqtt(std::shared_ptr bl, std::shared_ptrprintEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); } - catch(BaseLib::Exception& ex) - { - _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); - } catch(...) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); @@ -72,10 +68,6 @@ Mqtt::~Mqtt() { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); } - catch(BaseLib::Exception& ex) - { - _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); - } catch(...) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); @@ -103,10 +95,6 @@ void Mqtt::start() { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); } - catch(BaseLib::Exception& ex) - { - _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); - } catch(...) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); @@ -123,10 +111,6 @@ void Mqtt::stop() { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); } - catch(BaseLib::Exception& ex) - { - _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); - } catch(...) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); @@ -153,10 +137,6 @@ void Mqtt::waitForStop() { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); } - catch(BaseLib::Exception& ex) - { - _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); - } catch(...) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); @@ -265,11 +245,6 @@ void Mqtt::getResponseByType(const std::vector& packet, std::vector& _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); _requestsByTypeMutex.unlock(); } - catch(BaseLib::Exception& ex) - { - _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); - _requestsByTypeMutex.unlock(); - } catch(...) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); @@ -317,11 +292,6 @@ void Mqtt::getResponse(const std::vector& packet, std::vector& respo _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); _requestsMutex.unlock(); } - catch(BaseLib::Exception& ex) - { - _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); - _requestsMutex.unlock(); - } catch(...) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); @@ -360,10 +330,6 @@ void Mqtt::ping() { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); } - catch(BaseLib::Exception& ex) - { - _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); - } catch(...) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); @@ -444,7 +410,7 @@ void Mqtt::listen() catch(BaseLib::SocketOperationException& ex) { _socket->close(); - _out->printError("Error: " + ex.what()); + _out->printError("Error: " + std::string(ex.what())); continue; } @@ -464,10 +430,6 @@ void Mqtt::listen() { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); } - catch(BaseLib::Exception& ex) - { - _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); - } catch(...) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); @@ -538,10 +500,6 @@ void Mqtt::processData(std::vector& data) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); } - catch(BaseLib::Exception& ex) - { - _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); - } catch(...) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); @@ -604,10 +562,6 @@ void Mqtt::processPublish(std::vector& data) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); } - catch(BaseLib::Exception& ex) - { - _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); - } catch(...) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); @@ -677,10 +631,6 @@ void Mqtt::subscribe(std::string& topic) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); } - catch(BaseLib::Exception& ex) - { - _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); - } catch(...) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); @@ -731,10 +681,6 @@ void Mqtt::unsubscribe(std::string& topic) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); } - catch(BaseLib::Exception& ex) - { - _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); - } catch(...) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); @@ -770,10 +716,6 @@ void Mqtt::reconnectThread() { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); } - catch(BaseLib::Exception& ex) - { - _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); - } catch(...) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); @@ -795,10 +737,6 @@ void Mqtt::reconnect() { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); } - catch(BaseLib::Exception& ex) - { - _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); - } catch(...) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); @@ -947,10 +885,6 @@ void Mqtt::connect() { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); } - catch(BaseLib::Exception& ex) - { - _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); - } catch(...) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); @@ -974,10 +908,6 @@ void Mqtt::disconnect() { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); } - catch(BaseLib::Exception& ex) - { - _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); - } catch(...) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); @@ -1020,10 +950,6 @@ void Mqtt::registerNode(std::string& node) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); } - catch(BaseLib::Exception& ex) - { - _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); - } catch(...) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); @@ -1049,10 +975,6 @@ void Mqtt::registerTopic(std::string& node, std::string& topic) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); } - catch(BaseLib::Exception& ex) - { - _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); - } catch(...) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); @@ -1078,10 +1000,6 @@ void Mqtt::unregisterTopic(std::string& node, std::string& topic) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); } - catch(BaseLib::Exception& ex) - { - _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); - } catch(...) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); @@ -1104,10 +1022,6 @@ void Mqtt::queueMessage(std::string& topic, std::string& payload, bool retain) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); } - catch(BaseLib::Exception& ex) - { - _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); - } catch(...) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); @@ -1179,10 +1093,6 @@ void Mqtt::publish(const std::string& topic, const std::vector& data, bool { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); } - catch(BaseLib::Exception& ex) - { - _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); - } catch(...) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); @@ -1212,10 +1122,6 @@ void Mqtt::processQueueEntry(int32_t index, std::shared_ptrprintEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); } - catch(const BaseLib::Exception& ex) - { - _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); - } catch(...) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); diff --git a/mqtt/mqtt-broker/Mqtt.h b/mqtt/mqtt-broker/Mqtt.h index 145c0ca8..9fd0b3d7 100644 --- a/mqtt/mqtt-broker/Mqtt.h +++ b/mqtt/mqtt-broker/Mqtt.h @@ -34,6 +34,7 @@ #include #include #include +#include #include #include @@ -60,7 +61,7 @@ class Mqtt : public BaseLib::IQueue std::string certPath; std::string certData; std::string keyPath; - std::string keyData; + std::shared_ptr> keyData; bool verifyCertificate = true; }; diff --git a/mqtt/mqtt-broker/MyNode.cpp b/mqtt/mqtt-broker/MyNode.cpp index 3c46d943..9cfc7cf0 100644 --- a/mqtt/mqtt-broker/MyNode.cpp +++ b/mqtt/mqtt-broker/MyNode.cpp @@ -94,7 +94,9 @@ bool MyNode::start() { mqttSettings->caData = getConfigParameter(tlsNodeId, "cadata.password")->stringValue; mqttSettings->certData = getConfigParameter(tlsNodeId, "certdata.password")->stringValue; - mqttSettings->keyData = getConfigParameter(tlsNodeId, "keydata.password")->stringValue; + auto keyData = getConfigParameter(tlsNodeId, "keydata.password")->stringValue; + mqttSettings->keyData = std::make_shared>(); + mqttSettings->keyData->insert(mqttSettings->keyData->end(), keyData.begin(), keyData.end()); mqttSettings->caPath = getConfigParameter(tlsNodeId, "ca")->stringValue; mqttSettings->certPath = getConfigParameter(tlsNodeId, "cert")->stringValue; mqttSettings->keyPath = getConfigParameter(tlsNodeId, "key")->stringValue; diff --git a/mqtt/mqtt-broker/mqtt-broker.hni b/mqtt/mqtt-broker/mqtt-broker.hni index c8616491..3304dc9e 100644 --- a/mqtt/mqtt-broker/mqtt-broker.hni +++ b/mqtt/mqtt-broker/mqtt-broker.hni @@ -2,7 +2,8 @@ { "name": "mqtt-broker", "readableName": "MQTT broker", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 6 } diff --git a/mqtt/mqtt-in/mqtt-in.hni b/mqtt/mqtt-in/mqtt-in.hni index 2ce484d4..fc9f44fc 100644 --- a/mqtt/mqtt-in/mqtt-in.hni +++ b/mqtt/mqtt-in/mqtt-in.hni @@ -2,7 +2,8 @@ { "name": "mqtt-in", "readableName": "MQTT input", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 0 } diff --git a/mqtt/mqtt-out/MyNode.cpp b/mqtt/mqtt-out/MyNode.cpp index 22ca44eb..4b036895 100644 --- a/mqtt/mqtt-out/MyNode.cpp +++ b/mqtt/mqtt-out/MyNode.cpp @@ -52,7 +52,7 @@ bool MyNode::init(Flows::PNodeInfo info) if(settingsIterator != info->info->structValue->end()) _topic = settingsIterator->second->stringValue; settingsIterator = info->info->structValue->find("retain"); - if(settingsIterator != info->info->structValue->end()) _retain = settingsIterator->second->booleanValue; + if(settingsIterator != info->info->structValue->end()) _retain = (settingsIterator->second->stringValue == "true"); return true; } diff --git a/mqtt/mqtt-out/mqtt-out.hni b/mqtt/mqtt-out/mqtt-out.hni index 5f4412c5..d0400236 100644 --- a/mqtt/mqtt-out/mqtt-out.hni +++ b/mqtt/mqtt-out/mqtt-out.hni @@ -2,7 +2,8 @@ { "name": "mqtt-out", "readableName": "MQTT output", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 0 } diff --git a/notification/locales/de-DE/xmpp b/notification/locales/de-DE/xmpp new file mode 100755 index 00000000..4f4ba176 --- /dev/null +++ b/notification/locales/de-DE/xmpp @@ -0,0 +1,15 @@ +{ + "notification/xmpp.hni": { + "xmpp": { + "label": { + "name": "Name", + "server": "Server", + "recipient": "Empfänger", + "message": "Nachricht" + }, + "tip": "Tip: See the Info tab for help writing functions.", + "paletteHelp": "

Sendet eine Benachrichtigung per XMPP.

", + "help": "

Dieser Knoten sendet eine Benachrichtigung per XMPP.

Der Empfänger und die Nachricht kann manuell direkt im Knoten eingegeben werden, oder alternativ per
$message['xmpp-recipient']
$message['xmpp-message']
programmatisch (z.B. durch einen Funktions-Knoten) übergeben werden.

" + } + } +} \ No newline at end of file diff --git a/notification/locales/de-DE/xmpp-server b/notification/locales/de-DE/xmpp-server new file mode 100755 index 00000000..bc4aa656 --- /dev/null +++ b/notification/locales/de-DE/xmpp-server @@ -0,0 +1,13 @@ +{ + "notification/xmpp-server.hni": { + "xmpp-server": { + "help": "

Die XMPP (Jabber) Verbidung benötigt die Eingabe der Server-Addresse (den Domainnamen oder eine IP-Addresse ohne http(s)://), des Ports, einen Benutzernamen (nur den 'Localpart') und ein Passwort.

", + "label": { + "server": "Server", + "port": "Port", + "user": "Benutzername", + "pwd": "Passwort" + } + } + } +} \ No newline at end of file diff --git a/notification/xmpp-server.hni b/notification/xmpp-server.hni index ea9649a6..5d3b2a89 100755 --- a/notification/xmpp-server.hni +++ b/notification/xmpp-server.hni @@ -2,7 +2,8 @@ { "name": "xmpp-server", "readableName": "XMPP Server", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 6 } diff --git a/notification/xmpp.hni b/notification/xmpp.hni index 099603e0..87f4de01 100755 --- a/notification/xmpp.hni +++ b/notification/xmpp.hni @@ -2,7 +2,8 @@ { "name": "xmpp", "readableName": "XMPP", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 0 } diff --git a/parsers/json/json.hni b/parsers/json/json.hni index 53824149..3c8926ba 100644 --- a/parsers/json/json.hni +++ b/parsers/json/json.hni @@ -2,7 +2,8 @@ { "name": "json", "readableName": "JSON", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 0 } diff --git a/parsers/strip/strip.hni b/parsers/strip/strip.hni index 2edce96e..98c81b0b 100644 --- a/parsers/strip/strip.hni +++ b/parsers/strip/strip.hni @@ -2,7 +2,8 @@ { "name": "strip", "readableName": "Strip", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 0 } diff --git a/parsers/xml/xml.hni b/parsers/xml/xml.hni index 771d4643..1d395609 100644 --- a/parsers/xml/xml.hni +++ b/parsers/xml/xml.hni @@ -2,7 +2,8 @@ { "name": "xml", "readableName": "XML", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 0 } diff --git a/passthrough/passthrough.hni b/passthrough/passthrough.hni index 304e6310..2fb5d953 100644 --- a/passthrough/passthrough.hni +++ b/passthrough/passthrough.hni @@ -2,7 +2,8 @@ { "name": "passthrough", "readableName": "Passthrough", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 0 } diff --git a/php-test/php-test.hni b/php-test/php-test.hni index 6d22cef4..a1e43182 100644 --- a/php-test/php-test.hni +++ b/php-test/php-test.hni @@ -2,7 +2,8 @@ { "name": "php-test", "readableName": "php-test", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 1 } diff --git a/ping/MyNode.cpp b/ping/MyNode.cpp index 62375150..3558b431 100644 --- a/ping/MyNode.cpp +++ b/ping/MyNode.cpp @@ -27,8 +27,9 @@ * files in the program, then also delete it here. */ -#include #include "MyNode.h" +#include +#include namespace MyNode { @@ -61,10 +62,6 @@ bool MyNode::init(Flows::PNodeInfo info) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); } - catch(...) - { - _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); - } return false; } @@ -73,7 +70,6 @@ bool MyNode::start() try { std::lock_guard workerGuard(_workerThreadMutex); - //_statusLast = getNodeData("statusLast"); _stopThread = true; if (_workerThread.joinable())_workerThread.join(); _stopThread = false; @@ -84,10 +80,6 @@ bool MyNode::start() { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); } - catch(...) - { - _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); - } return false; } @@ -96,17 +88,12 @@ void MyNode::stop() try { std::lock_guard workerGuard(_workerThreadMutex); - //setNodeData("statusLast", _statusLast); _stopThread = true; } catch(const std::exception& ex) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); } - catch(...) - { - _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); - } } void MyNode::waitForStop() @@ -121,79 +108,45 @@ void MyNode::waitForStop() { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); } - catch(...) - { - _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); - } +} + +int32_t MyNode::getMaxFd() +{ + struct rlimit limits{}; + if(getrlimit(RLIMIT_NOFILE, &limits) == -1 || limits.rlim_cur >= INT32_MAX) + { + return 1024; + } + return limits.rlim_cur; } void MyNode::worker() { try { - int64_t cycleEndTime = 0; - int64_t cycleTime = 0; - int64_t cycleStartTime = Flows::HelperFunctions::getTime(); int64_t timeToSleep = 1000 * _interval; - int64_t sleepingTime = timeToSleep; + int64_t sleepTo = Flows::HelperFunctions::getTime() + timeToSleep; while (!_stopThread) { - if (sleepingTime >= timeToSleep) + if (Flows::HelperFunctions::getTime() >= sleepTo && _enabled) { - sleepingTime -= timeToSleep; - bool reachable = false; - + sleepTo = Flows::HelperFunctions::getTime() + timeToSleep; + std::string pingOutput; - std::string command = "ping -c 1 " + _host; - //_out->printInfo("Ping start for host " + _host); - - FILE* pipe = popen(command.c_str(), "r"); - if (pipe) - { - char buffer[128]; - int32_t bytesRead = 0; - pingOutput.reserve(1024); - while(!feof(pipe)) - { - if(fgets(buffer, 128, pipe) != 0) - { - if(pingOutput.size() + bytesRead > pingOutput.capacity()) pingOutput.reserve(pingOutput.capacity() + 1024); - pingOutput.insert(pingOutput.end(), buffer, buffer + strlen(buffer)); - } - } - pclose(pipe); - //_out->printInfo(pingOutput); - std::string findstr = "1 received"; - std::size_t found = pingOutput.find(findstr); - reachable = (found!=std::string::npos); - //_out->printInfo("reachable: " + std::to_string(reachable)); - } - else - _out->printInfo("no pipe"); - - if (reachable != _statusLast) - { - Flows::PVariable outputMessage = std::make_shared(Flows::VariableType::tStruct); - outputMessage->structValue->emplace("payload", std::make_shared(reachable)); - output(0, outputMessage); - _statusLast = reachable; - } + auto result = BaseLib::ProcessManager::exec("/bin/ping -c 1 " + _host, getMaxFd(), pingOutput); + bool reachable = (result == 0); + + Flows::PVariable outputMessage = std::make_shared(Flows::VariableType::tStruct); + outputMessage->structValue->emplace("payload", std::make_shared(reachable)); + output(0, outputMessage); } - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - cycleEndTime = Flows::HelperFunctions::getTime(); - cycleTime = cycleEndTime - cycleStartTime; - sleepingTime += cycleTime; - cycleStartTime = cycleEndTime; + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); } } catch(const std::exception& ex) { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); } - catch(...) - { - _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); - } } void MyNode::input(Flows::PNodeInfo info, uint32_t index, Flows::PVariable message) @@ -207,10 +160,6 @@ void MyNode::input(Flows::PNodeInfo info, uint32_t index, Flows::PVariable messa { _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); } - catch(...) - { - _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); - } } } diff --git a/ping/MyNode.h b/ping/MyNode.h index e686a658..e7329bcb 100644 --- a/ping/MyNode.h +++ b/ping/MyNode.h @@ -53,12 +53,12 @@ class MyNode: public Flows::INode std::string _host = ""; std::atomic_bool _enabled; - std::atomic_bool _statusLast; std::atomic_bool _stopThread; std::mutex _workerThreadMutex; std::thread _workerThread; void worker(); + int32_t getMaxFd(); virtual void input(Flows::PNodeInfo info, uint32_t index, Flows::PVariable message); }; diff --git a/ping/locales/en-US/ping b/ping/locales/en-US/ping index 1e06963a..0140f1cb 100644 --- a/ping/locales/en-US/ping +++ b/ping/locales/en-US/ping @@ -4,7 +4,7 @@ "label": { "ping": "ping", "host": "Host address", - "interval": "Interval between pings in s", + "interval": "Interval", "name": "Name" }, "input1Description": "Enables or disables pinging.", diff --git a/ping/ping.hni b/ping/ping.hni index c3328498..61f96bb3 100644 --- a/ping/ping.hni +++ b/ping/ping.hni @@ -2,22 +2,24 @@ { "name": "ping", "readableName": "ping", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 1 } @@ -36,6 +37,26 @@ +
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
diff --git a/pulsecounter/pulsecounter.hni b/pulsecounter/pulsecounter.hni index c49d9a61..3a1beb42 100644 --- a/pulsecounter/pulsecounter.hni +++ b/pulsecounter/pulsecounter.hni @@ -2,7 +2,8 @@ { "name": "pulsecounter", "readableName": "pulsecounter", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 1 } diff --git a/python/Factory.cpp b/python/Factory.cpp new file mode 100644 index 00000000..eee9ca53 --- /dev/null +++ b/python/Factory.cpp @@ -0,0 +1,42 @@ +/* Copyright 2013-2019 Homegear GmbH + * + * Homegear is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Homegear is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Homegear. If not, see . + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you + * do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source + * files in the program, then also delete it here. + */ + +#include "Factory.h" +#include "Python.h" +#include "../config.h" + +Flows::INode* MyFactory::createNode(std::string path, std::string nodeNamespace, std::string type, const std::atomic_bool* frontendConnected) +{ + return new Python::Python(path, nodeNamespace, type, frontendConnected); +} + +Flows::NodeFactory* getFactory() +{ + return (Flows::NodeFactory*) (new MyFactory); +} diff --git a/python/Factory.h b/python/Factory.h new file mode 100644 index 00000000..317c3b37 --- /dev/null +++ b/python/Factory.h @@ -0,0 +1,43 @@ +/* Copyright 2013-2019 Homegear GmbH + * + * Homegear is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Homegear is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Homegear. If not, see . + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you + * do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source + * files in the program, then also delete it here. + */ + +#ifndef FACTORY_H +#define FACTORY_H + +#include + +class MyFactory : Flows::NodeFactory +{ +public: + virtual Flows::INode* createNode(std::string path, std::string nodeNamespace, std::string type, const std::atomic_bool* frontendConnected); +}; + +extern "C" Flows::NodeFactory* getFactory(); + +#endif diff --git a/python/Makefile.am b/python/Makefile.am new file mode 100644 index 00000000..2df4b283 --- /dev/null +++ b/python/Makefile.am @@ -0,0 +1,16 @@ +AUTOMAKE_OPTIONS = subdir-objects + +AM_CPPFLAGS = -Wall -std=c++11 -DFORTIFY_SOURCE=2 -DGCRYPT_NO_DEPRECATED +AM_LDFLAGS = -Wl,-rpath=/lib/homegear -Wl,-rpath=/usr/lib/homegear -Wl,-rpath=/usr/local/lib/homegear +LIBS += -Wl,-Bdynamic -lhomegear-node -lhomegear-base + +libdir = $(localstatedir)/lib/homegear/node-blue/nodes/python +lib_LTLIBRARIES = python.la +python_la_SOURCES = Factory.cpp Python.cpp +python_la_LDFLAGS =-module -avoid-version -shared +python_ladir = $(libdir) +python_la_DATA = python.hni +locale_en_usdir = $(libdir)/locales/en-US +locale_en_us_DATA = locales/en-US/python +install-python-hook: + rm -f $(DESTDIR)$(libdir)/python.la diff --git a/python/Python.cpp b/python/Python.cpp new file mode 100644 index 00000000..a3b596db --- /dev/null +++ b/python/Python.cpp @@ -0,0 +1,540 @@ +/* Copyright 2013-2019 Homegear GmbH + * + * Homegear is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Homegear is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Homegear. If not, see . + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you + * do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source + * files in the program, then also delete it here. + */ + +#include "Python.h" +#include "homegear-base/Managers/ProcessManager.h" +#include +#include + +namespace Python +{ + +Python::Python(std::string path, std::string nodeNamespace, std::string type, const std::atomic_bool* frontendConnected) : Flows::INode(path, nodeNamespace, type, frontendConnected) +{ +} + +Python::~Python() +{ + _stopThread = true; + if(_pid != -1) kill(_pid, 9); + if(_execThread.joinable()) _execThread.join(); + if(_errorThread.joinable()) _errorThread.join(); + if(_callbackHandlerId != -1) BaseLib::ProcessManager::unregisterCallbackHandler(_callbackHandlerId); +} + +bool Python::init(Flows::PNodeInfo info) +{ + try + { + auto settingsIterator = info->info->structValue->find("func"); + std::string code; + if(settingsIterator != info->info->structValue->end()) code = settingsIterator->second->stringValue; + + _codeFile = "/tmp/node-blue-node-" + _id + ".py"; + BaseLib::Io::writeFile(_codeFile, code); + if(chmod(_codeFile.c_str(), S_IRUSR | S_IWUSR) == -1) _out->printError("Error: Could not set permissions on " + _codeFile); + + return true; + } + catch(const std::exception& ex) + { + _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); + } + return false; +} + +bool Python::start() +{ + try + { + _callbackHandlerId = BaseLib::ProcessManager::registerCallbackHandler(std::function(std::bind(&Python::sigchildHandler, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4))); + + startProgram(); + + { + while(!_startUpError) + { + if(_processStartUpComplete) break; + + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + } + + if(_startUpError) return false; + + return true; + } + catch(const std::exception& ex) + { + _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); + } + return false; +} + +void Python::startUpComplete() +{ + try + { + if(_pid == -1) return; + + callStartUpComplete(); + + _startUpComplete = true; + } + catch(const std::exception& ex) + { + _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); + } +} + +void Python::stop() +{ + try + { + _processStartUpComplete = false; + _stopThread = true; + if(_pid != -1) kill(_pid, 15); + } + catch(const std::exception& ex) + { + _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); + } +} + +void Python::waitForStop() +{ + try + { + if(_pid != -1) kill(_pid, 15); + for(int32_t i = 0; i < 600; i++) + { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + if(_pid == -1) break; + } + if(_pid != -1) + { + _out->printError("Error: Process did not finish within 60 seconds. Killing it."); + kill(_pid, 9); + close(_stdIn); + close(_stdOut); + close(_stdErr); + _stdIn = -1; + _stdOut = -1; + _stdErr = -1; + } + if(_execThread.joinable()) _execThread.join(); + if(_errorThread.joinable()) _errorThread.join(); + BaseLib::ProcessManager::unregisterCallbackHandler(_callbackHandlerId); + _callbackHandlerId = -1; + + BaseLib::Io::deleteFile(_codeFile); + } + catch(const std::exception& ex) + { + _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); + } +} + +void Python::input(const Flows::PNodeInfo info, uint32_t index, const Flows::PVariable message) +{ + try + { + if(index == 0) + { + if(_pid != -1) kill(_pid, message->structValue->at("payload")->integerValue64); + } + else if(index == 1) + { + if(_stdIn != -1) + { + size_t totalBytesWritten = 0; + while(totalBytesWritten < message->structValue->at("payload")->stringValue.size()) + { + int bytesWritten = -1; + do + { + bytesWritten = write(_stdIn, message->structValue->at("payload")->stringValue.data(), message->structValue->at("payload")->stringValue.size()); + } while(bytesWritten == -1 && (errno == EAGAIN || errno == EINTR)); + if(bytesWritten <= 0) + { + _out->printWarning("Warning: Could not write to STDIN: " + std::string(strerror(errno))); + break; + } + totalBytesWritten += bytesWritten; + } + } + } + else + { + auto nodeInputParameters = std::make_shared(); + nodeInputParameters->reserve(3); + nodeInputParameters->emplace_back(info->serialize()); + nodeInputParameters->emplace_back(std::make_shared(index)); + nodeInputParameters->emplace_back(message); + + auto parameters = std::make_shared(); + parameters->reserve(3); + parameters->emplace_back(std::make_shared(_pid)); + parameters->emplace_back(std::make_shared("nodeInput")); + parameters->emplace_back(std::make_shared(nodeInputParameters)); + auto result = invoke("invokeIpcProcessMethod", parameters); + if(result->errorStruct) _out->printError("Error calling nodeInput: " + result->structValue->at("faultString")->stringValue); + } + } + catch(const std::exception& ex) + { + _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); + } +} + +void Python::startProgram() +{ + try + { + if(_execThread.joinable()) _execThread.join(); + if(_errorThread.joinable()) _errorThread.join(); + _execThread = std::thread(&Python::execThread, this); + } + catch(const std::exception& ex) + { + _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); + } +} + +int32_t Python::getMaxFd() +{ + struct rlimit limits{}; + if(getrlimit(RLIMIT_NOFILE, &limits) == -1 || limits.rlim_cur >= INT32_MAX) + { + return 1024; + } + return limits.rlim_cur; +} + +void Python::execThread() +{ + try + { + do + { + if(_stdErr != -1) + { + close(_stdErr); + _stdErr = -1; + } + if(_errorThread.joinable()) _errorThread.join(); + + _processStartUpComplete = false; + + std::string pythonExecutablePath; + + { //Get Python path + std::string pathEnv = getenv("PATH"); + auto paths = BaseLib::HelperFunctions::splitAll(pathEnv, ':'); + for(auto& path : paths) + { + if(path.empty()) continue; + if(path.back() != '/') path.push_back('/'); + if(BaseLib::Io::fileExists(path + "python3")) + { + pythonExecutablePath = path + "python3"; + break; + } + } + + if(pythonExecutablePath.empty()) + { + for(auto& path : paths) + { + if(path.empty()) continue; + if(path.back() != '/') path.push_back('/'); + if(BaseLib::Io::fileExists(path + "python")) + { + pythonExecutablePath = path + "python"; + break; + } + } + } + } + + if(pythonExecutablePath.empty()) + { + if(BaseLib::Io::fileExists("/usr/bin/python3")) pythonExecutablePath = "/usr/bin/python3"; + else if(BaseLib::Io::fileExists("/usr/local/bin/python3")) pythonExecutablePath = "/usr/local/bin/python3"; + else + { + _startUpError = true; + _out->printError("Error: No Python executable found. Please install Python."); + return; + } + } + + std::string socketPath; + { + auto socketPathParameters = std::make_shared(); + socketPathParameters->emplace_back(std::make_shared("socketPath")); + auto result = invoke("getSystemVariable", socketPathParameters); + socketPath = result->stringValue; + } + if(socketPath.empty()) + { + _startUpError = true; + _out->printError("Error: Could not get socket path."); + return; + } + BaseLib::HelperFunctions::stringReplace(socketPath, "\"", "\\\""); + socketPath = "\"" + socketPath + "homegearIPC.sock" + "\""; + + std::vector arguments; + arguments.emplace_back(_codeFile); + arguments.emplace_back(socketPath); + arguments.emplace_back(_id); + + int stdIn = -1; + int stdOut = -1; + int stdErr = -1; + _pid = BaseLib::ProcessManager::systemp(pythonExecutablePath, arguments, getMaxFd(), stdIn, stdOut, stdErr); + _stdIn = stdIn; + _stdOut = stdOut; + _stdErr = stdErr; + + _errorThread = std::thread(&Python::errorThread, this); + + while(_pid != -1) //While process is running + { + auto parameters = std::make_shared(); + parameters->reserve(3); + parameters->emplace_back(std::make_shared(_pid)); + parameters->emplace_back(std::make_shared("ping")); + parameters->emplace_back(std::make_shared(Flows::VariableType::tArray)); + auto result = invoke("invokeIpcProcessMethod", parameters); + if(!result->errorStruct) break; //Process started and responding + + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + if(_pid == -1) _startUpError = true; + else _processStartUpComplete = true; + if(_startUpComplete) callStartUpComplete(); + + std::array buffer{}; + std::string bufferOut; + while(_stdOut != -1) + { + auto bytesRead = 0; + bufferOut.clear(); + do + { + bytesRead = read(_stdOut, buffer.data(), buffer.size()); + if(bytesRead > 0) + { + bufferOut.insert(bufferOut.end(), buffer.begin(), buffer.begin() + bytesRead); + } + } while(bytesRead > 0); + + if(!bufferOut.empty()) + { + auto outputVector = BaseLib::HelperFunctions::splitAll(bufferOut, '\n'); + + Flows::PVariable message = std::make_shared(Flows::VariableType::tStruct); + Flows::PVariable outputArray = std::make_shared(Flows::VariableType::tArray); + outputArray->arrayValue->reserve(outputVector.size()); + for(int32_t i = 0; i < (signed)outputVector.size(); i++) + { + if(i == (signed)outputVector.size() - 1 && outputVector[i].empty()) continue; + outputArray->arrayValue->emplace_back(std::make_shared(std::move(outputVector[i]))); + } + message->structValue->emplace("payload", outputArray); + output(1, message); + } + } + + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + while(!_stopThread); + } + catch(const std::exception& ex) + { + _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); + } +} + +void Python::errorThread() +{ + try + { + std::array buffer{}; + std::string bufferErr; + while(_stdErr != -1) + { + int32_t bytesRead = 0; + bufferErr.clear(); + do + { + bytesRead = read(_stdErr, buffer.data(), buffer.size()); + if(bytesRead > 0) + { + bufferErr.insert(bufferErr.end(), buffer.begin(), buffer.begin() + bytesRead); + } + } while(bytesRead > 0); + + if(!bufferErr.empty()) + { + _out->printError("Process error output: " + bufferErr); + + auto outputVector = BaseLib::HelperFunctions::splitAll(bufferErr, '\n'); + + Flows::PVariable message = std::make_shared(Flows::VariableType::tStruct); + Flows::PVariable outputArray = std::make_shared(Flows::VariableType::tArray); + outputArray->arrayValue->reserve(outputVector.size()); + for(int32_t i = 0; i < (signed)outputVector.size(); i++) + { + if(i == (signed)outputVector.size() - 1 && outputVector[i].empty()) continue; + outputArray->arrayValue->emplace_back(std::make_shared(std::move(outputVector[i]))); + } + message->structValue->emplace("payload", outputArray); + output(2, message); + } + } + } + catch(const std::exception& ex) + { + _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); + } +} + +void Python::sigchildHandler(pid_t pid, int exitCode, int signal, bool coreDumped) +{ + try + { + if(pid == _pid) + { + close(_stdIn); + close(_stdOut); + close(_stdErr); + _stdIn = -1; + _stdOut = -1; + _stdErr = -1; + _pid = -1; + Flows::PVariable message = std::make_shared(Flows::VariableType::tStruct); + message->structValue->emplace("coreDumped", std::make_shared(coreDumped)); + message->structValue->emplace("signal", std::make_shared(signal)); + message->structValue->emplace("payload", std::make_shared(exitCode)); + output(0, message); + } + } + catch(const std::exception& ex) + { + _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); + } +} + +int32_t Python::read(std::atomic_int& fd, uint8_t* buffer, int32_t bufferSize) +{ + if(fd == -1) return 0; + + timeval timeout{}; + timeout.tv_sec = 0; + timeout.tv_usec = 100000; + fd_set readFileDescriptor{}; + FD_ZERO(&readFileDescriptor); + int32_t nfds = fd + 1; + if(nfds <= 0) + { + close(fd); + fd = -1; + return -1; + } + FD_SET(fd, &readFileDescriptor); + auto bytesRead = select(nfds, &readFileDescriptor, nullptr, nullptr, &timeout); + if(bytesRead == 0) + { + return 0; + } + if(bytesRead != 1) + { + close(fd); + fd = -1; + return -1; + } + do + { + bytesRead = ::read(fd, buffer, bufferSize); + } while(bytesRead < 0 && (errno == EAGAIN || errno == EINTR)); + if(bytesRead <= 0) + { + if(bytesRead == -1) + { + if(errno == ETIMEDOUT) return 0; + else + { + close(fd); + fd = -1; + return -1; + } + } + else + { + close(fd); + fd = -1; + return -1; + } + } + if(bytesRead > bufferSize) bytesRead = bufferSize; + return bytesRead; +} + +void Python::callStartUpComplete() +{ + try + { + auto innerParameters = std::make_shared(); + innerParameters->reserve(5); + innerParameters->emplace_back(std::make_shared("nodeBlue")); + innerParameters->emplace_back(std::make_shared(0)); + innerParameters->emplace_back(std::make_shared(-1)); + innerParameters->emplace_back(std::make_shared(Flows::VariableType::tArray)); + innerParameters->emplace_back(std::make_shared(Flows::VariableType::tArray)); + innerParameters->at(3)->arrayValue->emplace_back(std::make_shared("startUpComplete")); + innerParameters->at(4)->arrayValue->emplace_back(std::make_shared(true)); + + auto parameters = std::make_shared(); + parameters->reserve(3); + parameters->emplace_back(std::make_shared(_pid)); + parameters->emplace_back(std::make_shared("broadcastEvent")); + parameters->emplace_back(std::make_shared(innerParameters)); + auto result = invoke("invokeIpcProcessMethod", parameters); + if(result->errorStruct) _out->printError("Error calling broadcastEvent on process: " + result->structValue->at("faultString")->stringValue); + } + catch(const std::exception& ex) + { + _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); + } +} + +} diff --git a/python/Python.h b/python/Python.h new file mode 100644 index 00000000..c2ec3c49 --- /dev/null +++ b/python/Python.h @@ -0,0 +1,77 @@ +/* Copyright 2013-2019 Homegear GmbH + * + * Homegear is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Homegear is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Homegear. If not, see . + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you + * do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source + * files in the program, then also delete it here. + */ + +#ifndef RUNSCRIPT_H_ +#define RUNSCRIPT_H_ + +#include +#include +#include + +namespace Python +{ + +class Python : public Flows::INode +{ +public: + Python(std::string path, std::string nodeNamespace, std::string type, const std::atomic_bool* frontendConnected); + ~Python() override; + + bool init(Flows::PNodeInfo info) override; + bool start() override; + void startUpComplete() override; + void stop() override; + void waitForStop() override; +private: + std::atomic_bool _startUpError{false}; + std::atomic_bool _startUpComplete{false}; + std::atomic_bool _processStartUpComplete{false}; + int32_t _callbackHandlerId = -1; + std::string _codeFile; + std::atomic_bool _stopThread{false}; + std::thread _execThread; + std::thread _errorThread; + std::atomic_int _pid{-1}; + std::atomic_int _stdIn{-1}; + std::atomic_int _stdOut{-1}; + std::atomic_int _stdErr{-1}; + + void input(const Flows::PNodeInfo info, uint32_t index, const Flows::PVariable message) override; + void startProgram(); + int32_t getMaxFd(); + void sigchildHandler(pid_t pid, int exitCode, int signal, bool coreDumped); + void execThread(); + void errorThread(); + int32_t read(std::atomic_int& fd, uint8_t* buffer, int32_t bufferSize); + void callStartUpComplete(); +}; + +} + +#endif diff --git a/python/locales/en-US/python b/python/locales/en-US/python new file mode 100644 index 00000000..9e5c641c --- /dev/null +++ b/python/locales/en-US/python @@ -0,0 +1,20 @@ +{ + "python/python.hni": { + "python": { + "label": { + "name": "Name", + "function": "Function", + "inputs": "Inputs", + "outputs": "Outputs" + }, + "tip": "Tip: See the Info tab for help.", + "paletteHelp": "

A node to write Python code to do more interesting things.

", + "help": "

A node to write Python code to do more interesting things. The node starts a new Python process executing the code. The process is continuously running. It communicates with Homegear using IPC.

When a message arrives, the node input callback method is called.

The message is passed in as a Python dictionary. The dictionary always contains at least the entry payload. Information about the node can be found in the dictionary nodeInfo. The input index is passed in inputIndex

Using the Homegear object hg you can directly execute RPC methods. E. g. hg.setValue(...).

Data storage

Data can be stored by calling hg.setNodeData(key, value), hg.setFlowData(key, value) or hg.setGlobalData(key, value) and retrieved with hg.getNodeData(key), hg.getFlowData(key) or hg.getGlobalData(key).

Sending messages

To send message to an output, you can call hg.nodeOutput(outputIndex, message).

", + "input1Description": "This signal is passed to the running Python instance.", + "input2Description": "This input is sent to stdin of the Python instance.", + "output1Description": "Outputs the exit code and some additional information when the Python process finishes.", + "output2Description": "stdout of the Python instance.", + "output3Description": "stderr of the Python instance." + } + } +} diff --git a/python/python.hni b/python/python.hni new file mode 100644 index 00000000..d64cae1f --- /dev/null +++ b/python/python.hni @@ -0,0 +1,139 @@ + + + + diff --git a/runscript/Makefile.am b/runscript/Makefile.am deleted file mode 100644 index 00887b0f..00000000 --- a/runscript/Makefile.am +++ /dev/null @@ -1,16 +0,0 @@ -AUTOMAKE_OPTIONS = subdir-objects - -AM_CPPFLAGS = -Wall -std=c++11 -DFORTIFY_SOURCE=2 -DGCRYPT_NO_DEPRECATED -AM_LDFLAGS = -Wl,-rpath=/lib/homegear -Wl,-rpath=/usr/lib/homegear -Wl,-rpath=/usr/local/lib/homegear -LIBS += -Wl,-Bdynamic -lhomegear-node -lhomegear-base - -libdir = $(localstatedir)/lib/homegear/node-blue/nodes/runscript -lib_LTLIBRARIES = runscript.la -runscript_la_SOURCES = Factory.cpp RunScript.cpp -runscript_la_LDFLAGS =-module -avoid-version -shared -runscript_ladir = $(libdir) -runscript_la_DATA = runscript.hni -locale_en_usdir = $(libdir)/locales/en-US -locale_en_us_DATA = locales/en-US/runscript -install-exec-hook: - rm -f $(DESTDIR)$(libdir)/runscript.la diff --git a/runscript/locales/en-US/runscript b/runscript/locales/en-US/runscript deleted file mode 100644 index 90b34158..00000000 --- a/runscript/locales/en-US/runscript +++ /dev/null @@ -1,16 +0,0 @@ -{ - "runscript/runscript.hni": { - "runscript": { - "label": { - "filename": "Filename", - "browse": "Browse", - "name": "Name" - }, - "paletteHelp": "

This node passes a value to the output depending on a boolean input.

", - "help": "

This node checks the boolean state of input EN. If the state is true the value at input v is passed to the output.

", - "input1Description": "The value to pass through.", - "input2Description": "Set to true to pass the value at input v through.", - "output1Description": "Outputs the value at input v unchanged." - } - } -} \ No newline at end of file diff --git a/runscript/runscript.hni b/runscript/runscript.hni deleted file mode 100644 index 670163cb..00000000 --- a/runscript/runscript.hni +++ /dev/null @@ -1,51 +0,0 @@ - - - diff --git a/serial/serial-in/serial-in.hni b/serial/serial-in/serial-in.hni index c18aec74..460bb7a3 100644 --- a/serial/serial-in/serial-in.hni +++ b/serial/serial-in/serial-in.hni @@ -2,7 +2,8 @@ { "name": "serial-in", "readableName": "Serial input", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 0 } diff --git a/serial/serial-out/serial-out.hni b/serial/serial-out/serial-out.hni index 1a1d6d9e..4797a3de 100644 --- a/serial/serial-out/serial-out.hni +++ b/serial/serial-out/serial-out.hni @@ -2,7 +2,8 @@ { "name": "serial-out", "readableName": "Serial output", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 0 } diff --git a/serial/serial-port/serial-port.hni b/serial/serial-port/serial-port.hni index 4a101c81..58b805c5 100644 --- a/serial/serial-port/serial-port.hni +++ b/serial/serial-port/serial-port.hni @@ -2,7 +2,8 @@ { "name": "serial-port", "readableName": "Serial port", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 1 } diff --git a/storage/file/file.hni b/storage/file/file.hni index 226179bf..fdb5d6ba 100644 --- a/storage/file/file.hni +++ b/storage/file/file.hni @@ -2,7 +2,8 @@ { "name": "file", "readableName": "File", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 0 } diff --git a/synchronous/synchronous.hni b/synchronous/synchronous.hni index a926f168..299d6fe6 100644 --- a/synchronous/synchronous.hni +++ b/synchronous/synchronous.hni @@ -2,7 +2,8 @@ { "name": "synchronous", "readableName": "Synchronous", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 0 } diff --git a/template/template.hni b/template/template.hni index 03bf315a..d319125b 100644 --- a/template/template.hni +++ b/template/template.hni @@ -2,7 +2,8 @@ { "name": "template", "readableName": "Template", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 0 } diff --git a/timers/Makefile.am b/timers/Makefile.am index 06f636cc..30f6b35b 100644 --- a/timers/Makefile.am +++ b/timers/Makefile.am @@ -6,7 +6,7 @@ LIBS += -Wl,-Bdynamic -lhomegear-node libdir = $(localstatedir)/lib/homegear/node-blue/nodes/timers -lib_LTLIBRARIES = clock.la delay.la impulse.la interval.la off-delay.la on-delay.la timer.la slow-pwm.la sun-position.la +lib_LTLIBRARIES = clock.la delay.la impulse.la interval.la off-delay.la on-delay.la rate-limiter.la timer.la slow-pwm.la sun-position.la clock_la_SOURCES = clock/Factory.cpp clock/MyNode.cpp clock_la_LDFLAGS =-module -avoid-version -shared @@ -26,6 +26,9 @@ off_delay_la_LDFLAGS =-module -avoid-version -shared on_delay_la_SOURCES = on-delay/Factory.cpp on-delay/MyNode.cpp on_delay_la_LDFLAGS =-module -avoid-version -shared +rate_limiter_la_SOURCES = rate-limiter/Factory.cpp rate-limiter/MyNode.cpp +rate_limiter_la_LDFLAGS =-module -avoid-version -shared + timer_la_SOURCES = timer/Factory.cpp timer/MyNode.cpp timer/SunTime.cpp timer_la_LDFLAGS =-module -avoid-version -shared @@ -36,9 +39,9 @@ sun_position_la_SOURCES = sun-position/Factory.cpp sun-position/MyNode.cpp sun-p sun_position_la_LDFLAGS =-module -avoid-version -shared timers_ladir = $(libdir) -timers_la_DATA = clock/clock.hni delay/delay.hni impulse/impulse.hni interval/interval.hni off-delay/off-delay.hni on-delay/on-delay.hni timer/timer.hni slow-pwm/slow-pwm.hni sun-position/sun-position.hni +timers_la_DATA = clock/clock.hni delay/delay.hni impulse/impulse.hni interval/interval.hni off-delay/off-delay.hni on-delay/on-delay.hni rate-limiter/rate-limiter.hni timer/timer.hni slow-pwm/slow-pwm.hni sun-position/sun-position.hni locale_en_usdir = $(libdir)/locales/en-US -locale_en_us_DATA = clock/locales/en-US/clock delay/locales/en-US/delay impulse/locales/en-US/impulse interval/locales/en-US/interval off-delay/locales/en-US/off-delay on-delay/locales/en-US/on-delay timer/locales/en-US/timer slow-pwm/locales/en-US/slow-pwm sun-position/locales/en-US/sun-position +locale_en_us_DATA = clock/locales/en-US/clock delay/locales/en-US/delay impulse/locales/en-US/impulse interval/locales/en-US/interval off-delay/locales/en-US/off-delay on-delay/locales/en-US/on-delay rate-limiter/locales/en-US/rate-limiter timer/locales/en-US/timer slow-pwm/locales/en-US/slow-pwm sun-position/locales/en-US/sun-position install-exec-hook: rm -f $(DESTDIR)$(libdir)/clock.la @@ -47,6 +50,7 @@ install-exec-hook: rm -f $(DESTDIR)$(libdir)/interval.la rm -f $(DESTDIR)$(libdir)/off-delay.la rm -f $(DESTDIR)$(libdir)/on-delay.la + rm -f $(DESTDIR)$(libdir)/rate-limiter.la rm -f $(DESTDIR)$(libdir)/timer.la rm -f $(DESTDIR)$(libdir)/slow-pwm.la rm -f $(DESTDIR)$(libdir)/sun-position.la diff --git a/timers/clock/clock.hni b/timers/clock/clock.hni index 3eb9cb7b..775d6987 100644 --- a/timers/clock/clock.hni +++ b/timers/clock/clock.hni @@ -2,7 +2,8 @@ { "name": "clock", "readableName": "Clock", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 1 } diff --git a/timers/delay/delay.hni b/timers/delay/delay.hni index 52c10c83..feaf4e3d 100644 --- a/timers/delay/delay.hni +++ b/timers/delay/delay.hni @@ -2,7 +2,8 @@ { "name": "delay", "readableName": "Delay", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 10 } diff --git a/timers/impulse/impulse.hni b/timers/impulse/impulse.hni index 140bea60..17f7481e 100644 --- a/timers/impulse/impulse.hni +++ b/timers/impulse/impulse.hni @@ -2,7 +2,8 @@ { "name": "impulse", "readableName": "Impulse", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 1 } @@ -19,7 +20,7 @@
- +
diff --git a/timers/off-delay/MyNode.cpp b/timers/off-delay/MyNode.cpp index b040b5fe..36a52dcd 100644 --- a/timers/off-delay/MyNode.cpp +++ b/timers/off-delay/MyNode.cpp @@ -141,7 +141,7 @@ void MyNode::timer(int64_t delayTo) if(_delay >= 1000) sleepingTime = 100; else if(_delay >= 30000) sleepingTime = 1000; - while (restTime > 0) + while(restTime > 0) { if ((restTime / sleepingTime) % (1000 / sleepingTime) == 0) { @@ -152,10 +152,6 @@ void MyNode::timer(int64_t delayTo) std::this_thread::sleep_for(std::chrono::milliseconds(sleepingTime)); if(_stopThread) { - Flows::PVariable outputMessage3 = std::make_shared(Flows::VariableType::tStruct); - outputMessage3->structValue->emplace("payload", std::make_shared(-1)); - output(1, outputMessage3); //rest time - setNodeData("delayTo", std::make_shared(0)); _threadRunning = false; return; } diff --git a/timers/off-delay/off-delay.hni b/timers/off-delay/off-delay.hni index 4d9c56a5..91da631d 100644 --- a/timers/off-delay/off-delay.hni +++ b/timers/off-delay/off-delay.hni @@ -2,7 +2,8 @@ { "name": "off-delay", "readableName": "Off-delay", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 1 } diff --git a/timers/on-delay/MyNode.cpp b/timers/on-delay/MyNode.cpp index f1120974..3cd3d562 100644 --- a/timers/on-delay/MyNode.cpp +++ b/timers/on-delay/MyNode.cpp @@ -152,10 +152,6 @@ void MyNode::timer(int64_t delayTo) std::this_thread::sleep_for(std::chrono::milliseconds(sleepingTime)); if(_stopThread) { - Flows::PVariable outputMessage3 = std::make_shared(Flows::VariableType::tStruct); - outputMessage3->structValue->emplace("payload", std::make_shared(-1)); - output(1, outputMessage3); //rest time - setNodeData("delayTo", std::make_shared(0)); _threadRunning = false; return; } diff --git a/timers/on-delay/on-delay.hni b/timers/on-delay/on-delay.hni index 526b3bcf..cda96b3e 100644 --- a/timers/on-delay/on-delay.hni +++ b/timers/on-delay/on-delay.hni @@ -2,7 +2,8 @@ { "name": "on-delay", "readableName": "on-delay", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 1 } diff --git a/timers/rate-limiter/Factory.cpp b/timers/rate-limiter/Factory.cpp new file mode 100644 index 00000000..842c28e3 --- /dev/null +++ b/timers/rate-limiter/Factory.cpp @@ -0,0 +1,42 @@ +/* Copyright 2013-2019 Homegear GmbH + * + * Homegear is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Homegear is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Homegear. If not, see . + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you + * do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source + * files in the program, then also delete it here. + */ + +#include "Factory.h" +#include "MyNode.h" +#include "../config.h" + +Flows::INode* MyFactory::createNode(std::string path, std::string nodeNamespace, std::string type, const std::atomic_bool* frontendConnected) +{ + return new MyNode::MyNode(path, nodeNamespace, type, frontendConnected); +} + +Flows::NodeFactory* getFactory() +{ + return (Flows::NodeFactory*) (new MyFactory); +} diff --git a/timers/rate-limiter/Factory.h b/timers/rate-limiter/Factory.h new file mode 100644 index 00000000..6fa76bfb --- /dev/null +++ b/timers/rate-limiter/Factory.h @@ -0,0 +1,44 @@ +/* Copyright 2013-2019 Homegear GmbH + * + * Homegear is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Homegear is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Homegear. If not, see . + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you + * do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source + * files in the program, then also delete it here. + */ + +#ifndef FACTORY_H +#define FACTORY_H + +#include +#include "MyNode.h" + +class MyFactory : Flows::NodeFactory +{ +public: + virtual Flows::INode* createNode(std::string path, std::string nodeNamespace, std::string type, const std::atomic_bool* frontendConnected); +}; + +extern "C" Flows::NodeFactory* getFactory(); + +#endif diff --git a/timers/rate-limiter/MyNode.cpp b/timers/rate-limiter/MyNode.cpp new file mode 100644 index 00000000..15f9dd8f --- /dev/null +++ b/timers/rate-limiter/MyNode.cpp @@ -0,0 +1,241 @@ +/* Copyright 2013-2019 Homegear GmbH + * + * Homegear is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Homegear is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Homegear. If not, see . + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you + * do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source + * files in the program, then also delete it here. + */ + +#include +#include "MyNode.h" + +namespace MyNode +{ + +MyNode::MyNode(std::string path, std::string nodeNamespace, std::string type, const std::atomic_bool* frontendConnected) + : Flows::INode(path, nodeNamespace, type, frontendConnected) +{ +} + +MyNode::~MyNode() +{ + _stopThread = true; + waitForStop(); +} + + +bool MyNode::init(Flows::PNodeInfo info) +{ + try + { + auto settingsIterator = info->info->structValue->find("max-inputs"); + if(settingsIterator != info->info->structValue->end()) _maxInputs = Flows::Math::getUnsignedNumber(settingsIterator->second->stringValue); + if(_maxInputs < 1) _maxInputs = 1; + + settingsIterator = info->info->structValue->find("interval"); + if(settingsIterator != info->info->structValue->end()) _interval = Flows::Math::getUnsignedNumber(settingsIterator->second->stringValue); + + settingsIterator = info->info->structValue->find("output-first"); + if(settingsIterator != info->info->structValue->end()) _outputFirst = settingsIterator->second->booleanValue; + + return true; + } + catch(const std::exception& ex) + { + _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); + } + return false; +} + +bool MyNode::start() +{ + try + { + std::lock_guard timerGuard(_timerThreadMutex); + _stopThread = true; + if(_timerThread.joinable()) _timerThread.join(); + _stopThread = false; + _timerThread = std::thread(&MyNode::timer, this); + + return true; + } + catch(const std::exception& ex) + { + _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); + } + return false; +} + +void MyNode::stop() +{ + try + { + std::lock_guard timerGuard(_timerThreadMutex); + _stopThread = true; + } + catch(const std::exception& ex) + { + _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); + } +} + +void MyNode::waitForStop() +{ + try + { + std::lock_guard timerGuard(_timerThreadMutex); + _stopThread = true; + if(_timerThread.joinable()) _timerThread.join(); + } + catch(const std::exception& ex) + { + _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); + } + catch(...) + { + _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); + } +} + +void MyNode::checkLastInput() +{ + if(_inputCount.load(std::memory_order_acquire) == 0) + { + _firstInterval.store(true, std::memory_order_release); + + std::lock_guard lastInputGuard(_lastInputMutex); + if(_lastInput) + { + output(0, _lastInput); + _lastInput.reset(); + } + } +} + +void MyNode::timer() +{ + int32_t sleepingTime = _interval; + if(sleepingTime < 1) sleepingTime = 1; + + int64_t startTime = Flows::HelperFunctions::getTime(); + + while(!_stopThread) + { + try + { + if(sleepingTime > 1000 && sleepingTime < 30000) + { + int32_t iterations = sleepingTime / 100; + for(int32_t j = 0; j < iterations; j++) + { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + if(_stopThread) break; + checkLastInput(); + } + if(sleepingTime % 100) std::this_thread::sleep_for(std::chrono::milliseconds(sleepingTime % 100)); + } + else if(sleepingTime >= 30000) + { + int32_t iterations = sleepingTime / 1000; + for(int32_t j = 0; j < iterations; j++) + { + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + if(_stopThread) break; + checkLastInput(); + } + if(sleepingTime % 1000) std::this_thread::sleep_for(std::chrono::milliseconds(sleepingTime % 1000)); + } + else + { + std::this_thread::sleep_for(std::chrono::milliseconds(sleepingTime)); + if(_stopThread) break; + checkLastInput(); + } + + { + int64_t lastInputTime = _lastInputTime.load(std::memory_order_acquire); + if(lastInputTime != 0) + { + _lastInputTime.store(0, std::memory_order_release); + sleepingTime = _interval - (Flows::HelperFunctions::getTime() - lastInputTime); + if(sleepingTime < 1) sleepingTime = 1; + continue; + } + } + + _inputCount.store(0, std::memory_order_release); + + int64_t diff = Flows::HelperFunctions::getTime() - startTime; + if(diff <= _interval) sleepingTime = _interval; + else sleepingTime = _interval - (diff - _interval); + if(sleepingTime < 1) sleepingTime = 1; + startTime = Flows::HelperFunctions::getTime(); + } + catch(const std::exception& ex) + { + _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); + } + } +} + +void MyNode::input(const Flows::PNodeInfo info, uint32_t index, const Flows::PVariable message) +{ + try + { + if(_firstInterval.load(std::memory_order_acquire)) _lastInputTime.store(Flows::HelperFunctions::getTime(), std::memory_order_release); + if(_inputCount.load(std::memory_order_acquire) < _maxInputs) + { + { + std::lock_guard lastInputGuard(_lastInputMutex); + _lastInput.reset(); + } + + if((!_outputFirst && !_firstInterval.load(std::memory_order_acquire)) || _outputFirst) + { + _inputCount.fetch_add(1, std::memory_order_acq_rel); + output(0, message); + } + else + { + std::lock_guard lastInputGuard(_lastInputMutex); + _inputCount.fetch_add(1, std::memory_order_acq_rel); + _lastInput = message; + } + + _firstInterval.store(false, std::memory_order_release); + } + else + { + std::lock_guard lastInputGuard(_lastInputMutex); + _lastInput = message; + } + } + catch(const std::exception& ex) + { + _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); + } +} + +} + diff --git a/runscript/RunScript.cpp b/timers/rate-limiter/MyNode.h similarity index 56% rename from runscript/RunScript.cpp rename to timers/rate-limiter/MyNode.h index cbedc867..eb41a83a 100644 --- a/runscript/RunScript.cpp +++ b/timers/rate-limiter/MyNode.h @@ -27,56 +27,45 @@ * files in the program, then also delete it here. */ -#include "RunScript.h" +#ifndef MYNODE_H_ +#define MYNODE_H_ -namespace RunScript -{ - -RunScript::RunScript(std::string path, std::string nodeNamespace, std::string type, const std::atomic_bool* frontendConnected) : Flows::INode(path, nodeNamespace, type, frontendConnected) -{ -} +#include +#include +#include -RunScript::~RunScript() +namespace MyNode { -} -bool RunScript::init(Flows::PNodeInfo info) +class MyNode: public Flows::INode { - try - { - auto settingsIterator = info->info->structValue->find("onboolean"); - if(settingsIterator != info->info->structValue->end()) _onBoolean = settingsIterator->second->booleanValue; +public: + MyNode(std::string path, std::string nodeNamespace, std::string type, const std::atomic_bool* frontendConnected); + virtual ~MyNode(); - _input1 = getNodeData("input1"); - _input2 = getNodeData("input2")->booleanValue; + virtual bool init(Flows::PNodeInfo info); + virtual bool start(); + virtual void stop(); + virtual void waitForStop(); +private: + uint32_t _maxInputs = 1; + uint32_t _interval = 1000; + bool _outputFirst = true; - return true; - } - catch(const std::exception& ex) - { - _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); - } - catch(...) - { - _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); - } - return false; -} + std::atomic_bool _firstInterval{true}; + std::atomic_bool _stopThread{true}; + std::mutex _timerThreadMutex; + std::thread _timerThread; -void RunScript::input(const Flows::PNodeInfo info, uint32_t index, const Flows::PVariable message) -{ - try - { + std::mutex _lastInputMutex; + Flows::PVariable _lastInput; + std::atomic _lastInputTime{0}; + std::atomic _inputCount{0}; + void timer(); + void checkLastInput(); + virtual void input(const Flows::PNodeInfo info, uint32_t index, const Flows::PVariable message); +}; - } - catch(const std::exception& ex) - { - _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__, ex.what()); - } - catch(...) - { - _out->printEx(__FILE__, __LINE__, __PRETTY_FUNCTION__); - } } -} +#endif diff --git a/timers/rate-limiter/locales/en-US/rate-limiter b/timers/rate-limiter/locales/en-US/rate-limiter new file mode 100644 index 00000000..0ce642b1 --- /dev/null +++ b/timers/rate-limiter/locales/en-US/rate-limiter @@ -0,0 +1,14 @@ +{ + "timers/rate-limiter.hni": { + "rate-limiter": { + "label": { + "max-inputs": "Maximum no. of messages", + "interval": "Within interval", + "output-first": "Output during first interval", + "name": "Name" + }, + "paletteHelp": "

Rate limits incoming messages.

", + "help": "

This node rate limits incoming messages. You can specify the maximum number of incoming messages that are forwarded within interval. The last message is stored and always forwarded at the end. By unchecking \"Output during first interval\", the node waits for \"interval\" until the first message is output.

" + } + } +} diff --git a/timers/rate-limiter/rate-limiter.hni b/timers/rate-limiter/rate-limiter.hni new file mode 100644 index 00000000..3eac97d3 --- /dev/null +++ b/timers/rate-limiter/rate-limiter.hni @@ -0,0 +1,50 @@ + + + diff --git a/timers/slow-pwm/slow-pwm.hni b/timers/slow-pwm/slow-pwm.hni index f4eba8cf..6fd84649 100644 --- a/timers/slow-pwm/slow-pwm.hni +++ b/timers/slow-pwm/slow-pwm.hni @@ -2,7 +2,8 @@ { "name": "slow-pwm", "readableName": "Slow PWM", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 1 } diff --git a/timers/sun-position/sun-position.hni b/timers/sun-position/sun-position.hni index 900cd254..a2cb9b6e 100644 --- a/timers/sun-position/sun-position.hni +++ b/timers/sun-position/sun-position.hni @@ -2,7 +2,8 @@ { "name": "sun-position", "readableName": "Sun Position", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 1 } diff --git a/timers/timer/timer.hni b/timers/timer/timer.hni index 92893e7c..f22a4f34 100644 --- a/timers/timer/timer.hni +++ b/timers/timer/timer.hni @@ -2,7 +2,8 @@ { "name": "timer", "readableName": "Timer", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 1 } diff --git a/tls-config/tls-config.hni b/tls-config/tls-config.hni index 98c48bcf..cca20eba 100644 --- a/tls-config/tls-config.hni +++ b/tls-config/tls-config.hni @@ -2,7 +2,8 @@ { "name": "tls-config", "readableName": "TLS configuration", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 0 } diff --git a/tls-server-config/tls-server-config.hni b/tls-server-config/tls-server-config.hni index 7c1a6851..a40fbc07 100644 --- a/tls-server-config/tls-server-config.hni +++ b/tls-server-config/tls-server-config.hni @@ -2,7 +2,8 @@ { "name": "tls-server-config", "readableName": "TLS server configuration", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 0 } diff --git a/variable/constant/MyNode.cpp b/variable/constant/MyNode.cpp index 5cc595d5..2af3ef9a 100644 --- a/variable/constant/MyNode.cpp +++ b/variable/constant/MyNode.cpp @@ -121,7 +121,7 @@ void MyNode::startUpComplete() } } -void MyNode::setNodeVariable(std::string variable, Flows::PVariable value) +void MyNode::setNodeVariable(const std::string& variable, Flows::PVariable value) { try { diff --git a/variable/constant/MyNode.h b/variable/constant/MyNode.h index c5b3dba9..7bbe4779 100644 --- a/variable/constant/MyNode.h +++ b/variable/constant/MyNode.h @@ -44,7 +44,7 @@ class MyNode: public Flows::INode virtual bool init(Flows::PNodeInfo info); virtual void startUpComplete(); - virtual void setNodeVariable(std::string variable, Flows::PVariable value); + virtual void setNodeVariable(const std::string& variable, Flows::PVariable value); private: bool _outputOnStartup = true; std::string _payloadType; diff --git a/variable/constant/constant.hni b/variable/constant/constant.hni index 861b5c77..8dc2c4b7 100644 --- a/variable/constant/constant.hni +++ b/variable/constant/constant.hni @@ -2,7 +2,8 @@ { "name": "constant", "readableName": "Constant", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 0 } diff --git a/variable/toggle/MyNode.cpp b/variable/toggle/MyNode.cpp index 7fb43cc0..c3e73285 100644 --- a/variable/toggle/MyNode.cpp +++ b/variable/toggle/MyNode.cpp @@ -48,7 +48,8 @@ bool MyNode::init(Flows::PNodeInfo info) auto settingsIterator = info->info->structValue->find("variabletype"); if(settingsIterator != info->info->structValue->end()) variableType = settingsIterator->second->stringValue; - if(variableType == "device") _variableType = VariableType::device; + if(variableType == "self") _variableType = VariableType::self; + else if(variableType == "device") _variableType = VariableType::device; else if(variableType == "metadata") _variableType = VariableType::metadata; else if(variableType == "system") _variableType = VariableType::system; else if(variableType == "flow") _variableType = VariableType::flow; @@ -66,8 +67,11 @@ bool MyNode::init(Flows::PNodeInfo info) if(settingsIterator != info->info->structValue->end()) _channel = Flows::Math::getNumber(settingsIterator->second->stringValue); } - settingsIterator = info->info->structValue->find("variable"); - if(settingsIterator != info->info->structValue->end()) _variable = settingsIterator->second->stringValue; + if(_variableType != VariableType::self) + { + settingsIterator = info->info->structValue->find("variable"); + if(settingsIterator != info->info->structValue->end()) _variable = settingsIterator->second->stringValue; + } return true; } @@ -86,7 +90,17 @@ void MyNode::input(const Flows::PNodeInfo info, uint32_t index, const Flows::PVa { try { - if(_variableType == VariableType::flow) + if(_variableType == VariableType::self) + { + bool newValue = !((bool)(*getNodeData("value"))); + setNodeData("value", std::make_shared(newValue)); + + Flows::PVariable message = std::make_shared(Flows::VariableType::tStruct); + message->structValue->emplace("payload", std::make_shared(newValue)); + + output(0, message); + } + else if(_variableType == VariableType::flow) { bool newValue = !((bool)(*getFlowData(_variable))); setFlowData(_variable, std::make_shared(newValue)); diff --git a/variable/toggle/MyNode.h b/variable/toggle/MyNode.h index b9c005af..3b2529d7 100644 --- a/variable/toggle/MyNode.h +++ b/variable/toggle/MyNode.h @@ -45,6 +45,7 @@ class MyNode: public Flows::INode private: enum class VariableType { + self, device, metadata, system, diff --git a/variable/toggle/locales/en-US/toggle b/variable/toggle/locales/en-US/toggle index 2025a32d..8b41cb69 100644 --- a/variable/toggle/locales/en-US/toggle +++ b/variable/toggle/locales/en-US/toggle @@ -5,6 +5,7 @@ "variabletype": "Variable type", "devicevariable": "Device variable", "metadata": "Metadata", + "novariable": "None", "systemvariable": "System variable", "flowvariable": "Flow variable", "globalvariable": "Global variable", diff --git a/variable/toggle/toggle.hni b/variable/toggle/toggle.hni index 94b8b144..61dd8c29 100644 --- a/variable/toggle/toggle.hni +++ b/variable/toggle/toggle.hni @@ -2,7 +2,8 @@ { "name": "toggle", "readableName": "Toggle", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 0 } @@ -14,6 +15,7 @@
@@ -67,6 +73,11 @@ +
+ + + +
@@ -93,11 +104,18 @@ eventsource: {value:"all",validate: function(v) { return v != ""; }}, refractoryperiod: {value: "0",validate:RED.validators.number()}, outputonstartup: {value: false}, + "changes-only": {value: false, required: false}, loopprevention: {value: false}, looppreventiongroup: {type:"variable-loop-prevention-group", required: false}, name: {value:""} }, - inputs:0, + inputs:1, + inputInfo: [ + { + label: "TRG", + types: ["any"] + } + ], outputs:1, outputInfo: [ { @@ -112,7 +130,7 @@ else if(this.variabletype == "flow" && this.variable) return "flow." + this.variable; else if(this.variabletype == "global" && this.variable) return "global." + this.variable; else if(this.variable) return this.variable; - return "variable"; + return "variable-in"; }, oneditprepare: function() { var that = this; @@ -305,7 +323,7 @@ $("#node-input-peerid-container").show(); $("#node-input-channel-container").hide(); $("#node-input-variable-container").show(); - $("#node-input-eventsource-container").hide(); + $("#node-input-eventsource-container").show(); $("#node-create-variable").show(); updateMetadataVariableOptions(); } else { @@ -313,7 +331,7 @@ $("#node-input-peerid-container").hide(); $("#node-input-channel-container").hide(); $("#node-input-variable-container").show(); - $("#node-input-eventsource-container").hide(); + $("#node-input-eventsource-container").show(); $("#node-create-variable").show(); if(variableType == "system") updateSystemVariableOptions(); else if(variableType == "flow") updateFlowVariableOptions(); @@ -485,6 +503,8 @@ $("#node-input-looppreventiongroup-container").show(); } else { $("#node-input-looppreventiongroup-container").hide(); + $("#node-input-looppreventiongroup").val(""); + that.looppreventiongroup = ""; } }); //}}} diff --git a/variable/variable-loop-prevention-group/variable-loop-prevention-group.hni b/variable/variable-loop-prevention-group/variable-loop-prevention-group.hni index be7b6c9e..61fb2ec1 100644 --- a/variable/variable-loop-prevention-group/variable-loop-prevention-group.hni +++ b/variable/variable-loop-prevention-group/variable-loop-prevention-group.hni @@ -2,7 +2,8 @@ { "name": "variable-loop-prevention-group", "readableName": "Variable loop prevention group", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 0 } diff --git a/variable/variable-out/MyNode.cpp b/variable/variable-out/MyNode.cpp index 89f8ce0b..519f3e4e 100644 --- a/variable/variable-out/MyNode.cpp +++ b/variable/variable-out/MyNode.cpp @@ -102,10 +102,22 @@ void MyNode::input(const Flows::PNodeInfo info, uint32_t index, const Flows::PVa parameters->push_back(std::make_shared(_channel)); parameters->push_back(std::make_shared(_variable)); parameters->push_back(message->structValue->at("payload")); - parameters->push_back(std::make_shared(false)); + parameters->push_back(std::make_shared(true)); Flows::PVariable result = invoke("setValue", parameters); - if(result->errorStruct) _out->printError("Error setting variable (Peer ID: " + std::to_string(_peerId) + ", channel: " + std::to_string(_channel) + ", name: " + _variable + "): " + result->structValue->at("faultString")->stringValue); + + Flows::PVariable message = std::make_shared(Flows::VariableType::tStruct); + if(result->errorStruct) + { + _out->printError("Error setting variable (Peer ID: " + std::to_string(_peerId) + ", channel: " + std::to_string(_channel) + ", name: " + _variable + "): " + result->structValue->at("faultString")->stringValue); + message->structValue->emplace("payload", std::make_shared(false)); + } + else + { + message->structValue->emplace("payload", std::make_shared(true)); + } + + output(0, message); } } catch(const std::exception& ex) diff --git a/variable/variable-out/locales/en-US/variable-out b/variable/variable-out/locales/en-US/variable-out index bc63b662..dd1efe9c 100644 --- a/variable/variable-out/locales/en-US/variable-out +++ b/variable/variable-out/locales/en-US/variable-out @@ -20,8 +20,9 @@ }, "tip": "Tip: See help tab for usage information.", "paletteHelp": "

Sets a variable.

", - "help": "Use this node to set a variable in Homegear.", - "input1Description": "The value to set the referenced variable to." + "help": "Use this node to set a variable in Homegear. When the variable was successfully set, the node outputs true. On error it outputs false. Please beware that not all errors are handled and this doesn't work for all device families. So make sure, you test all relevant error conditions.", + "input1Description": "The value to set the referenced variable to.", + "output1Description": "Outputs true on success and false on error." } } } \ No newline at end of file diff --git a/variable/variable-out/variable-out.hni b/variable/variable-out/variable-out.hni index d34e43e9..2cabcaef 100644 --- a/variable/variable-out/variable-out.hni +++ b/variable/variable-out/variable-out.hni @@ -2,7 +2,8 @@ { "name": "variable-out", "readableName": "Variable output", - "version": "0.0.1", + "version": "1.0.0", + "coreNode": true, "maxThreadCount": 0 } @@ -65,13 +66,19 @@ name: {value:""} }, - inputs:1, + inputs: 1, inputInfo: [ { types: ["any"] } ], - outputs:0, + outputs: 1, + outputInfo: [ + { + label: "RSLT", + types: ["any"] + } + ], icon: "arrow-in.png", align: "right", label: function() { @@ -81,7 +88,7 @@ else if(this.variabletype == "flow" && this.variable) return "flow." + this.variable; else if(this.variabletype == "global" && this.variable) return "global." + this.variable; else if(this.variable) return this.variable; - return "variable"; + return "variable-out"; }, oneditprepare: function() { var that = this;