diff --git a/src/hos_core_types/schema/core/configs/rulepackage.json b/src/hos_core_types/schema/core/configs/rulepackage.json index 9934ba65..51cdf6e2 100644 --- a/src/hos_core_types/schema/core/configs/rulepackage.json +++ b/src/hos_core_types/schema/core/configs/rulepackage.json @@ -21,47 +21,97 @@ ".*": { "type": "object", "properties": { - "body": { + "script": { "type": "object" }, - "conditions": { - "type": "array", - "minItems": 1, - "items": { - "type": "object", - "anyOf": [ - { "$ref": "#/definitions/DeviceMsgCondition" }, - { "$ref": "#/definitions/DeviceStatusCondition" }, - { "$ref": "#/definitions/TimeCondition" }, - { "$ref": "#/definitions/SystemCondition" } - ] - } + "exitscript": { + "type": "object" + }, + "condition": { + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "$schema": { + "type": "string" + } + }, + "additionalProperties": true, + "required": [ "type" ] } }, "additionalProperties": false, - "required": [ "conditions", "body" ] + "required": [ "condition", "script" ] } } } }, "definitions": { - "DeviceMsgCondition": { + "LogicalOrCondition": { "type": "object", "properties": { "type": { "description": "", "type": "string", - "pattern": "devicemsg" + "pattern": "logical_or" }, + "$schema": { + "type": "string", + "pattern": "hos:schema://core/configs/rulepackage#/definitions/LogicalOrCondition" + }, + "conditions": { + "description": "", + "type": "array", + "minItems": 1, + "items": { + "type": "object" + } + } + }, + "additionalProperties": false, + "required": [ "type", "conditions" ] + }, + "LogicalAndCondition": { + "type": "object", + "properties": { + "type": { + "description": "", + "type": "string", + "pattern": "logical_or" + }, + "$schema": { + "type": "string", + "pattern": "hos:schema://core/configs/rulepackage#/definitions/LogicalAndCondition" + }, + "conditions": { + "description": "", + "type": "array", + "minItems": 1, + "items": { + "type": "object" + } + } + }, + "additionalProperties": false, + "required": [ "type", "conditions" ] + }, + "DeviceStatusMsgCondition": { + "type": "object", + "properties": { + "type": { + "description": "", + "type": "string", + "pattern": "devicestatusmsg" + }, + "$schema": { + "type": "string", + "pattern": "hos:schema://core/configs/rulepackage#/definitions/DeviceStatusMsgCondition" + }, "deviceid": { "description": "", "type": "string" }, - "msgtype": { - "description": "", - "type": "string", - "enum": ["status", "command"] - }, "msgpointer": { "description": "", "type": "string" @@ -72,19 +122,50 @@ }, "additionalProperties": false, "anyOf": [ - { "required": [ "type", "deviceid" ] }, - { "required": [ "type", "deviceid", "msgtype", "msgvalue" ] }, - { "required": [ "type", "deviceid", "msgtype", "msgpointer" ] } - ] + { "required": [ "type", "deviceid" ] } + ] }, - "DeviceStatusCondition": { + "DeviceCommandMsgCondition": { "type": "object", "properties": { "type": { "description": "", "type": "string", - "pattern": "devicestatus" + "pattern": "devicecommandmsg" }, + "$schema": { + "type": "string", + "pattern": "hos:schema://core/configs/rulepackage#/definitions/DeviceCommandMsgCondition" + }, + "deviceid": { + "description": "", + "type": "string" + }, + "msgpointer": { + "description": "", + "type": "string" + }, + "msgvalue": { + "description": "" + } + }, + "additionalProperties": false, + "anyOf": [ + { "required": [ "type", "deviceid" ] } + ] + }, + "DeviceStateChangeCondition": { + "type": "object", + "properties": { + "type": { + "description": "", + "type": "string", + "pattern": "devicestatechange" + }, + "$schema": { + "type": "string", + "pattern": "hos:schema://core/configs/rulepackage#/definitions/DeviceStateChangeCondition" + }, "deviceid": { "description": "", "type": "string" @@ -113,6 +194,10 @@ "type": "string", "pattern": "time" }, + "$schema": { + "type": "string", + "pattern": "hos:schema://core/configs/rulepackage#/definitions/TimeCondition" + }, "cron": { "description": "", "type": "string" @@ -135,6 +220,10 @@ "type": "string", "pattern": "systemevent" }, + "$schema": { + "type": "string", + "pattern": "hos:schema://core/configs/rulepackage#/definitions/SystemCondition" + }, "event": { "description": "", "type": "string", diff --git a/src/hos_cosgi_ruleengine/CMakeLists.txt b/src/hos_cosgi_ruleengine/CMakeLists.txt index ae95c6db..7a3741d2 100644 --- a/src/hos_cosgi_ruleengine/CMakeLists.txt +++ b/src/hos_cosgi_ruleengine/CMakeLists.txt @@ -124,7 +124,7 @@ bundle(hos_cosgi_ruleengine ${PROJECT_SOURCE_DIR}/src/hos/cosgi/service/ruleengine/ruleengine_serviceimpl ${PROJECT_SOURCE_DIR}/src/hos/cosgi/service/ruleengine/rule/RulePackage ${PROJECT_SOURCE_DIR}/src/hos/cosgi/service/ruleengine/rule/Rule - ${PROJECT_SOURCE_DIR}/src/hos/cosgi/service/ruleengine/rule/RuleCondition + ${PROJECT_SOURCE_DIR}/src/hos/cosgi/service/ruleengine/rule/RuleConditionImpl ) target_link_libraries(hos_cosgi_ruleengine ${catkin_LIBRARIES} diff --git a/src/hos_cosgi_ruleengine/include/hos_cosgi_ruleengine/hos/cosgi/service/ruleengine/rule/Rule.h b/src/hos_cosgi_ruleengine/include/hos_cosgi_ruleengine/hos/cosgi/service/ruleengine/rule/Rule.h new file mode 100644 index 00000000..2d1dd39b --- /dev/null +++ b/src/hos_cosgi_ruleengine/include/hos_cosgi_ruleengine/hos/cosgi/service/ruleengine/rule/Rule.h @@ -0,0 +1,94 @@ +/* + * Rule.h + * + * Created on: 29.04.2015 + * Author: matzman + */ + +#ifndef SRC_HOS_COSGI_SERVICE_RULEENGINE_RULE_RULE_H_ +#define SRC_HOS_COSGI_SERVICE_RULEENGINE_RULE_RULE_H_ + + +#include +#include +#include +#include +#include "RuleCondition.h" + + +// Forward declarations +namespace hos { +namespace cosgi { +namespace service { +namespace devicerepository { +class Device; +class DeviceEvent; +class DeviceRepositoryService; +} +namespace ruleengine { +namespace rule { +class RulePackage; +} +} +namespace scripting { +class ScriptingService; +class GlobalScriptContext; +class Script; +} +} +} +namespace libvariant { +class HosSchemaLoader; +} +} + + +namespace hos { +namespace cosgi { +namespace service { +namespace ruleengine { +namespace rule { + +using namespace ::libvariant; + +class Rule { +private: + std::shared_ptr script; + std::shared_ptr exitScript; + std::shared_ptr condition; + bool enabled = false; + bool currentState = false; + +public: + RulePackage* const parent; + const std::string name; + + Rule(RulePackage* parent, const std::string name); + static std::shared_ptr createRule(RulePackage* parent, const std::string name, Variant raw); + + bool getCurrentState() { return currentState; } + bool evaluate(); + void execute(Variant lContext = Variant()); + void executeExitScript(Variant lContext = Variant()); + + bool isEnabled() { return enabled; } + void enable(); + void disable(); + + void conditionStateChanged(bool newState, RuleCondition::Category category, Variant lcontext = Variant()); + + std::shared_ptr createRuleCondition(Variant raw); + + libvariant::HosSchemaLoader* getHOSSchemaLoader(); + devicerepository::DeviceRepositoryService* getDeviceRepository(); +}; + + +} /* namespace rule */ +} /* namespace ruleengine */ +} /* namespace service */ +} /* namespace cosgi */ +} /* namespace hos */ + + +#endif /* SRC_HOS_COSGI_SERVICE_RULEENGINE_RULE_RULE_H_ */ diff --git a/src/hos_cosgi_ruleengine/include/hos_cosgi_ruleengine/hos/cosgi/service/ruleengine/rule/RuleCondition.h b/src/hos_cosgi_ruleengine/include/hos_cosgi_ruleengine/hos/cosgi/service/ruleengine/rule/RuleCondition.h new file mode 100644 index 00000000..9c45fa32 --- /dev/null +++ b/src/hos_cosgi_ruleengine/include/hos_cosgi_ruleengine/hos/cosgi/service/ruleengine/rule/RuleCondition.h @@ -0,0 +1,105 @@ +/* + * RuleCondition.h + * + * Created on: 29.04.2015 + * Author: matzman + */ + +#ifndef SRC_HOS_COSGI_SERVICE_RULEENGINE_RULE_RULECONDITION_H_ +#define SRC_HOS_COSGI_SERVICE_RULEENGINE_RULE_RULECONDITION_H_ + +#include +#include +#include + + + + +namespace hos { +namespace cosgi { +namespace service { +namespace ruleengine { +namespace rule { + +class Rule; +class RuleCompositeCondition; + +using namespace ::libvariant; + + +class RuleCondition { +public: + enum class Category : int { Event, State, Mixed }; + +protected: + bool currentState = false; + Category category; + RuleCompositeCondition* compositeParent = nullptr; + bool enabled = false; + + void notifyStateChangeToParent(bool newState, Category category, Variant lcontext = Variant()); + +public: + Rule* const parent; + const std::string type; + + RuleCondition(Rule* parent, Category category, const std::string type) : + parent(parent), category(category), type(type) {} + virtual ~RuleCondition() {} + void setCompositeParent(RuleCompositeCondition* compositeParent) { + this->compositeParent = compositeParent; + } + + virtual Category getCategory() { return category; } + + bool getCurrentState() { return currentState; } + virtual bool evaluate() { return currentState; } + + bool isEnabled() { return enabled; } + virtual void enable() { enabled = true; } + virtual void disable() { enabled = false; } +}; + + +class RuleCompositeCondition : public RuleCondition { +protected: + std::vector> children; +public: + RuleCompositeCondition(Rule* parent, Category category, std::string type) : + RuleCondition(parent, category, type) {} + + virtual void childStateChanged(RuleCondition* child, bool newState, Category category, Variant lcontext = Variant()) = 0; + + virtual void addChild(std::shared_ptr child) { + if (child) { + child->setCompositeParent(this); + children.push_back(child); + } + }; + + virtual void enable() { + if (!enabled) { + for (auto c : children) { + c->enable(); + } + enabled = true; + } + } + virtual void disable() { + if (enabled) { + for (auto c : children) { + c->disable(); + } + enabled = false; + } + } +}; + + +} /* namespace rule */ +} /* namespace ruleengine */ +} /* namespace service */ +} /* namespace cosgi */ +} /* namespace hos */ + +#endif /* SRC_HOS_COSGI_SERVICE_RULEENGINE_RULE_RULECONDITION_H_ */ diff --git a/src/hos_cosgi_ruleengine/include/hos_cosgi_ruleengine/hos/cosgi/service/ruleengine/ruleengine_service.h b/src/hos_cosgi_ruleengine/include/hos_cosgi_ruleengine/hos/cosgi/service/ruleengine/ruleengine_service.h index 4e0f3519..62130c2e 100644 --- a/src/hos_cosgi_ruleengine/include/hos_cosgi_ruleengine/hos/cosgi/service/ruleengine/ruleengine_service.h +++ b/src/hos_cosgi_ruleengine/include/hos_cosgi_ruleengine/hos/cosgi/service/ruleengine/ruleengine_service.h @@ -11,6 +11,8 @@ #include #include +#include +#include namespace hos { @@ -18,10 +20,22 @@ namespace cosgi { namespace service { namespace ruleengine { +namespace rule { +class RuleCondition; +class Rule; +} + +class RuleConditionFactory : public virtual interface::Object { +public: + virtual std::shared_ptr createCondition(rule::Rule* parent, ::libvariant::Variant raw) = 0; +}; + class RuleEngineService : public virtual interface::Service { public: static constexpr const char* SERVICE_NAME = "hos_ruleengine_service"; + + virtual void registerRuleConditionFactory(std::string type, std::shared_ptr factory) = 0; }; diff --git a/src/hos_cosgi_ruleengine/notes/RuleEngine.ecore b/src/hos_cosgi_ruleengine/notes/RuleEngine.ecore new file mode 100644 index 00000000..1d06b533 --- /dev/null +++ b/src/hos_cosgi_ruleengine/notes/RuleEngine.ecore @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/hos_cosgi_ruleengine/notes/RuleEngine.ecore_diagram b/src/hos_cosgi_ruleengine/notes/RuleEngine.ecore_diagram new file mode 100644 index 00000000..88b42a7b --- /dev/null +++ b/src/hos_cosgi_ruleengine/notes/RuleEngine.ecore_diagram @@ -0,0 +1,269 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/rule/Rule.cpp b/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/rule/Rule.cpp index a3c9d324..8fa0668f 100644 --- a/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/rule/Rule.cpp +++ b/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/rule/Rule.cpp @@ -6,12 +6,17 @@ */ -#include "Rule.h" +#include #include -#include "RuleCondition.h" +#include #include +#include "RulePackage.h" +#include +#include +#include +#include "../ruleengine_serviceimpl.h" namespace hos { @@ -21,40 +26,161 @@ namespace ruleengine { namespace rule { -Rule::Rule(RulePackage* parent, std::string name, scripting::ScriptingService* executionService) - : parent(parent), name(name), executionService(executionService) {} +Rule::Rule(RulePackage* parent, std::string name) + : parent(parent), name(name) {} -Rule::~Rule() {} -std::shared_ptr Rule::createRule(RulePackage* parent, std::string name, scripting::GlobalScriptContext* gContext, Variant raw, scripting::ScriptingService* executionService) { - std::shared_ptr retval; - if (raw.IsMap()) { - retval = std::make_shared(parent, name, executionService); - if (raw.HasPath("conditions") && raw["conditions"].IsList()) { - auto conditions = raw["conditions"]; - for (auto c : conditions.AsList()) { - auto cond = RuleCondition::createRuleCondition(retval.get(), c); - retval->conditions.push_back(cond); +libvariant::HosSchemaLoader* Rule::getHOSSchemaLoader() { + return parent->parent->getHOSSchemaLoader(); +} + +std::shared_ptr Rule::createRuleCondition(Variant condition) { + std::shared_ptr retval; + if (condition.IsMap() && condition.HasPath("type") && condition["type"].IsString()) { + if (condition.HasPath("$schema")) { + SchemaResult sr = SchemaValidate(Variant(), condition, getHOSSchemaLoader()); + if (sr.Error()) { + throw std::runtime_error(sr.PrettyPrintMessage()); } - if (raw.HasPath("body") && raw["body"].IsMap()) { - retval->body = executionService->createScript(gContext, raw["body"]); + } + auto conditionFactory = parent->getConditionFactory(condition["type"].AsString()); + if (conditionFactory) { + auto cond = conditionFactory->createCondition(this, condition); + if (cond) { + retval = cond; } else { - ROS_ERROR_STREAM("Could not create rule \"" << name << "\"."); - retval.reset(); + throw std::runtime_error("Could not create condition."); } } else { - ROS_ERROR_STREAM("Could not create rule \"" << name << "\"."); - retval.reset(); + throw std::runtime_error(std::string("Unknown condition type ").append(condition["type"].AsString())); } + } else { + throw std::runtime_error("Malformed JSON: missing property type or is not an string."); } return retval; } -void Rule::exec(scripting::GlobalScriptContext* gContext) { - body->exec(gContext); +std::shared_ptr Rule::createRule(RulePackage* parent, std::string name, Variant raw) { + std::shared_ptr retval; + if (parent->getRule(name)) { + throw std::runtime_error("Rule with the same name already exists."); + } + if (raw.IsMap()) { + retval = std::make_shared(parent, name); + if (raw.HasPath("condition") && raw["condition"].IsMap()) { + auto condition = raw["condition"]; + auto cond = retval->createRuleCondition(condition); + if (cond) { + retval->condition = cond; + } else { + throw std::runtime_error("Could not create condition."); + } + auto scriptFactory = parent->getScriptingService(); + //ToDo: Create Condition + if (raw.HasPath("script") && raw["script"].IsMap()) { + auto script = scriptFactory->createScript(parent->getGlobalScriptContext().get(), raw["script"]); + if (script) { + retval->script = script; + } else { + throw std::runtime_error("Could not create script."); + } + } else { + throw std::runtime_error("Malformed JSON: missing property script or is not an object."); + } + if (raw.HasPath("exitscript") && raw["exitscript"].IsMap()) { + auto script = scriptFactory->createScript(parent->getGlobalScriptContext().get(), raw["exitscript"]); + if (script) { + retval->exitScript = script; + } else { + throw std::runtime_error("Could not create exitscript."); + } + } + } else { + throw std::runtime_error("Malformed JSON: missing property condition."); + } + } else { + throw std::runtime_error("Malformed JSON: not an object."); + } + return retval; } +devicerepository::DeviceRepositoryService* Rule::getDeviceRepository() { + return parent->parent->getDeviceRepository(); +} + +void Rule::enable() { + if (!enabled) { + if (condition) { + condition->enable(); + } + enabled = true; + } +} + +void Rule::disable() { + if (enabled) { + if (condition) { + condition->disable(); + } + enabled = false; + } +} + + +void Rule::execute(Variant lContext) { + if (enabled && script) { + try { + script->exec(parent->getGlobalScriptContext().get(), lContext); + } catch(std::exception& e) { + ROS_ERROR_STREAM(e.what()); + } + } +} + +void Rule::executeExitScript(Variant lContext) { + if (enabled && exitScript) { + try { + exitScript->exec(parent->getGlobalScriptContext().get(), lContext); + } catch(std::exception& e) { + ROS_ERROR_STREAM(e.what()); + } + } +} + + +bool Rule::evaluate() { + if (condition) { + bool retval = condition->evaluate(); + if (retval != currentState) { + currentState = retval; + if (retval) { + execute(); + } else { + executeExitScript(); + } + } + return retval; + } else { + currentState = false; + return false; + } +} + + +void Rule::conditionStateChanged(bool newState, RuleCondition::Category category, Variant lcontext) { + if (newState && !currentState && category == RuleCondition::Category::Event) { + execute(lcontext); + } else if (category == RuleCondition::Category::State && newState != currentState) { + currentState = newState; + if (newState) { + execute(lcontext); + } else { + executeExitScript(lcontext); + } + } +} + } /* namespace rule */ } /* namespace ruleengine */ } /* namespace service */ diff --git a/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/rule/Rule.h b/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/rule/Rule.h deleted file mode 100644 index a0ab8f82..00000000 --- a/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/rule/Rule.h +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Rule.h - * - * Created on: 29.04.2015 - * Author: matzman - */ - -#ifndef SRC_HOS_COSGI_SERVICE_RULEENGINE_RULE_RULE_H_ -#define SRC_HOS_COSGI_SERVICE_RULEENGINE_RULE_RULE_H_ - - -#include -#include -#include -#include - - -// Forward declarations -namespace hos { -namespace cosgi { -namespace service { -namespace ruleengine { -namespace rule { -class RulePackage; -class RuleCondition; -} -} -namespace scripting { -class ScriptingService; -class GlobalScriptContext; -class Script; -} -} -} -} - - -namespace hos { -namespace cosgi { -namespace service { -namespace ruleengine { -namespace rule { - -using namespace libvariant; - -class Rule { -private: - RulePackage* parent; - std::string name; - std::shared_ptr body; - std::vector> conditions; - scripting::ScriptingService* executionService; -public: - Rule(RulePackage* parent, std::string name, scripting::ScriptingService* executionService); - static std::shared_ptr createRule(RulePackage* parent, std::string name, scripting::GlobalScriptContext* gContext, Variant raw, scripting::ScriptingService* executionService); - virtual ~Rule(); - - RulePackage* getParent() { return parent; } - - virtual void exec(scripting::GlobalScriptContext* gContext); -}; - -class RuleBody { - -}; - - -} /* namespace rule */ -} /* namespace ruleengine */ -} /* namespace service */ -} /* namespace cosgi */ -} /* namespace hos */ - - -#endif /* SRC_HOS_COSGI_SERVICE_RULEENGINE_RULE_RULE_H_ */ diff --git a/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/rule/RuleCondition.cpp b/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/rule/RuleCondition.cpp deleted file mode 100644 index 18c0c613..00000000 --- a/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/rule/RuleCondition.cpp +++ /dev/null @@ -1,27 +0,0 @@ -/* - * RuleCondition.cpp - * - * Created on: 29.04.2015 - * Author: matzman - */ - -#include "RuleCondition.h" - -namespace hos { -namespace cosgi { -namespace service { -namespace ruleengine { -namespace rule { - - -std::shared_ptr RuleCondition::createRuleCondition(Rule* parent, Variant raw) { - std::shared_ptr retval; - return retval; -} - - -} /* namespace rule */ -} /* namespace ruleengine */ -} /* namespace service */ -} /* namespace cosgi */ -} /* namespace hos */ diff --git a/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/rule/RuleCondition.h b/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/rule/RuleCondition.h deleted file mode 100644 index 7e4db473..00000000 --- a/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/rule/RuleCondition.h +++ /dev/null @@ -1,125 +0,0 @@ -/* - * RuleCondition.h - * - * Created on: 29.04.2015 - * Author: matzman - */ - -#ifndef SRC_HOS_COSGI_SERVICE_RULEENGINE_RULE_RULECONDITION_H_ -#define SRC_HOS_COSGI_SERVICE_RULEENGINE_RULE_RULECONDITION_H_ - -#include -#include -#include - - -// Forward declarations -namespace hos { -namespace cosgi { -namespace service { -namespace ruleengine { -namespace rule { -class Rule; -class ConditionDeviceStatusMsgEvent; -class ConditionDeviceCommandMsgEvent; -class ConditionDeviceStateChangedEvent; -class ConditionSystemEvent; -} -} -} -} -} - - -namespace hos { -namespace cosgi { -namespace service { -namespace ruleengine { -namespace rule { - -using namespace libvariant; - -class RuleCondition { -public: - enum class Type : int { DeviceStatusMsg, DeviceCommandMsg, DeviceStateChange, TimeEventCondition, SystemEventCondition}; -protected: - Rule* parent; - const Type type; -public: - static std::shared_ptr createRuleCondition(Rule* parent, libvariant::Variant raw); - RuleCondition(Rule* parent, Type type) : parent(parent), type(type) {} -public: - virtual ~RuleCondition() {} - - const Type getType() { return type; } -}; - - -class DeviceMsgCondition : public RuleCondition { -friend class RuleCondition; -protected: - Variant msgValue; - std::string msgPath; - std::string deviceID; - DeviceMsgCondition(Rule* parent, Type type, const Variant msgValue, const std::string msgPath, std::string deviceID) - : RuleCondition(parent, type) {} -}; - - -class DeviceStatusMsgCondition : public DeviceMsgCondition, public common::IObserver { -friend class RuleCondition; -protected: - DeviceStatusMsgCondition(Rule* parent, const Variant msgValue, const std::string msgPath, std::string deviceID) - : DeviceMsgCondition(parent, Type::DeviceStatusMsg, msgValue, msgPath, deviceID) {} -public: - virtual void notificationReceived(common::Observable* o, const std::string key, const ConditionDeviceStatusMsgEvent* e) override; -}; - - - -class DeviceCommandMsgCondition : public DeviceMsgCondition, public common::IObserver { -friend class RuleCondition; -protected: -public: - DeviceCommandMsgCondition(Rule* parent, const Variant msgValue, const std::string msgPath, std::string deviceID) - : DeviceMsgCondition(parent, Type::DeviceCommandMsg, msgValue, msgPath, deviceID) {} - virtual void notificationReceived(common::Observable* o, const std::string key, const ConditionDeviceCommandMsgEvent* e) override; -}; - - - -class DeviceStateChangeCondition : public RuleCondition, public common::IObserver { -friend class RuleCondition; -protected: -public: - DeviceStateChangeCondition(Rule* parent) : RuleCondition(parent, Type::DeviceStateChange) {} - virtual void notificationReceived(common::Observable* o, const std::string key, const ConditionDeviceStateChangedEvent* e) override; -}; - - - -class TimeEventCondition : public RuleCondition { -friend class RuleCondition; -protected: -public: - TimeEventCondition(Rule* parent) : RuleCondition(parent, Type::TimeEventCondition) {} -}; - - - -class SystemEventCondition : public RuleCondition, public common::IObserver { -friend class RuleCondition; -protected: -public: - SystemEventCondition(Rule* parent) : RuleCondition(parent, Type::SystemEventCondition) {} - virtual void notificationReceived(common::Observable* o, const std::string key, const ConditionSystemEvent* e) override; -}; - - -} /* namespace rule */ -} /* namespace ruleengine */ -} /* namespace service */ -} /* namespace cosgi */ -} /* namespace hos */ - -#endif /* SRC_HOS_COSGI_SERVICE_RULEENGINE_RULE_RULECONDITION_H_ */ diff --git a/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/rule/RuleConditionImpl.cpp b/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/rule/RuleConditionImpl.cpp new file mode 100644 index 00000000..401ea9ce --- /dev/null +++ b/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/rule/RuleConditionImpl.cpp @@ -0,0 +1,409 @@ +/* + * RuleCondition.cpp + * + * Created on: 29.04.2015 + * Author: matzman + */ + +#include "RuleConditionImpl.h" +#include +#include +#include + +namespace hos { +namespace cosgi { +namespace service { +namespace ruleengine { +namespace rule { + + +std::shared_ptr LogicalOrConditionFactory::createCondition(rule::Rule* parent, Variant raw) { + std::shared_ptr retval; + if (raw.HasPath("conditions") && raw["conditions"].IsList() && raw["conditions"].Size() > 0) { + retval = std::make_shared(parent); + for (auto c : raw["conditions"].AsList()) { + auto cond = parent->createRuleCondition(c); + if (cond) { + retval->addChild(cond); + } else { + throw std::runtime_error("Could not create condition."); + } + } + } else { + throw std::runtime_error("Malformed JSON: missing property condition or is not a list."); + } + return std::static_pointer_cast(retval); +} + + +std::shared_ptr LogicalAndConditionFactory::createCondition(rule::Rule* parent, Variant raw) { + std::shared_ptr retval; + if (raw.HasPath("conditions") && raw["conditions"].IsList() && raw["conditions"].Size() > 0) { + retval = std::make_shared(parent); + for (auto c : raw["conditions"].AsList()) { + auto cond = parent->createRuleCondition(c); + if (cond) { + retval->addChild(cond); + } else { + throw std::runtime_error("Could not create condition."); + } + } + } else { + throw std::runtime_error("Malformed JSON: missing property condition or is not a list."); + } + return std::static_pointer_cast(retval); +} + + + +void RuleCondition::notifyStateChangeToParent(bool newState, Category category, Variant lcontext) { + if (compositeParent) { + compositeParent->childStateChanged(this, newState, category, lcontext); + } else { + parent->conditionStateChanged(newState, category, lcontext); + } +} + + +void LogicalOrCondition::addChild(std::shared_ptr child) { + if (child->getCategory() == Category::Event) { + eventChildrenCount++; + } else if (child->getCategory() == Category::State) { + stateChildrenCount++; + } else { + mixedChildrenCount++; + } + if (mixedChildrenCount > 0 || (eventChildrenCount > 0 && stateChildrenCount > 0)) { + category = Category::Mixed; + } else if (eventChildrenCount > 0 && stateChildrenCount == 0) { + category = Category::Event; + } else { + category = Category::State; + } + RuleCompositeCondition::addChild(child); +} + + +void LogicalOrCondition::childStateChanged(RuleCondition* child, bool newState, Category category, Variant lcontext) { + if (newState && category == Category::Event && !currentState) { + notifyStateChangeToParent(true, category, lcontext); + } else if (category == Category::State) { + if (newState) { + trueChildrenCount++; + if (!currentState) { + currentState = true; + notifyStateChangeToParent(true, category, lcontext); + } + } else { + trueChildrenCount--; + if (currentState && trueChildrenCount <= 0) { + currentState = false; + notifyStateChangeToParent(false, category, lcontext); + } + } + } +} + + +bool LogicalOrCondition::evaluate() { + // Event conditions always evaluate to false + if (category == Category::Event) { + return false; + } + trueChildrenCount = 0; + for (auto c : children) { + if (c->evaluate()) { + trueChildrenCount++; + } + } + currentState = trueChildrenCount > 0; + return currentState; +} + + + +void LogicalAndCondition::addChild(std::shared_ptr child) { + if (child->getCategory() == Category::Event) { + if (eventChildrenCount > 0) { + throw new std::runtime_error("Cannot have an and condition with more than two event conditions."); + } + eventChildrenCount++; + } else if (child->getCategory() == Category::State) { + stateChildrenCount++; + } else { + mixedChildrenCount++; + } + if (mixedChildrenCount > 0 || (eventChildrenCount > 0 && stateChildrenCount > 0)) { + category = Category::Mixed; + } else if (eventChildrenCount > 0 && stateChildrenCount == 0) { + category = Category::Event; + } else { + category = Category::State; + } + RuleCompositeCondition::addChild(child); +} + +void LogicalAndCondition::childStateChanged(RuleCondition* child, bool newState, Category category, Variant lcontext) { + if (newState && category == Category::Event && !currentState && trueChildrenCount >= children.size()-1) { + notifyStateChangeToParent(true, category); + } else if (category == Category::State) { + if (newState) { + trueChildrenCount++; + if (!currentState && trueChildrenCount >= children.size()) { + currentState = true; + notifyStateChangeToParent(true, category, lcontext); + } + } else { + trueChildrenCount--; + if (currentState) { + currentState = false; + notifyStateChangeToParent(false, category, lcontext); + } + } + } +} + + + + +bool LogicalAndCondition::evaluate() { + // Event conditions always evaluate to false + if (category == Category::Event) { + return false; + } + trueChildrenCount = 0; + for (auto c : children) { + if (c->evaluate()) { + trueChildrenCount++; + } + } + currentState = trueChildrenCount == children.size(); + return currentState; +} + + + +void DeviceMsgCondition::notificationReceived(interface::Observer::observable_type* o, const devicerepository::DeviceEvent* e) { + switch (e->eventType) { + case devicerepository::DeviceEvent::EventType::DEVICE_ADDED: + if (!device && this->deviceID.compare(e->device->getDeviceID()) == 0) { + ROS_INFO_STREAM("Rule " << parent->name << ": Device " << deviceID << " appeared."); + device = e->device; + device->addObserver(dynamic_cast*>(this)); + } + break; + case devicerepository::DeviceEvent::EventType::DEVICE_REMOVED: + if (device && device->getDeviceID().compare(e->device->getDeviceID()) == 0) { + ROS_ERROR_STREAM("Rule " << parent->name << ": Device " << deviceID << " disappeared."); + device->deleteObserver(dynamic_cast*>(this)); + device = nullptr; + } + break; + default: + break; + } +} + +void DeviceMsgCondition::enable() { + auto repoService = parent->getDeviceRepository(); + auto device = repoService->getDevice(deviceID); + if (device) { + this->device = device.get(); + device->addObserver(dynamic_cast*>(this)); + } else { + ROS_ERROR_STREAM("Rule " << parent->name << ": Could not find device " << deviceID); + repoService->addObserver(dynamic_cast*>(this)); + } +} + +void DeviceMsgCondition::disable() { + if (device) { + device->deleteObserver(this); + device = nullptr; + } + parent->getDeviceRepository()->deleteObserver(this); +} + + + + + +std::shared_ptr DeviceStatusMsgConditionFactory::createCondition(rule::Rule* parent, Variant raw) { + std::shared_ptr retval; + std::string deviceID; + if (raw.HasPath("deviceid") && raw["deviceid"].IsString()) { + deviceID = raw["deviceid"].AsString(); + } else { + throw std::runtime_error("Malformed JSON: missing property deviceid or is not an string."); + } + std::string msgPointer; + if (raw.HasPath("msgpointer")) { + if (raw["msgpointer"].IsString()) { + msgPointer = raw["msgpointer"].AsString(); + } else { + throw std::runtime_error("Malformed JSON: property msgpointer is not an string."); + } + } + Variant msgValue; + if (raw.HasPath("msgvalue")) { + msgValue = raw["msgvalue"]; + } + retval = std::make_shared(parent, deviceID, msgPointer, msgValue); + return retval; +} + +void DeviceStatusMsgCondition::notificationReceived(interface::Observer::observable_type* o, const devicerepository::DeviceEvent* e) { + switch (e->eventType) { + case devicerepository::DeviceEvent::EventType::STATUS_UPDATED: + if (device && device == e->device) { + auto e2 = static_cast(e); + bool notify = true; + Variant value; + if (msgPointer.empty()) { + value = e2->statusMsg; + } else if (e2->statusMsg.HasPath(msgPointer)) { + value = e2->statusMsg.Get(msgPointer); + } else { + notify = false; + } + if (notify && !msgValue.IsNull()) { + if (msgValue != value) { + notify = false; + } + } + if (notify) { + Variant lcontext; + lcontext["event_deviceid"] = Variant(e->device->getDeviceID()); + lcontext["event_type"] = Variant("statusmsg"); + lcontext["event_msg"] = e2->statusMsg; + notifyStateChangeToParent(true, Category::Event, lcontext); + } + } + break; + default: + DeviceMsgCondition::notificationReceived(o, e); + break; + } +} + + +std::shared_ptr DeviceCommandMsgConditionFactory::createCondition(rule::Rule* parent, Variant raw) { + std::shared_ptr retval; + std::string deviceID; + if (raw.HasPath("deviceid") && raw["deviceid"].IsString()) { + deviceID = raw["deviceid"].AsString(); + } else { + throw std::runtime_error("Malformed JSON: missing property deviceid or is not an string."); + } + std::string msgPointer; + if (raw.HasPath("msgpointer")) { + if (raw["msgpointer"].IsString()) { + msgPointer = raw["msgpointer"].AsString(); + } else { + throw std::runtime_error("Malformed JSON: property msgpointer is not an string."); + } + } + Variant msgValue; + if (raw.HasPath("msgvalue")) { + msgValue = raw["msgvalue"]; + } + retval = std::make_shared(parent, deviceID, msgPointer, msgValue); + return retval; +} + +void DeviceCommandMsgCondition::notificationReceived(interface::Observer::observable_type* o, const devicerepository::DeviceEvent* e) { + switch (e->eventType) { + case devicerepository::DeviceEvent::EventType::COMMAND_RECEIVED: + if (device && device == e->device) { + auto e2 = static_cast(e); + bool notify = true; + Variant value; + if (msgPointer.empty()) { + value = e2->cmdMsg; + } else if (e2->cmdMsg.HasPath(msgPointer)) { + value = e2->cmdMsg.Get(msgPointer); + } else { + notify = false; + } + if (notify && !msgValue.IsNull()) { + if (msgValue != value) { + notify = false; + } + } + if (notify) { + Variant lcontext; + lcontext["event_deviceid"] = Variant(e->device->getDeviceID()); + lcontext["event_type"] = Variant("commandmsg"); + lcontext["event_msg"] = e2->cmdMsg; + notifyStateChangeToParent(true, Category::Event, lcontext); + } + } + break; + default: + DeviceMsgCondition::notificationReceived(o, e); + break; + } +} + + +std::shared_ptr DeviceStateChangeConditionFactory::createCondition(rule::Rule* parent, Variant raw) { + std::shared_ptr retval; + std::string deviceID; + if (raw.HasPath("deviceid") && raw["deviceid"].IsString()) { + deviceID = raw["deviceid"].AsString(); + } else { + throw std::runtime_error("Malformed JSON: missing property deviceid or is not an string."); + } + std::string statePointer; + if (raw.HasPath("msgpointer")) { + if (raw["msgpointer"].IsString()) { + statePointer = raw["msgpointer"].AsString(); + } else { + throw std::runtime_error("Malformed JSON: property msgpointer is not an string."); + } + } + retval = std::make_shared(parent, deviceID, statePointer); + return retval; +} + + +void DeviceStateChangeCondition::notificationReceived(interface::Observer::observable_type* o, const devicerepository::DeviceEvent* e) { + switch (e->eventType) { + case devicerepository::DeviceEvent::EventType::STATUS_UPDATED: + if (device && device == e->device) { + auto e2 = static_cast(e); + bool notify = true; + Variant current, last; + if (msgPointer.empty()) { + current = device->getCurrentState(); + last = device->getLastState(); + } else if (device->getCurrentState().HasPath(msgPointer) && device->getLastState().HasPath(msgPointer)) { + current = device->getCurrentState().Get(msgPointer); + last = device->getLastState().Get(msgPointer); + } else { + notify = false; + } + if (notify && current == last) { + notify = false; + } + if (notify) { + Variant lcontext; + lcontext["event_deviceid"] = Variant(e->device->getDeviceID()); + lcontext["event_type"] = Variant("statechange"); + lcontext["event_msg"] = e2->statusMsg; + notifyStateChangeToParent(true, Category::Event, lcontext); + } + } + break; + default: + DeviceMsgCondition::notificationReceived(o, e); + break; + } +} + + +} /* namespace rule */ +} /* namespace ruleengine */ +} /* namespace service */ +} /* namespace cosgi */ +} /* namespace hos */ diff --git a/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/rule/RuleConditionImpl.h b/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/rule/RuleConditionImpl.h new file mode 100644 index 00000000..9a7383da --- /dev/null +++ b/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/rule/RuleConditionImpl.h @@ -0,0 +1,140 @@ +/* + * RuleConditionImpl.h + * + * Created on: 11.05.2015 + * Author: matzman + */ + +#ifndef SRC_HOS_COSGI_SERVICE_RULEENGINE_RULE_RULECONDITIONIMPL_H_ +#define SRC_HOS_COSGI_SERVICE_RULEENGINE_RULE_RULECONDITIONIMPL_H_ + +#include +#include + + +// Service Namespace +namespace hos { +namespace cosgi { +namespace service { +namespace devicerepository { +class Device; +class DeviceEvent; +class DeviceRepositoryService; +} +namespace ruleengine { +namespace rule { + +using namespace ::libvariant; + + +class LogicalOrCondition : public RuleCompositeCondition { +protected: + unsigned eventChildrenCount = 0; + unsigned stateChildrenCount = 0; + unsigned mixedChildrenCount = 0; + unsigned trueChildrenCount = 0; +public: + LogicalOrCondition(Rule* parent) : + RuleCompositeCondition(parent, Category::Mixed, "logical_or") {} + + virtual void addChild(std::shared_ptr child) override; + virtual void childStateChanged(RuleCondition* child, bool newState, Category category, Variant lcontext = Variant()) override; + virtual bool evaluate() override; +}; + +class LogicalOrConditionFactory : public virtual RuleConditionFactory { +public: + virtual std::shared_ptr createCondition(rule::Rule* parent, ::libvariant::Variant raw) override; +}; + + +class LogicalAndCondition : public RuleCompositeCondition { +protected: + unsigned eventChildrenCount = 0; + unsigned stateChildrenCount = 0; + unsigned mixedChildrenCount = 0; + unsigned trueChildrenCount = 0; +public: + LogicalAndCondition(Rule* parent) : + RuleCompositeCondition(parent, Category::Mixed, "logical_and") {} + + virtual void addChild(std::shared_ptr child) override; + virtual void childStateChanged(RuleCondition* child, bool newState, Category category, Variant lcontext = Variant()) override; + virtual bool evaluate() override; +}; + +class LogicalAndConditionFactory : public virtual RuleConditionFactory { +public: + virtual std::shared_ptr createCondition(rule::Rule* parent, ::libvariant::Variant raw) override; +}; + + +class DeviceMsgCondition : public RuleCondition, public virtual interface::Observer { +protected: + const std::string deviceID; + devicerepository::Device* device = nullptr; + const std::string msgPointer; + const Variant msgValue; + +public: + DeviceMsgCondition(Rule* parent, Category category, std::string type, const std::string deviceID, const std::string msgPointer, const Variant msgValue) : + RuleCondition(parent, category, type), deviceID(deviceID), msgPointer(msgPointer), msgValue(msgValue) {} + + virtual void notificationReceived(interface::Observer::observable_type* o, const devicerepository::DeviceEvent* e) override; + virtual void enable() override; + virtual void disable() override; +}; + + +class DeviceStatusMsgCondition : public DeviceMsgCondition { +public: + DeviceStatusMsgCondition(Rule* parent, const std::string deviceID, const std::string msgPointer, const Variant msgValue) : + DeviceMsgCondition(parent, Category::Event, "devicestatusmsg", deviceID, msgPointer, msgValue) {} + + virtual void notificationReceived(interface::Observer::observable_type* o, const devicerepository::DeviceEvent* e) override; +}; + +class DeviceStatusMsgConditionFactory : public virtual RuleConditionFactory { +public: + virtual std::shared_ptr createCondition(rule::Rule* parent, Variant raw) override; +}; + + +class DeviceCommandMsgCondition : public DeviceMsgCondition { +public: + DeviceCommandMsgCondition(Rule* parent, const std::string deviceID, const std::string msgPointer, const Variant msgValue) : + DeviceMsgCondition(parent, Category::Event, "devicecommandmsg", deviceID, msgPointer, msgValue) {} + + virtual void notificationReceived(interface::Observer::observable_type* o, const devicerepository::DeviceEvent* e) override; +}; + +class DeviceCommandMsgConditionFactory : public virtual RuleConditionFactory { +public: + virtual std::shared_ptr createCondition(rule::Rule* parent, ::libvariant::Variant raw) override; +}; + + +class DeviceStateChangeCondition : public DeviceMsgCondition { +public: + DeviceStateChangeCondition(Rule* parent, const std::string deviceID, const std::string statePointer) : + DeviceMsgCondition(parent, Category::Event, "devicestatechange", deviceID, statePointer, Variant()) {} + + virtual void notificationReceived(interface::Observer::observable_type* o, const devicerepository::DeviceEvent* e) override; +}; + +class DeviceStateChangeConditionFactory : public virtual RuleConditionFactory { +public: + virtual std::shared_ptr createCondition(rule::Rule* parent, Variant raw) override; +}; + + + + +} /* namespace rule */ +} /* namespace ruleengine */ +} /* namespace service */ +} /* namespace cosgi */ +} /* namespace hos */ + + +#endif /* SRC_HOS_COSGI_SERVICE_RULEENGINE_RULE_RULECONDITIONIMPL_H_ */ diff --git a/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/rule/RulePackage.cpp b/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/rule/RulePackage.cpp index cdc0c59b..aea80299 100644 --- a/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/rule/RulePackage.cpp +++ b/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/rule/RulePackage.cpp @@ -12,7 +12,7 @@ #include #include "../ruleengine_serviceimpl.h" -#include "Rule.h" +#include namespace hos { namespace cosgi { @@ -20,21 +20,26 @@ namespace service { namespace ruleengine { namespace rule { -RulePackage::RulePackage(service::ruleengine::RuleEngineServiceImpl* parent, std::string name) - : parent(parent), name(name) {} +RulePackage::RulePackage(service::ruleengine::RuleEngineServiceImpl* parent, const std::string name, const std::string language, const Variant raw) + : parent(parent), name(name), language(language), rawValue(raw) {} -RulePackage::~RulePackage() { -} +RulePackage::~RulePackage() {} std::shared_ptr RulePackage::createRulePackage(service::ruleengine::RuleEngineServiceImpl* parent, std::string name, Variant raw) { std::shared_ptr retval; + if (parent->getRulePackage(name)) { + std::stringstream ss; + ss << "Could not create package " << name << ": There already exists a rule package with the same name."; + throw std::runtime_error(ss.str()); + } if (raw.IsMap()) { - retval = std::make_shared(parent, name); + std::string language; if (raw.HasPath("language") && raw["language"].IsString()) { - retval->language = raw["language"].AsString(); + language = raw["language"].AsString(); } else { throw std::runtime_error("Rule package JSON is missing language parameter or parameter has wrong type."); } + retval = std::make_shared(parent, name, language, raw); retval->executionService = parent->getExecutionBackend(retval->language); if (retval->executionService) { if (raw.HasPath("globalcontext")) { @@ -42,9 +47,13 @@ std::shared_ptr RulePackage::createRulePackage(service::ruleengine: } if (raw.HasPath("rules") && raw["rules"].IsMap()) { for (auto r : raw["rules"].AsMap()) { - std::shared_ptr rule = Rule::createRule(retval.get(), r.first, retval->globalContext.get(), r.second, retval->executionService); - if (rule) { - retval->rules.insert({r.first, rule}); + try { + std::shared_ptr rule = Rule::createRule(retval.get(), r.first, r.second); + if (rule) { + retval->rules.insert({r.first, rule}); + } + } catch (std::runtime_error& e) { + ROS_ERROR_STREAM("Error with rule \"" << r.first << "\": " << e.what()); } } if (retval->rules.size() == 0) { @@ -53,9 +62,6 @@ std::shared_ptr RulePackage::createRulePackage(service::ruleengine: } else { throw std::runtime_error("Rule package JSON is missing rules parameter or parameter has wrong type."); } - retval->setState(State::Ok); - } else { - retval->setState(State::NoExecService); } } else { throw std::runtime_error("Rule package JSON is not valid."); @@ -63,6 +69,15 @@ std::shared_ptr RulePackage::createRulePackage(service::ruleengine: return retval; } + +std::shared_ptr RulePackage::getRule(const std::string& name) { + std::shared_ptr retval; + try { + retval = rules.at(name); + } catch (...) {} + return retval; +} + size_t RulePackage::getRules(std::vector>& rules) { size_t retval = 0; for (auto item : this->rules) { @@ -72,6 +87,31 @@ size_t RulePackage::getRules(std::vector>& rules) { return retval; } +std::shared_ptr RulePackage::getConditionFactory(std::string type) { + return parent->getRuleConditionFactory(type); +} + + +void RulePackage::enable() { + if (!enabled) { + for (auto r : rules) { + r.second->enable(); + } + enabled = true; + } +} + + +void RulePackage::disable() { + if (enabled) { + for (auto r : rules) { + r.second->enable(); + } + enabled = false; + } +} + + } /* namespace rule */ } /* namespace ruleengine */ } /* namespace service */ diff --git a/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/rule/RulePackage.h b/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/rule/RulePackage.h index ce94550b..1388a564 100644 --- a/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/rule/RulePackage.h +++ b/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/rule/RulePackage.h @@ -23,6 +23,7 @@ namespace cosgi { namespace service { namespace ruleengine { class RuleEngineServiceImpl; +class RuleConditionFactory; namespace rule { class Rule; } @@ -44,38 +45,35 @@ namespace rule { using namespace ::libvariant; class RulePackage { -public: - enum class State : int { Error = 0, NoExecService = 1, Ok = 2 }; - private: typedef std::map> ruleMap_t; - std::string name; - std::string language; - State state = State::Error; - ruleMap_t rules; - std::shared_ptr globalContext; - service::ruleengine::RuleEngineServiceImpl* parent; service::scripting::ScriptingService* executionService = nullptr; - - - bool setState(State state) { - this->state = state; - return true; - } + std::shared_ptr globalContext; + ruleMap_t rules; + Variant rawValue; + bool enabled = false; public: - RulePackage(service::ruleengine::RuleEngineServiceImpl* parent, std::string name); + service::ruleengine::RuleEngineServiceImpl* const parent; + const std::string name; + const std::string language; + static std::shared_ptr createRulePackage(service::ruleengine::RuleEngineServiceImpl* parent, std::string name, Variant raw); + RulePackage(service::ruleengine::RuleEngineServiceImpl* parent, const std::string name, const std::string language, Variant raw); virtual ~RulePackage(); - std::shared_ptr getRule(const std::string& name) { return rules[name]; } + std::shared_ptr getRule(const std::string& name); size_t getRules(std::vector>& rules); - service::ruleengine::RuleEngineServiceImpl* getParent() { return parent; } + bool isEnabled() { return enabled; } + void enable(); + void disable(); - State getState() { return state; } + service::scripting::ScriptingService* getScriptingService() { return executionService; } + std::shared_ptr getGlobalScriptContext() { return globalContext; } + std::shared_ptr getConditionFactory(std::string type); }; } /* namespace rule */ diff --git a/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/ruleengine_serviceimpl.cpp b/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/ruleengine_serviceimpl.cpp index 98166c70..a9c07759 100644 --- a/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/ruleengine_serviceimpl.cpp +++ b/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/ruleengine_serviceimpl.cpp @@ -10,12 +10,14 @@ #include #include #include +#include #include #include #include #include #include "rule/RulePackage.h" +#include "rule/RuleConditionImpl.h" namespace hos { @@ -23,6 +25,9 @@ namespace cosgi { namespace service { namespace ruleengine { +using namespace ::libvariant; + + template<> const char* implementation::ServiceInfo::name = service::ruleengine::RuleEngineService::SERVICE_NAME; template<> const int implementation::ServiceInfo::major = 0; template<> const int implementation::ServiceInfo::minor = 6; @@ -57,6 +62,11 @@ void RuleEngineServiceImpl::init() { XmlRpc::XmlRpcValue rawValue; nodehandle->getParam(ruleEnginePrefix, rawValue); schemaLoader.reset(new libvariant::HosSchemaLoader()); + registerRuleConditionFactory("logical_or", std::make_shared()); + registerRuleConditionFactory("logical_and", std::make_shared()); + registerRuleConditionFactory("devicestatusmsg", std::make_shared()); + registerRuleConditionFactory("devicecommandmsg", std::make_shared()); + registerRuleConditionFactory("devicestatechange", std::make_shared()); if (rawValue.getType() == XmlRpc::XmlRpcValue::TypeStruct) { for (auto pkg : rawValue) { std::string pname = pkg.first; @@ -80,26 +90,28 @@ RuleEngineServiceImpl::~RuleEngineServiceImpl() { void RuleEngineServiceImpl::notificationReceived(interface::Observer::observable_type* o, const devicerepository::DeviceEvent* e) { - switch (e->eventType) { + /*switch (e->eventType) { case devicerepository::DeviceEvent::EventType::STATUS_UPDATED: { - ConditionDeviceStatusMsgEvent event; - common::Observable::notifyObservers(e->device->getDeviceID(), &event); - if (e->device->stateChanged()) { - ConditionDeviceStateChangedEvent event; - common::Observable::notifyObservers(e->device->getDeviceID(), &event); + auto e2 = static_cast(e); + rule::DeviceStatusMsgEvent event(e2->device, e2->statusMsg, std::chrono::system_clock::now()); + this->notifyObservers(&event, (int)rule::RuleEvent::EventType::DeviceStatusMsgEvent); + if (e2->device->getCurrentState() != e2->device->getLastState()) { + rule::DeviceStateChangeEvent event2(e2->device, std::chrono::system_clock::now()); + this->notifyObservers(&event2, (int)rule::RuleEvent::EventType::DeviceStateChangeEvent); } } break; case devicerepository::DeviceEvent::EventType::COMMAND_RECEIVED: { - ConditionDeviceCommandMsgEvent event; - common::Observable::notifyObservers(e->device->getDeviceID(), &event); + auto e2 = static_cast(e); + rule::DeviceCommandMsgEvent event(e2->device, e2->cmdMsg, std::chrono::system_clock::now()); + this->notifyObservers(&event, (int)rule::RuleEvent::EventType::DeviceCommandMsgEvent); } break; default: break; - } + }*/ } void RuleEngineServiceImpl::addRulePkg(std::string name) { @@ -111,23 +123,32 @@ void RuleEngineServiceImpl::addRulePkg(std::string name) { } void RuleEngineServiceImpl::addRulePkg(std::string name, std::string rawValue) { - ::libvariant::Variant s = schemaLoader->GetSchema(std::string("hos:schema://core/configs/rulepackage")); - ::libvariant::Variant m = ::libvariant::Deserialize(rawValue, ::libvariant::SERIALIZE_JSON); - ::libvariant::SchemaResult sr = ::libvariant::SchemaValidate(s, m, schemaLoader.get()); + Variant s = schemaLoader->GetSchema(std::string("hos:schema://core/configs/rulepackage")); + Variant m = Deserialize(rawValue, SERIALIZE_JSON); + SchemaResult sr = SchemaValidate(s, m, schemaLoader.get()); if (!sr.Error()) { std::shared_ptr pkg = rule::RulePackage::createRulePackage(this, name, m); if (pkg) { - if (pkg->getState() == rule::RulePackage::State::Ok) { + if (pkg->getScriptingService()) { rulePkgs.insert({name, pkg}); - } else if (pkg->getState() == rule::RulePackage::State::NoExecService) { + pkg->enable(); + } else { noExecRulePkgs.insert({name, pkg}); std::cout << "No execution service found for rule package \"" << name << "\"" << std::endl; - } else { - throw std::runtime_error("Unknown state"); } + } else { + throw std::runtime_error("Unknown error during rule package creation."); } } else { - throw std::runtime_error(sr.PrettyPrintMessage()); + std::stringstream ss; + ss << sr.PrettyPrintMessage() << std::endl; + for (auto e : sr.GetErrors()) { + ss << e.PrettyPrintMessage() << std::endl; + } + for (auto e : sr.GetSchemaErrors()) { + ss << e.PrettyPrintMessage() << std::endl; + } + throw std::runtime_error(ss.str()); } } @@ -152,6 +173,24 @@ scripting::ScriptingService* RuleEngineServiceImpl::getExecutionBackend(std::str } +void RuleEngineServiceImpl::registerRuleConditionFactory(std::string type, std::shared_ptr factory) { + condFactories.insert({type, factory}); +} + + +std::shared_ptr RuleEngineServiceImpl::getRulePackage(const std::string& name) { + std::shared_ptr retval; + try { + retval = rulePkgs.at(name); + } catch(...) {} + if (!retval) { + try { + retval = noExecRulePkgs.at(name); + } catch(...) {} + } + return retval; +} + } // namespace ruleengine } // namespace service } // namespace cosgi diff --git a/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/ruleengine_serviceimpl.h b/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/ruleengine_serviceimpl.h index c5e5f07d..c7777d39 100644 --- a/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/ruleengine_serviceimpl.h +++ b/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/ruleengine_serviceimpl.h @@ -9,21 +9,19 @@ #define INCLUDE_HOS_COSGI_RULEENGINE_HOS_RULEENGINE_SERVICE_IMPL_H_ #include -#include #include #include #include #include #include #include +#include #include #include #include -#include #include - -#include #include +#include // Forward declarations @@ -36,15 +34,6 @@ namespace service { namespace core { class CoreService; } -namespace ruleengine { -namespace rule { -class RulePackage; -} -} -} -namespace devicerepository { -class DeviceRepositoryService; -class DeviceEvent; } } } @@ -56,35 +45,21 @@ namespace hos { namespace cosgi { namespace service { namespace ruleengine { +namespace rule { +class RulePackage; +} -class ConditionDeviceStatusMsgEvent { - -}; - -class ConditionDeviceCommandMsgEvent { - -}; - -class ConditionDeviceStateChangedEvent { - -}; - -class ConditionSystemEvent { - -}; class RuleEngineServiceImpl; -class RuleEngineServiceImpl : public virtual RuleEngineService, +class RuleEngineServiceImpl : + public virtual RuleEngineService, public virtual implementation::Service, - public virtual interface::Observer, - public common::Observable, - public common::Observable, - public common::Observable, - public common::Observable { + public virtual interface::Observer { private: typedef std::map> rulePkgMap_t; typedef std::map execServiceMap_t; + typedef std::map> condFactoryMap_t; service::core::CoreService* coreService = nullptr; devicerepository::DeviceRepositoryService* repoService = nullptr; @@ -95,6 +70,7 @@ private: rulePkgMap_t rulePkgs; rulePkgMap_t noExecRulePkgs; execServiceMap_t execServices; + condFactoryMap_t condFactories; std::unique_ptr schemaLoader; @@ -115,6 +91,8 @@ public: /* Public Service Implementations */ + virtual void registerRuleConditionFactory(std::string type, std::shared_ptr factory) override; + /* Observer Callbacks */ virtual void notificationReceived(interface::Observer::observable_type* o, const devicerepository::DeviceEvent* e) override; @@ -125,6 +103,14 @@ public: void addExecutionBackend(scripting::ScriptingService* service); scripting::ScriptingService* getExecutionBackend(std::string language); + + std::shared_ptr getRuleConditionFactory(std::string type) { return condFactories[type]; } + + libvariant::HosSchemaLoader* getHOSSchemaLoader() { return schemaLoader.get(); } + + devicerepository::DeviceRepositoryService* getDeviceRepository() { return repoService; } + + std::shared_ptr getRulePackage(const std::string& name); }; diff --git a/src/hos_cosgi_ruleengine/test/Copy of rulepkg01.json b/src/hos_cosgi_ruleengine/test/Copy of rulepkg01.json new file mode 100644 index 00000000..a8a78b9f --- /dev/null +++ b/src/hos_cosgi_ruleengine/test/Copy of rulepkg01.json @@ -0,0 +1,60 @@ +{ + "language": "python", + "globalcontext": { "code": "print \"GlobalContext: Init!\"\nvar1 = 66.6\nvar2 = 0"}, + "rules": { + "rule01": { + "condition": { + "type": "logical_or", + "$schema": "hos:schema://core/configs/rulepackage#/definitions/LogicalOrCondition", + "conditions" : [ + { + "type": "devicestatusmsg", + "deviceid": "huelight3" + }, + { + "type": "devicestatechange", + "deviceid": "huelight4" + } + ] + }, + "script": { "code": "print \"Hello World1: \" + str(var2)\nvar2 = var2 + 1\nimport __main__\nprint \"__main__.__dict__: \" + str(__main__.__dict__)\nprint \"test: \" + event_type" }, + "exitscript": { "code": "print \"Hello from exitscript1\"" } + }, + "rule02": { + "condition": { + "type": "logical_or", + "$schema": "hos:schema://core/configs/rulepackage#/definitions/LogicalOrCondition", + "conditions" : [ + { + "type": "devicecommandmsg", + "deviceid": "huelight4" + }, + { + "type": "devicestatechange", + "deviceid": "huelight3" + } + ] + }, + "script": { "code": "print \"Hello World2: \" + str(var2)\nvar2 = var2 + 1\nimport __main__\nprint \"__main__.__dict__: \" + str(__main__.__dict__)\nprint \"test: \" + event_type" }, + "exitscript": { "code": "print \"Hello from exitscript2\"" } + }, + "rule03": { + "condition": { + "type": "logical_or", + "$schema": "hos:schema://core/configs/rulepackage#/definitions/LogicalOrCondition", + "conditions" : [ + { + "type": "devicecommandmsg", + "deviceid": "huelight1" + }, + { + "type": "devicestatechange", + "deviceid": "huelight2" + } + ] + }, + "script": { "code": "print \"Hello World3: \" + str(var2)\nvar2 = var2 + 1\nimport __main__\nprint \"__main__.__dict__: \" + str(__main__.__dict__)\nprint \"test: \" + event_type" }, + "exitscript": { "code": "print \"Hello from exitscript2\"" } + } + } +} \ No newline at end of file diff --git a/src/hos_cosgi_ruleengine/test/rulepkg01.json b/src/hos_cosgi_ruleengine/test/rulepkg01.json index 67a66629..5af327a6 100644 --- a/src/hos_cosgi_ruleengine/test/rulepkg01.json +++ b/src/hos_cosgi_ruleengine/test/rulepkg01.json @@ -1,32 +1,60 @@ { "language": "python", - "globalcontext": { "code": "print \"GlobalContext: Init!\"\nvar1 = 66.6"}, + "globalcontext": { "code": "print \"GlobalContext: Init!\"\nvar1 = 66.6\nvar2 = 0"}, "rules": { "rule01": { - "conditions": [ - { - "type": "devicemsg", - "deviceid": "huelight1" - }, - { - "type": "devicestatus", - "deviceid": "huelight1" - } - ], - "body": { "code": "print \"Hello World1\"" } + "condition": { + "type": "logical_or", + "$schema": "hos:schema://core/configs/rulepackage#/definitions/LogicalOrCondition", + "conditions" : [ + { + "type": "devicestatusmsg", + "deviceid": "huelight3" + }, + { + "type": "devicestatechange", + "deviceid": "huelight4" + } + ] + }, + "script": { "code": "global var2\nprint \"Hello World1: \" + str(var2)\nvar2 = var2 + 1\nimport __main__\nprint \"__main__.__dict__: \" + str(__main__.__dict__)\nprint \"test: \" + event_type\ndev1 = Devices.getDevice(\"huelight1\")\nprint \"device: \" + dev1.deviceID()" }, + "exitscript": { "code": "print \"Hello from exitscript1\"" } }, "rule02": { - "conditions": [ - { - "type": "devicemsg", - "deviceid": "huelight2" - }, - { - "type": "devicestatus", - "deviceid": "huelight2" - } - ], - "body": { "code": "print \"Hello World2\"" } + "condition": { + "type": "logical_or", + "$schema": "hos:schema://core/configs/rulepackage#/definitions/LogicalOrCondition", + "conditions" : [ + { + "type": "devicecommandmsg", + "deviceid": "huelight4" + }, + { + "type": "devicestatechange", + "deviceid": "huelight3" + } + ] + }, + "script": { "code": "print \"Hello World2: \" + str(var2)\nvar2 = var2 + 1\nimport __main__\nprint \"__main__.__dict__: \" + str(__main__.__dict__)\nprint \"test: \" + event_type" }, + "exitscript": { "code": "print \"Hello from exitscript2\"" } + }, + "rule03": { + "condition": { + "type": "logical_or", + "$schema": "hos:schema://core/configs/rulepackage#/definitions/LogicalOrCondition", + "conditions" : [ + { + "type": "devicecommandmsg", + "deviceid": "huelight1" + }, + { + "type": "devicestatechange", + "deviceid": "huelight2" + } + ] + }, + "script": { "code": "print \"Hello World3: \" + str(var2)\nvar2 = var2 + 1\nimport __main__\nprint \"__main__.__dict__: \" + str(__main__.__dict__)\nprint \"test: \" + event_type" }, + "exitscript": { "code": "print \"Hello from exitscript2\"" } } } } \ No newline at end of file diff --git a/src/hos_cosgi_scripting/include/hos_cosgi_scripting/hos/cosgi/service/scripting/scripting_service.h b/src/hos_cosgi_scripting/include/hos_cosgi_scripting/hos/cosgi/service/scripting/scripting_service.h index 83ec1f0d..eda19c95 100644 --- a/src/hos_cosgi_scripting/include/hos_cosgi_scripting/hos/cosgi/service/scripting/scripting_service.h +++ b/src/hos_cosgi_scripting/include/hos_cosgi_scripting/hos/cosgi/service/scripting/scripting_service.h @@ -19,6 +19,7 @@ namespace cosgi { namespace service { namespace scripting { +using namespace ::libvariant; class GlobalScriptContext : public virtual interface::Object { @@ -27,7 +28,7 @@ class GlobalScriptContext : public virtual interface::Object { class Script : public virtual interface::Object { public: - virtual void exec(GlobalScriptContext* gContext) = 0; + virtual void exec(GlobalScriptContext* gContext, Variant lContext = Variant()) = 0; }; @@ -37,8 +38,8 @@ public: virtual std::string getLanguage() = 0; - virtual std::shared_ptr createGlobalScriptContext(libvariant::Variant raw) = 0; - virtual std::shared_ptr