From 774452ff7c46e259b3596cf50fe23fb7967e23df Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Tue, 26 Mar 2019 12:21:29 +0100 Subject: [PATCH] Add support for state based rule action params --- libnymea-core/jsonrpc/jsontypes.cpp | 25 ++- libnymea-core/nymeacore.cpp | 78 +++++++-- libnymea-core/ruleengine.cpp | 239 +++++++++++++++------------ libnymea-core/time/timeeventitem.cpp | 2 +- libnymea-core/time/timeeventitem.h | 2 +- libnymea/types/ruleaction.cpp | 16 +- libnymea/types/ruleaction.h | 1 + libnymea/types/ruleactionparam.cpp | 148 +++++++++++++---- libnymea/types/ruleactionparam.h | 29 +++- tests/auto/rules/testrules.cpp | 95 ++++++++++- 10 files changed, 467 insertions(+), 168 deletions(-) diff --git a/libnymea-core/jsonrpc/jsontypes.cpp b/libnymea-core/jsonrpc/jsontypes.cpp index f8b87dc6..8e1727ac 100644 --- a/libnymea-core/jsonrpc/jsontypes.cpp +++ b/libnymea-core/jsonrpc/jsontypes.cpp @@ -188,6 +188,8 @@ void JsonTypes::init() s_ruleActionParam.insert("o:value", basicTypeRef()); s_ruleActionParam.insert("o:eventTypeId", basicTypeToString(Uuid)); s_ruleActionParam.insert("o:eventParamTypeId", basicTypeToString(Uuid)); + s_ruleActionParam.insert("o:deviceId", basicTypeToString(Uuid)); + s_ruleActionParam.insert("o:stateTypeId", basicTypeToString(Uuid)); // ParamDescriptor s_paramDescriptor.insert("o:paramTypeId", basicTypeToString(Uuid)); @@ -612,10 +614,13 @@ QVariantMap JsonTypes::packRuleActionParam(const RuleActionParam &ruleActionPara } else { variantMap.insert("paramName", ruleActionParam.paramName()); } - // if this ruleaction param has a valid EventTypeId, there is no value - if (ruleActionParam.eventTypeId() != EventTypeId()) { + + if (ruleActionParam.isEventBased()) { variantMap.insert("eventTypeId", ruleActionParam.eventTypeId().toString()); variantMap.insert("eventParamTypeId", ruleActionParam.eventParamTypeId().toString()); + } else if (ruleActionParam.isStateBased()) { + variantMap.insert("deviceId", ruleActionParam.deviceId().toString()); + variantMap.insert("stateTypeId", ruleActionParam.stateTypeId().toString()); } else { variantMap.insert("value", ruleActionParam.value()); } @@ -1429,13 +1434,19 @@ RuleActionParam JsonTypes::unpackRuleActionParam(const QVariantMap &ruleActionPa ParamTypeId paramTypeId = ParamTypeId(ruleActionParamMap.value("paramTypeId").toString()); QString paramName = ruleActionParamMap.value("paramName").toString(); - QVariant value = ruleActionParamMap.value("value"); - EventTypeId eventTypeId = EventTypeId(ruleActionParamMap.value("eventTypeId").toString()); - ParamTypeId eventParamTypeId = ParamTypeId(ruleActionParamMap.value("eventParamTypeId").toString()); + + RuleActionParam param; if (paramTypeId.isNull()) { - return RuleActionParam(paramName, value, eventTypeId, eventParamTypeId); + param = RuleActionParam(paramName); + } else { + param = RuleActionParam(paramTypeId); } - return RuleActionParam(paramTypeId, value, eventTypeId, eventParamTypeId); + param.setValue(ruleActionParamMap.value("value")); + param.setEventTypeId(EventTypeId(ruleActionParamMap.value("eventTypeId").toString())); + param.setEventParamTypeId(ParamTypeId(ruleActionParamMap.value("eventParamTypeId").toString())); + param.setDeviceId(DeviceId(ruleActionParamMap.value("deviceId").toString())); + param.setStateTypeId(StateTypeId(ruleActionParamMap.value("stateTypeId").toString())); + return param; } /*! Returns a \l{RuleActionParamList} created from the given \a ruleActionParamList. */ diff --git a/libnymea-core/nymeacore.cpp b/libnymea-core/nymeacore.cpp index ea9ebff1..e2b402b7 100644 --- a/libnymea-core/nymeacore.cpp +++ b/libnymea-core/nymeacore.cpp @@ -438,31 +438,85 @@ void NymeaCore::executeRuleActions(const QList ruleActions) QList actions; foreach (const RuleAction &ruleAction, ruleActions) { if (ruleAction.type() == RuleAction::TypeDevice) { - actions.append(ruleAction.toAction()); + Device *device = m_deviceManager->findConfiguredDevice(ruleAction.deviceId()); + ActionTypeId actionTypeId = ruleAction.actionTypeId(); + ParamList params; + bool ok = true; + foreach (const RuleActionParam &ruleActionParam, ruleAction.ruleActionParams()) { + if (ruleActionParam.isValueBased()) { + params.append(Param(ruleActionParam.paramTypeId(), ruleActionParam.value())); + } else if (ruleActionParam.isStateBased()) { + Device *stateDevice = m_deviceManager->findConfiguredDevice(ruleActionParam.deviceId()); + if (!stateDevice) { + qCWarning(dcRuleEngine()) << "Cannot find device" << ruleActionParam.deviceId() << "required by rule action" << ruleAction.id(); + ok = false; + break; + } + DeviceClass stateDeviceClass = m_deviceManager->findDeviceClass(stateDevice->deviceClassId()); + if (!stateDeviceClass.hasStateType(ruleActionParam.stateTypeId())) { + qCWarning(dcRuleEngine()) << "Device" << device->name() << device->id() << "does not have a state type" << ruleActionParam.stateTypeId(); + ok = false; + break; + } + params.append(Param(ruleActionParam.paramTypeId(), stateDevice->stateValue(ruleActionParam.stateTypeId()))); + } + } + if (!ok) { + qCWarning(dcRuleEngine()) << "Not executing rule action" << ruleAction.id(); + continue; + } + Action action(actionTypeId, device->id()); + action.setParams(params); + actions.append(action); } else { QList devices = m_deviceManager->findConfiguredDevices(ruleAction.interface()); foreach (Device* device, devices) { - DeviceClass dc = m_deviceManager->findDeviceClass(device->deviceClassId()); - ActionType at = dc.actionTypes().findByName(ruleAction.interfaceAction()); - if (at.id().isNull()) { + DeviceClass deviceClass = m_deviceManager->findDeviceClass(device->deviceClassId()); + ActionType actionType = deviceClass.actionTypes().findByName(ruleAction.interfaceAction()); + if (actionType.id().isNull()) { qCWarning(dcRuleEngine()) << "Error creating Action. The given DeviceClass does not implement action:" << ruleAction.interfaceAction(); continue; } - Action action = Action(at.id(), device->id()); + ParamList params; - foreach (const RuleActionParam &rap, ruleAction.ruleActionParams()) { - ParamType pt = at.paramTypes().findByName(rap.paramName()); - if (pt.id().isNull()) { - qCWarning(dcRuleEngine()) << "Error creating Action. Failed to match interface param type to DeviceClass paramtype."; + bool ok = true; + foreach (const RuleActionParam &ruleActionParam, ruleAction.ruleActionParams()) { + ParamType paramType = actionType.paramTypes().findByName(ruleActionParam.paramName()); + if (paramType.id().isNull()) { + qCWarning(dcRuleEngine()) << "Error creating Action. The given ActionType does not have a parameter:" << ruleActionParam.paramName(); + ok = false; continue; } - params.append(Param(pt.id(), rap.value())); + if (ruleActionParam.isValueBased()) { + params.append(Param(paramType.id(), ruleActionParam.value())); + } else if (ruleActionParam.isStateBased()) { + Device *stateDevice = m_deviceManager->findConfiguredDevice(ruleActionParam.deviceId()); + if (!stateDevice) { + qCWarning(dcRuleEngine()) << "Cannot find device" << ruleActionParam.deviceId() << "required by rule action" << ruleAction.id(); + ok = false; + break; + } + DeviceClass stateDeviceClass = m_deviceManager->findDeviceClass(stateDevice->deviceClassId()); + if (!stateDeviceClass.hasStateType(ruleActionParam.stateTypeId())) { + qCWarning(dcRuleEngine()) << "Device" << device->name() << device->id() << "does not have a state type" << ruleActionParam.stateTypeId(); + ok = false; + break; + } + params.append(Param(paramType.id(), stateDevice->stateValue(ruleActionParam.stateTypeId()))); + } } + if (!ok) { + qCWarning(dcRuleEngine()) << "Not executing rule action" << ruleAction.id(); + continue; + } + + Action action = Action(actionType.id(), device->id()); action.setParams(params); actions.append(action); } } } + foreach (const Action &action, actions) { qCDebug(dcRuleEngine) << "Executing action" << action.actionTypeId() << action.params(); DeviceManager::DeviceError status = executeAction(action); @@ -652,9 +706,7 @@ void NymeaCore::gotEvent(const Event &event) foreach (RuleActionParam ruleActionParam, ruleAction.ruleActionParams()) { // if this event param should be taken over in this action if (event.eventTypeId() == ruleActionParam.eventTypeId()) { - QVariant eventValue = event.params().first().value(); - - // TODO: get param names...when an event has more than one parameter + QVariant eventValue = event.params().paramValue(ruleActionParam.eventParamTypeId()); // TODO: limits / scale calculation -> actionValue = eventValue * x // something like a EventParamDescriptor diff --git a/libnymea-core/ruleengine.cpp b/libnymea-core/ruleengine.cpp index 3edbbe28..dc9b870c 100644 --- a/libnymea-core/ruleengine.cpp +++ b/libnymea-core/ruleengine.cpp @@ -401,55 +401,6 @@ RuleEngine::RuleError RuleEngine::addRule(const Rule &rule, bool fromEdit) return RuleErrorActionTypeNotFound; } - // check possible eventTypeIds in params - if (action.isEventBased()) { - foreach (const RuleActionParam &ruleActionParam, action.ruleActionParams()) { - if (ruleActionParam.eventTypeId() != EventTypeId()) { - // We have an eventTypeId - if (rule.eventDescriptors().isEmpty()) { - qCWarning(dcRuleEngine) << "Cannot create rule. RuleAction" << action.actionTypeId() << "contains an eventTypeId, but there are no eventDescriptors."; - return RuleErrorInvalidRuleActionParameter; - } - - // now check if this eventType is in the eventDescriptorList of this rule - if (!checkEventDescriptors(rule.eventDescriptors(), ruleActionParam.eventTypeId())) { - qCWarning(dcRuleEngine) << "Cannot create rule. EventTypeId from RuleAction" << action.actionTypeId() << "not in eventDescriptors."; - return RuleErrorInvalidRuleActionParameter; - } - - // check if the param type of the event and the action match - QVariant::Type eventParamType = getEventParamType(ruleActionParam.eventTypeId(), ruleActionParam.eventParamTypeId()); - QVariant v(eventParamType); - QVariant::Type actionParamType = getActionParamType(action.actionTypeId(), ruleActionParam.paramTypeId()); - if (eventParamType != actionParamType && !v.canConvert(actionParamType)) { - qCWarning(dcRuleEngine) << "Cannot create rule. RuleActionParam" << ruleActionParam.paramTypeId().toString() << " and given event param " << ruleActionParam.eventParamTypeId().toString() << "have not the same type:"; - qCWarning(dcRuleEngine) << " -> actionParamType:" << actionParamType; - qCWarning(dcRuleEngine) << " -> eventParamType:" << eventParamType; - return RuleErrorTypesNotMatching; - } - } - } - } else { - // verify action params - foreach (const ActionType &actionType, deviceClass.actionTypes()) { - if (actionType.id() == action.actionTypeId()) { - ParamList finalParams = action.toAction().params(); - DeviceManager::DeviceError paramCheck = NymeaCore::instance()->deviceManager()->verifyParams(actionType.paramTypes(), finalParams); - if (paramCheck != DeviceManager::DeviceErrorNoError) { - qCWarning(dcRuleEngine) << "Cannot create rule. Got an invalid actionParam."; - return RuleErrorInvalidRuleActionParameter; - } - } - } - } - - foreach (const RuleActionParam &ruleActionParam, action.ruleActionParams()) { - if (!ruleActionParam.isValid()) { - qCWarning(dcRuleEngine) << "Cannot create rule. Got an actionParam with \"value\" AND \"eventTypeId\"."; - return RuleEngine::RuleErrorInvalidRuleActionParameter; - } - } - } else { // Is TypeInterface Interface iface = NymeaCore::instance()->deviceManager()->supportedInterfaces().findByName(action.interface()); if (!iface.isValid()) { @@ -471,77 +422,145 @@ RuleEngine::RuleError RuleEngine::addRule(const Rule &rule, bool fromEdit) return RuleError::RuleErrorInvalidParameter; } } - // TODO: Check params + } + + foreach (const RuleActionParam &ruleActionParam, action.ruleActionParams()) { + if (ruleActionParam.isEventBased()) { + // We have an eventTypeId, see if the rule actually has such a event + if (rule.eventDescriptors().isEmpty() || !checkEventDescriptors(rule.eventDescriptors(), ruleActionParam.eventTypeId())) { + qCWarning(dcRuleEngine) << "Cannot create rule. EventTypeId from RuleAction" << action.actionTypeId() << "not in eventDescriptors."; + return RuleErrorInvalidRuleActionParameter; + } + + // check if the param type of the event and the action match + QVariant::Type eventParamType = getEventParamType(ruleActionParam.eventTypeId(), ruleActionParam.eventParamTypeId()); + QVariant v(eventParamType); + QVariant::Type actionParamType = getActionParamType(action.actionTypeId(), ruleActionParam.paramTypeId()); + if (eventParamType != actionParamType && !v.canConvert(static_cast(actionParamType))) { + qCWarning(dcRuleEngine) << "Cannot create rule. RuleActionParam" << ruleActionParam.paramTypeId().toString() << " and given event param " << ruleActionParam.eventParamTypeId().toString() << "have not the same type:"; + qCWarning(dcRuleEngine) << " -> actionParamType:" << actionParamType; + qCWarning(dcRuleEngine) << " -> eventParamType:" << eventParamType; + return RuleErrorTypesNotMatching; + } + } else if (ruleActionParam.isStateBased()) { + Device *d = NymeaCore::instance()->deviceManager()->findConfiguredDevice(ruleActionParam.deviceId()); + if (!d) { + qCWarning(dcRuleEngine()) << "Cannot create Rule. DeviceId from RuleAction" << action.actionTypeId() << "not found in system."; + return RuleErrorDeviceNotFound; + } + DeviceClass stateDeviceClass = NymeaCore::instance()->deviceManager()->findDeviceClass(d->deviceClassId()); + StateType stateType = stateDeviceClass.stateTypes().findById(ruleActionParam.stateTypeId()); + QVariant::Type actionParamType = getActionParamType(action.actionTypeId(), ruleActionParam.paramTypeId()); + QVariant v(stateType.type()); + if (actionParamType != stateType.type() && !v.canConvert(static_cast(actionParamType))) { + qCWarning(dcRuleEngine) << "Cannot create rule. RuleActionParam" << ruleActionParam.paramTypeId().toString() << " and given state based param " << ruleActionParam.stateTypeId().toString() << "have not the same type:"; + qCWarning(dcRuleEngine) << " -> actionParamType:" << actionParamType; + qCWarning(dcRuleEngine) << " -> stateType:" << stateType.type(); + return RuleErrorTypesNotMatching; + } + } else { + if (ruleActionParam.value().isNull()) { + qCDebug(dcRuleEngine()) << "Cannot create rule. No param value given for action:" << ruleActionParam.paramTypeId().toString(); + return RuleErrorInvalidRuleActionParameter; + } + QVariant::Type actionParamType = getActionParamType(action.actionTypeId(), ruleActionParam.paramTypeId()); + if (ruleActionParam.value().type() != actionParamType && !ruleActionParam.value().canConvert(static_cast(actionParamType))) { + qCDebug(dcRuleEngine()) << "Cannot create rule. Given param value for action" << ruleActionParam.paramTypeId().toString() << "does not match type"; + return RuleErrorInvalidRuleActionParameter; + } + } + } + + foreach (const RuleActionParam &ruleActionParam, action.ruleActionParams()) { + if (!ruleActionParam.isValid()) { + qCWarning(dcRuleEngine) << "Cannot create rule. There must be only one out of \"value\", \"eventTypeId/eventParamTypeID\" or \"deviceId/stateTypeId\"."; + return RuleEngine::RuleErrorInvalidRuleActionParameter; + } } } // Check exit actions - foreach (const RuleAction &ruleAction, rule.exitActions()) { - if (!ruleAction.isValid()) { + foreach (const RuleAction &ruleExitAction, rule.exitActions()) { + if (!ruleExitAction.isValid()) { qWarning(dcRuleEngine()) << "Exit Action is incomplete. It must have either actionTypeId and deviceId, or interface and interfaceAction"; return RuleErrorActionTypeNotFound; } - if (ruleAction.type() == RuleAction::TypeDevice) { - Device *device = NymeaCore::instance()->deviceManager()->findConfiguredDevice(ruleAction.deviceId()); + if (ruleExitAction.type() == RuleAction::TypeDevice) { + Device *device = NymeaCore::instance()->deviceManager()->findConfiguredDevice(ruleExitAction.deviceId()); if (!device) { - qCWarning(dcRuleEngine) << "Cannot create rule. No configured device for exit action with actionTypeId" << ruleAction.actionTypeId(); + qCWarning(dcRuleEngine) << "Cannot create rule. No configured device for exit action with actionTypeId" << ruleExitAction.actionTypeId(); return RuleErrorDeviceNotFound; } DeviceClass deviceClass = NymeaCore::instance()->deviceManager()->findDeviceClass(device->deviceClassId()); - if (!deviceClass.hasActionType(ruleAction.actionTypeId())) { - qCWarning(dcRuleEngine) << "Cannot create rule. Device " + device->name() + " has no action type:" << ruleAction.actionTypeId(); + if (!deviceClass.hasActionType(ruleExitAction.actionTypeId())) { + qCWarning(dcRuleEngine) << "Cannot create rule. Device " + device->name() + " has no action type:" << ruleExitAction.actionTypeId(); return RuleErrorActionTypeNotFound; } - // verify action params - foreach (const ActionType &actionType, deviceClass.actionTypes()) { - if (actionType.id() == ruleAction.actionTypeId()) { - ParamList finalParams = ruleAction.toAction().params(); - DeviceManager::DeviceError paramCheck = NymeaCore::instance()->deviceManager()->verifyParams(actionType.paramTypes(), finalParams); - if (paramCheck != DeviceManager::DeviceErrorNoError) { - qCWarning(dcRuleEngine) << "Cannot create rule. Got an invalid exit actionParam."; - return RuleErrorInvalidRuleActionParameter; - } - } - } - - // Exit action can never be event based. - if (ruleAction.isEventBased()) { - qCWarning(dcRuleEngine) << "Cannot create rule. Got exitAction with an actionParam containing an eventTypeId. "; - return RuleErrorInvalidRuleActionParameter; - } - - foreach (const RuleActionParam &ruleActionParam, ruleAction.ruleActionParams()) { - if (!ruleActionParam.isValid()) { - qCWarning(dcRuleEngine) << "Cannot create rule. Got an actionParam with \"value\" AND \"eventTypeId\"."; - return RuleEngine::RuleErrorInvalidRuleActionParameter; - } - } - } else { // Is TypeInterface - Interface iface = NymeaCore::instance()->deviceManager()->supportedInterfaces().findByName(ruleAction.interface()); + Interface iface = NymeaCore::instance()->deviceManager()->supportedInterfaces().findByName(ruleExitAction.interface()); if (!iface.isValid()) { - qCWarning(dcRuleEngine()) << "Cannot create rule. No such interface:" << ruleAction.interface(); + qCWarning(dcRuleEngine()) << "Cannot create rule. No such interface:" << ruleExitAction.interface(); return RuleError::RuleErrorInterfaceNotFound; } - ActionType ifaceActionType = iface.actionTypes().findByName(ruleAction.interfaceAction()); + ActionType ifaceActionType = iface.actionTypes().findByName(ruleExitAction.interfaceAction()); if (ifaceActionType.name().isEmpty()) { - qCWarning(dcRuleEngine()) << "Cannot create rule. Interface" << iface.name() << "does not implement action" << ruleAction.interfaceAction(); + qCWarning(dcRuleEngine()) << "Cannot create rule. Interface" << iface.name() << "does not implement action" << ruleExitAction.interfaceAction(); return RuleError::RuleErrorActionTypeNotFound; } foreach (const ParamType &ifaceActionParamType, ifaceActionType.paramTypes()) { - if (!ruleAction.ruleActionParams().hasParam(ifaceActionParamType.name())) { - qCWarning(dcRuleEngine()) << "Cannot create rule. Interface action" << iface.name() << ":" << ruleAction.interfaceAction() << "requires a" << ifaceActionParamType.name() << "param of type" << QVariant::typeToName(ifaceActionParamType.type()); + if (!ruleExitAction.ruleActionParams().hasParam(ifaceActionParamType.name())) { + qCWarning(dcRuleEngine()) << "Cannot create rule. Interface action" << iface.name() << ":" << ruleExitAction.interfaceAction() << "requires a" << ifaceActionParamType.name() << "param of type" << QVariant::typeToName(ifaceActionParamType.type()); return RuleError::RuleErrorMissingParameter; } - if (!ruleAction.ruleActionParam(ifaceActionParamType.name()).value().canConvert(ifaceActionParamType.type())) { - qCWarning(dcRuleEngine()) << "Cannot create rule. Interface action parameter" << iface.name() << ":" << ruleAction.interfaceAction() << ":" << ifaceActionParamType.name() << "has wrong type. Expected" << QVariant::typeToName(ifaceActionParamType.type()); + if (!ruleExitAction.ruleActionParam(ifaceActionParamType.name()).value().canConvert(ifaceActionParamType.type())) { + qCWarning(dcRuleEngine()) << "Cannot create rule. Interface action parameter" << iface.name() << ":" << ruleExitAction.interfaceAction() << ":" << ifaceActionParamType.name() << "has wrong type. Expected" << QVariant::typeToName(ifaceActionParamType.type()); return RuleError::RuleErrorInvalidParameter; } } - // TODO: Check params + } + + foreach (const RuleActionParam &ruleActionParam, ruleExitAction.ruleActionParams()) { + if (ruleActionParam.isEventBased()) { + // We have an eventTypeId, see if the rule actually has such a event + qCWarning(dcRuleEngine) << "Cannot create rule. Exit actions cannot be event based."; + return RuleErrorInvalidRuleActionParameter; + } else if (ruleActionParam.isStateBased()) { + Device *d = NymeaCore::instance()->deviceManager()->findConfiguredDevice(ruleActionParam.deviceId()); + if (!d) { + qCWarning(dcRuleEngine()) << "Cannot create Rule. DeviceId from RuleAction" << ruleExitAction.actionTypeId() << "not found in system."; + return RuleErrorDeviceNotFound; + } + DeviceClass stateDeviceClass = NymeaCore::instance()->deviceManager()->findDeviceClass(d->deviceClassId()); + StateType stateType = stateDeviceClass.stateTypes().findById(ruleActionParam.stateTypeId()); + QVariant::Type actionParamType = getActionParamType(ruleExitAction.actionTypeId(), ruleActionParam.paramTypeId()); + QVariant v(stateType.type()); + if (actionParamType != stateType.type() && !v.canConvert(static_cast(actionParamType))) { + qCWarning(dcRuleEngine) << "Cannot create rule. RuleActionParam" << ruleActionParam.paramTypeId().toString() << " and given state based param " << ruleActionParam.stateTypeId().toString() << "have not the same type:"; + qCWarning(dcRuleEngine) << " -> actionParamType:" << actionParamType; + qCWarning(dcRuleEngine) << " -> stateType:" << stateType.type(); + return RuleErrorTypesNotMatching; + } + } else { + if (ruleActionParam.value().isNull()) { + qCDebug(dcRuleEngine()) << "Cannot create rule. No param value given for action:" << ruleActionParam.paramTypeId().toString(); + return RuleErrorInvalidRuleActionParameter; + } + QVariant::Type actionParamType = getActionParamType(ruleExitAction.actionTypeId(), ruleActionParam.paramTypeId()); + if (ruleActionParam.value().type() != actionParamType && !ruleActionParam.value().canConvert(static_cast(actionParamType))) { + qCDebug(dcRuleEngine()) << "Cannot create rule. Given param value for action" << ruleActionParam.paramTypeId().toString() << "does not match type"; + return RuleErrorInvalidRuleActionParameter; + } + } + } + + foreach (const RuleActionParam &ruleActionParam, ruleExitAction.ruleActionParams()) { + if (!ruleActionParam.isValid()) { + qCWarning(dcRuleEngine) << "Cannot create rule. There must be only one out of \"value\", \"eventTypeId/eventParamTypeID\" or \"deviceId/stateTypeId\"."; + return RuleEngine::RuleErrorInvalidRuleActionParameter; + } } } @@ -698,9 +717,9 @@ RuleEngine::RuleError RuleEngine::disableRule(const RuleId &ruleId) */ RuleEngine::RuleError RuleEngine::executeActions(const RuleId &ruleId) { - // check if rule exits + // check if rule exists if (!m_rules.contains(ruleId)) { - qCWarning(dcRuleEngine) << "Not executing rule actions: rule not found."; + qCWarning(dcRuleEngine) << "Not executing rule actions: Rule not found."; return RuleErrorRuleNotFound; } @@ -1203,11 +1222,14 @@ void RuleEngine::saveRule(const Rule &rule) } else { settings.beginGroup("RuleActionParam-" + param.paramName()); } - settings.setValue("valueType", (int)param.value().type()); + settings.setValue("valueType", static_cast(param.value().type())); settings.setValue("value", param.value()); - if (param.eventTypeId() != EventTypeId()) { + if (param.isEventBased()) { settings.setValue("eventTypeId", param.eventTypeId().toString()); settings.setValue("eventParamTypeId", param.eventParamTypeId()); + } else if (param.isStateBased()) { + settings.setValue("deviceId", param.deviceId().toString()); + settings.setValue("stateTypeId", param.stateTypeId()); } settings.endGroup(); } @@ -1276,7 +1298,7 @@ void RuleEngine::init() QList weekDays; QList monthDays; - RepeatingOption::RepeatingMode mode = (RepeatingOption::RepeatingMode)settings.value("mode", 0).toInt(); + RepeatingOption::RepeatingMode mode = static_cast(settings.value("mode", 0).toInt()); // Load weekDays int weekDaysCount = settings.beginReadArray("weekDays"); @@ -1313,7 +1335,7 @@ void RuleEngine::init() QList weekDays; QList monthDays; - RepeatingOption::RepeatingMode mode = (RepeatingOption::RepeatingMode)settings.value("mode", 0).toInt(); + RepeatingOption::RepeatingMode mode = static_cast(settings.value("mode", 0).toInt()); // Load weekDays int weekDaysCount = settings.beginReadArray("weekDays"); @@ -1414,6 +1436,8 @@ void RuleEngine::init() QString strippedParamTypeIdString = paramTypeIdString.remove(QRegExp("^RuleActionParam-")); EventTypeId eventTypeId = EventTypeId(settings.value("eventTypeId", EventTypeId()).toString()); ParamTypeId eventParamTypeId = ParamTypeId(settings.value("eventParamTypeId", ParamTypeId()).toString()); + DeviceId deviceId = DeviceId(settings.value("deviceId", DeviceId()).toString()); + StateTypeId stateTypeId = StateTypeId(settings.value("stateTypeId", StateTypeId()).toString()); QVariant value = settings.value("value"); if (settings.contains("valueType")) { QVariant::Type valueType = (QVariant::Type)settings.value("valueType").toInt(); @@ -1427,19 +1451,20 @@ void RuleEngine::init() } } + RuleActionParam param; if (!ParamTypeId(strippedParamTypeIdString).isNull()) { - RuleActionParam param(ParamTypeId(strippedParamTypeIdString), - value, - eventTypeId, - eventParamTypeId); - params.append(param); + // By ParamTypeId + param = RuleActionParam(ParamTypeId(strippedParamTypeIdString), value); } else { - RuleActionParam param(strippedParamTypeIdString, - value, - eventTypeId, - eventParamTypeId); - params.append(param); + // By param name + param = RuleActionParam(strippedParamTypeIdString, value); } + param.setEventTypeId(eventTypeId); + param.setEventParamTypeId(eventParamTypeId); + param.setDeviceId(deviceId); + param.setStateTypeId(stateTypeId); + params.append(param); + params.append(param); settings.endGroup(); } } diff --git a/libnymea-core/time/timeeventitem.cpp b/libnymea-core/time/timeeventitem.cpp index 3ef56957..b4fa53f8 100644 --- a/libnymea-core/time/timeeventitem.cpp +++ b/libnymea-core/time/timeeventitem.cpp @@ -48,7 +48,7 @@ QDateTime TimeEventItem::dateTime() const } /*! Sets the dateTime of this \l{TimeEventItem} to the given \a timeStamp. */ -void TimeEventItem::setDateTime(const int &timeStamp) +void TimeEventItem::setDateTime(const uint &timeStamp) { m_dateTime = QDateTime::fromTime_t(timeStamp); } diff --git a/libnymea-core/time/timeeventitem.h b/libnymea-core/time/timeeventitem.h index c91f0be2..60a2a82a 100644 --- a/libnymea-core/time/timeeventitem.h +++ b/libnymea-core/time/timeeventitem.h @@ -33,7 +33,7 @@ public: TimeEventItem(); QDateTime dateTime() const; - void setDateTime(const int &timeStamp); + void setDateTime(const uint &timeStamp); QTime time() const; void setTime(const QTime &time); diff --git a/libnymea/types/ruleaction.cpp b/libnymea/types/ruleaction.cpp index 17feaffd..08882ccd 100644 --- a/libnymea/types/ruleaction.cpp +++ b/libnymea/types/ruleaction.cpp @@ -99,7 +99,17 @@ RuleAction::Type RuleAction::type() const bool RuleAction::isEventBased() const { foreach (const RuleActionParam ¶m, m_ruleActionParams) { - if (param.eventTypeId() != EventTypeId()) { + if (param.isEventBased()) { + return true; + } + } + return false; +} + +bool RuleAction::isStateBased() const +{ + foreach (const RuleActionParam ¶m, m_ruleActionParams) { + if (param.isStateBased()) { return true; } } @@ -167,7 +177,7 @@ RuleActionParam RuleAction::ruleActionParam(const ParamTypeId &ruleActionParamTy return ruleActionParam; } } - return RuleActionParam(QString()); + return RuleActionParam(); } /*! Returns the \l{RuleActionParam} of this RuleAction with the given \a ruleActionParamName. @@ -180,7 +190,7 @@ RuleActionParam RuleAction::ruleActionParam(const QString &ruleActionParamName) return ruleActionParam; } } - return RuleActionParam(QString()); + return RuleActionParam(); } /*! Copy the data to a \l{RuleAction} from an \a other rule action. */ diff --git a/libnymea/types/ruleaction.h b/libnymea/types/ruleaction.h index 75b6d72d..98dee3b9 100644 --- a/libnymea/types/ruleaction.h +++ b/libnymea/types/ruleaction.h @@ -45,6 +45,7 @@ public: Type type() const; bool isEventBased() const; + bool isStateBased() const; Action toAction() const; diff --git a/libnymea/types/ruleactionparam.cpp b/libnymea/types/ruleactionparam.cpp index a60943de..91737fae 100644 --- a/libnymea/types/ruleactionparam.cpp +++ b/libnymea/types/ruleactionparam.cpp @@ -32,6 +32,21 @@ A RuleActionParam allows rules to take over an \l{Event} parameter into a rule \l{RuleAction}. + RuleActionParams are identified by either paramTypeId or paramName (for interface based actions). + + The parameter value can either be a static \l{value}, a pair of \l{EventTypeId} and \l{ParamTypeId} or a pair of + \l{DeviceId} and \l{StateTypeId}. + + When composing the actual Param for the executeAction() call the value is generated as follows: + - Static value params are filled with \l{RuleActionParam::paramTypeId()} and the \l{RuleActionParam::value()} + - Event based actions are filled with \l{RuleActionParam::paramTypeId()} and the param value of the event that triggered this rule, identified by \l{RuleActionParam::eventTypeId()} and \l{RuleActionParam::eventParamTypeId()} + - State based actions are filled with \l{RuleActionParam::paramTypeId()} and the current value of the state identified by \l{RuleActionParam::deviceId()} and \l{RuleActionParam::stateTypeId()} + + If the param types are not matching, nymea will do a best effort to cast the values. E.g. a RuleActionParam for + a param of type "string" and a state of type "int" would cast the integer to a string which would always work. + However, the other way round, having a parameter requiring an "int" value, and reading the value from a state of type + "string" might work, if the string does only hold numbers but would fail. + \sa nymeaserver::Rule, RuleAction, */ @@ -41,33 +56,68 @@ * \sa Param, */ RuleActionParam::RuleActionParam(const Param ¶m) : m_paramTypeId(param.paramTypeId()), - m_value(param.value()), - m_eventTypeId(EventTypeId()), - m_eventParamTypeId(ParamTypeId()) + m_value(param.value()) { } -/*! Constructs a \l{RuleActionParam} with the given \a paramTypeId, \a value, \a eventTypeId and \a eventParamTypeId. +/*! Constructs a \l{RuleActionParam} with the given \a paramTypeId and \a value. * \sa Param, Event, */ -RuleActionParam::RuleActionParam(const ParamTypeId ¶mTypeId, const QVariant &value, const EventTypeId &eventTypeId, const ParamTypeId &eventParamTypeId) : +RuleActionParam::RuleActionParam(const ParamTypeId ¶mTypeId, const QVariant &value): + m_paramTypeId(paramTypeId), + m_value(value) +{ + +} + +/*! Constructs a \l{RuleActionParam} with the given \a paramTypeId, \a eventTypeId and \a eventParamTypeId. + * \sa Param, Event, */ +RuleActionParam::RuleActionParam(const ParamTypeId ¶mTypeId, const EventTypeId &eventTypeId, const ParamTypeId &eventParamTypeId): m_paramTypeId(paramTypeId), - m_value(value), m_eventTypeId(eventTypeId), m_eventParamTypeId(eventParamTypeId) { + } -/*! Constructs a \l{RuleActionParam} with the given \a paramName, \a value, \a eventTypeId and \a eventParamTypeId. +/*! Constructs a \l{RuleActionParam} with the given \a paramTypeId, \a deviceId and \a stateTypeId. * \sa Param, Event, */ -RuleActionParam::RuleActionParam(const QString ¶mName, const QVariant &value, const EventTypeId &eventTypeId, const ParamTypeId &eventParamTypeId): +RuleActionParam::RuleActionParam(const ParamTypeId ¶mTypeId, const DeviceId &deviceId, const StateTypeId &stateTypeId): + m_paramTypeId(paramTypeId), + m_deviceId(deviceId), + m_stateTypeId(stateTypeId) +{ + +} + +/*! Constructs a \l{RuleActionParam} with the given \a paramName and \a value. + * \sa Param, Event, */ +RuleActionParam::RuleActionParam(const QString ¶mName, const QVariant &value): + m_paramName(paramName), + m_value(value) +{ + +} + +/*! Constructs a \l{RuleActionParam} with the given \a paramName, \a eventTypeId and \a eventParamTypeId. + * \sa Param, Event, */ +RuleActionParam::RuleActionParam(const QString ¶mName, const EventTypeId &eventTypeId, const ParamTypeId &eventParamTypeId): m_paramName(paramName), - m_value(value), m_eventTypeId(eventTypeId), m_eventParamTypeId(eventParamTypeId) { } +/*! Constructs a \l{RuleActionParam} with the given \a paramName, \a deviceId and \a stateTypeId. + * \sa Param, Event, */ +RuleActionParam::RuleActionParam(const QString ¶mName, const DeviceId &deviceId, const StateTypeId &stateTypeId): + m_paramName(paramName), + m_deviceId(deviceId), + m_stateTypeId(stateTypeId) +{ + +} + /*! Returns the \l ParamTypeId of this \l RuleActionParam. */ ParamTypeId RuleActionParam::paramTypeId() const { @@ -80,18 +130,6 @@ QString RuleActionParam::paramName() const return m_paramName; } -/*! Returns the eventParamTypeId of this RuleActionParam. */ -ParamTypeId RuleActionParam::eventParamTypeId() const -{ - return m_eventParamTypeId; -} - -/*! Sets the \a eventParamTypeId of this RuleActionParam. */ -void RuleActionParam::setEventParamTypeId(const ParamTypeId &eventParamTypeId) -{ - m_eventParamTypeId = eventParamTypeId; -} - /*! Returns the value of this RuleActionParam. */ QVariant RuleActionParam::value() const { @@ -104,14 +142,6 @@ void RuleActionParam::setValue(const QVariant &value) m_value = value; } -/*! Returns true if the \tt{(paramTypeId AND value) XOR (paramTypeId AND eventTypeId AND eventParamName)} of this RuleActionParam are set.*/ -bool RuleActionParam::isValid() const -{ - bool validValue = (!m_paramTypeId.isNull() && m_value.isValid() && m_eventTypeId.isNull() && m_eventParamTypeId.isNull()); - bool validEvent = (!m_paramTypeId.isNull() && !m_value.isValid() && !m_eventTypeId.isNull() && !m_eventParamTypeId.isNull()); - return validValue ^ validEvent; -} - /*! Return the EventTypeId of the \l{Event} with the \l{Param} which will be taken over in the \l{RuleAction}. */ EventTypeId RuleActionParam::eventTypeId() const { @@ -124,6 +154,66 @@ void RuleActionParam::setEventTypeId(const EventTypeId &eventTypeId) m_eventTypeId = eventTypeId; } +/*! Returns the eventParamTypeId of this RuleActionParam. */ +ParamTypeId RuleActionParam::eventParamTypeId() const +{ + return m_eventParamTypeId; +} + +/*! Sets the \a eventParamTypeId of this RuleActionParam. */ +void RuleActionParam::setEventParamTypeId(const ParamTypeId &eventParamTypeId) +{ + m_eventParamTypeId = eventParamTypeId; +} + +/*! Returns the deviceId identifying the device to use a state value from. */ +DeviceId RuleActionParam::deviceId() const +{ + return m_deviceId; +} + +/*! Sets the deviceId identifying the device to use a state value from. */ +void RuleActionParam::setDeviceId(const DeviceId &deviceId) +{ + m_deviceId = deviceId; +} + +/*! Returns the stateTypeId identifying the state to use the value. */ +StateTypeId RuleActionParam::stateTypeId() const +{ + return m_stateTypeId; +} + +/*! Sets the stateTypeId identifying the state to use the value from. */ +void RuleActionParam::setStateTypeId(const StateTypeId &stateTypeId) +{ + m_stateTypeId = stateTypeId; +} + +/*! Returns true if the \tt{(paramTypeId AND value) XOR (paramTypeId AND eventTypeId AND eventParamName)} of this RuleActionParam are set.*/ +bool RuleActionParam::isValid() const +{ + if (m_paramTypeId.isNull() && m_paramName.isNull()) { + return false; + } + return isValueBased() ^ isEventBased() ^ isStateBased(); +} + +bool RuleActionParam::isValueBased() const +{ + return !m_value.isNull(); +} + +bool RuleActionParam::isEventBased() const +{ + return !m_eventTypeId.isNull() && !m_eventParamTypeId.isNull(); +} + +bool RuleActionParam::isStateBased() const +{ + return !m_deviceId.isNull() && !m_stateTypeId.isNull(); +} + /*! Writes the paramTypeId, value, eventId and eventParamTypeId of the given \a ruleActionParam to \a dbg. */ QDebug operator<<(QDebug dbg, const RuleActionParam &ruleActionParam) { diff --git a/libnymea/types/ruleactionparam.h b/libnymea/types/ruleactionparam.h index ad07b1e8..efbfd71f 100644 --- a/libnymea/types/ruleactionparam.h +++ b/libnymea/types/ruleactionparam.h @@ -36,29 +36,46 @@ class LIBNYMEA_EXPORT RuleActionParam { public: RuleActionParam(const Param ¶m = Param()); - RuleActionParam(const ParamTypeId ¶mTypeId, const QVariant &value = QVariant(), const EventTypeId &eventTypeId = EventTypeId(), const ParamTypeId &eventParamTypeId = ParamTypeId()); - RuleActionParam(const QString ¶mName, const QVariant &value = QVariant(), const EventTypeId &eventTypeId = EventTypeId(), const ParamTypeId &eventParamTypeId = ParamTypeId()); + RuleActionParam(const ParamTypeId ¶mTypeId, const QVariant &value = QVariant()); + RuleActionParam(const ParamTypeId ¶mTypeId, const EventTypeId &eventTypeId, const ParamTypeId &eventParamTypeId); + RuleActionParam(const ParamTypeId ¶mTypeId, const DeviceId &deviceId, const StateTypeId &stateTypeId); + RuleActionParam(const QString ¶mName, const QVariant &value = QVariant()); + RuleActionParam(const QString ¶mName, const EventTypeId &eventTypeId, const ParamTypeId &eventParamTypeId); + RuleActionParam(const QString ¶mName, const DeviceId &deviceId, const StateTypeId &stateTypeId); ParamTypeId paramTypeId() const; QString paramName() const; - ParamTypeId eventParamTypeId() const; - void setEventParamTypeId(const ParamTypeId &eventParamTypeId); + bool isValid() const; + bool isValueBased() const; + bool isEventBased() const; + bool isStateBased() const; QVariant value() const; void setValue(const QVariant &value); - bool isValid() const; - EventTypeId eventTypeId() const; void setEventTypeId(const EventTypeId &eventTypeId); + ParamTypeId eventParamTypeId() const; + void setEventParamTypeId(const ParamTypeId &eventParamTypeId); + + DeviceId deviceId() const; + void setDeviceId(const DeviceId &deviceId); + + StateTypeId stateTypeId() const; + void setStateTypeId(const StateTypeId &stateTypeId); + private: ParamTypeId m_paramTypeId; QString m_paramName; QVariant m_value; + EventTypeId m_eventTypeId; ParamTypeId m_eventParamTypeId; + + DeviceId m_deviceId; + StateTypeId m_stateTypeId; }; Q_DECLARE_METATYPE(RuleActionParam) diff --git a/tests/auto/rules/testrules.cpp b/tests/auto/rules/testrules.cpp index 03d29bed..c4efee05 100644 --- a/tests/auto/rules/testrules.cpp +++ b/tests/auto/rules/testrules.cpp @@ -93,9 +93,10 @@ private slots: void enableDisableRule(); void testEventBasedAction(); - void testEventBasedRuleWithExitAction(); + void testStateBasedAction(); + void removePolicyUpdate(); void removePolicyCascade(); void removePolicyUpdateRendersUselessRule(); @@ -2214,6 +2215,98 @@ void TestRules::testEventBasedRuleWithExitAction() } +void TestRules::testStateBasedAction() +{ + QNetworkAccessManager nam; + QSignalSpy spy(&nam, SIGNAL(finished(QNetworkReply*))); + + // Init bool state to true + spy.clear(); + QNetworkRequest request(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(m_mockDevice1Port).arg(mockBoolStateId.toString()).arg(true))); + QNetworkReply *reply = nam.get(request); + spy.wait(); + QCOMPARE(spy.count(), 1); + reply->deleteLater(); + + // Init int state to 11 + spy.clear(); + request = QNetworkRequest(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(m_mockDevice1Port).arg(mockIntStateId.toString()).arg(11))); + reply = nam.get(request); + spy.wait(); + QCOMPARE(spy.count(), 1); + reply->deleteLater(); + + // Add a rule + QVariantMap addRuleParams; + QVariantMap eventDescriptor; + eventDescriptor.insert("eventTypeId", mockEvent1Id); + eventDescriptor.insert("deviceId", m_mockDeviceId); + addRuleParams.insert("eventDescriptors", QVariantList() << eventDescriptor); + addRuleParams.insert("name", "TestRule"); + addRuleParams.insert("enabled", true); + + QVariantList actions; + QVariantMap action; + QVariantList ruleActionParams; + QVariantMap param1; + param1.insert("paramTypeId", mockActionParam1ParamTypeId); + param1.insert("deviceId", m_mockDeviceId); + param1.insert("stateTypeId", mockIntStateId); + QVariantMap param2; + param2.insert("paramTypeId", mockActionParam2ParamTypeId); + param2.insert("deviceId", m_mockDeviceId); + param2.insert("stateTypeId", mockBoolStateId); + ruleActionParams.append(param1); + ruleActionParams.append(param2); + + actions.clear(); + action.insert("deviceId", m_mockDeviceId); + action.insert("actionTypeId", mockActionIdWithParams); + action.insert("ruleActionParams", ruleActionParams); + actions.append(action); + addRuleParams.insert("actions", actions); + + qDebug() << addRuleParams; + + QVariant response = injectAndWait("Rules.AddRule", addRuleParams); + verifyRuleError(response); + + // trigger event + spy.clear(); + request = QNetworkRequest(QUrl(QString("http://localhost:%1/generateevent?eventtypeid=%2").arg(m_mockDevice1Port).arg(mockEvent1Id.toString()))); + reply = nam.get(request); + spy.wait(); + QCOMPARE(spy.count(), 1); + reply->deleteLater(); + + LogFilter filter; + filter.addDeviceId(m_mockDeviceId); + filter.addTypeId(mockActionIdWithParams); + QList entries = NymeaCore::instance()->logEngine()->logEntries(filter); + qCDebug(dcTests()) << "Log entries:" << entries; + + // set bool state to false + spy.clear(); + request = QNetworkRequest(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(m_mockDevice1Port).arg(mockBoolStateId.toString()).arg(false))); + reply = nam.get(request); + spy.wait(); + QCOMPARE(spy.count(), 1); + reply->deleteLater(); + + // trigger event + spy.clear(); + request = QNetworkRequest(QUrl(QString("http://localhost:%1/generateevent?eventtypeid=%2").arg(m_mockDevice1Port).arg(mockEvent1Id.toString()))); + reply = nam.get(request); + spy.wait(); + QCOMPARE(spy.count(), 1); + reply->deleteLater(); + + entries = NymeaCore::instance()->logEngine()->logEntries(filter); + qCDebug(dcTests()) << "Log entries:" << entries; + + +} + void TestRules::removePolicyUpdate() { // ADD parent device