diff --git a/guh.pri b/guh.pri index 0c035e39..48d8e67d 100644 --- a/guh.pri +++ b/guh.pri @@ -6,7 +6,7 @@ GUH_PLUGINS_PATH=/usr/lib/$$system('dpkg-architecture -q DEB_HOST_MULTIARCH')/gu # define protocol versions JSON_PROTOCOL_VERSION_MAJOR=1 -JSON_PROTOCOL_VERSION_MINOR=0 +JSON_PROTOCOL_VERSION_MINOR=1 REST_API_VERSION=1 DEFINES += GUH_VERSION_STRING=\\\"$${GUH_VERSION_STRING}\\\" \ diff --git a/libguh-core/guhcore.cpp b/libguh-core/guhcore.cpp index 7f67b7c7..8cc95d58 100644 --- a/libguh-core/guhcore.cpp +++ b/libguh-core/guhcore.cpp @@ -338,9 +338,36 @@ DeviceManager::DeviceError GuhCore::executeAction(const Action &action) /*! Execute the given \a ruleActions. */ void GuhCore::executeRuleActions(const QList ruleActions) { + QList actions; foreach (const RuleAction &ruleAction, ruleActions) { - Action action = ruleAction.toAction(); - qCDebug(dcRuleEngine) << "Executing action" << ruleAction.actionTypeId() << action.params(); + if (ruleAction.type() == RuleAction::TypeDevice) { + actions.append(ruleAction.toAction()); + } 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()) { + 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."; + continue; + } + params.append(Param(pt.id(), rap.value())); + } + action.setParams(params); + actions.append(action); + } + } + } + foreach (const Action &action, actions) { + qCDebug(dcRuleEngine) << "Executing action" << action.actionTypeId() << action.params(); DeviceManager::DeviceError status = executeAction(action); switch(status) { case DeviceManager::DeviceErrorNoError: diff --git a/libguh-core/guhcore.h b/libguh-core/guhcore.h index c9046b55..62a39a40 100644 --- a/libguh-core/guhcore.h +++ b/libguh-core/guhcore.h @@ -24,8 +24,8 @@ #include "rule.h" #include "types/event.h" +#include "types/deviceclass.h" #include "plugin/deviceplugin.h" -#include "plugin/deviceclass.h" #include "plugin/devicedescriptor.h" #include "logging/logengine.h" diff --git a/libguh-core/jsonrpc/devicehandler.cpp b/libguh-core/jsonrpc/devicehandler.cpp index c139eef1..65dcab89 100644 --- a/libguh-core/jsonrpc/devicehandler.cpp +++ b/libguh-core/jsonrpc/devicehandler.cpp @@ -57,8 +57,8 @@ #include "guhcore.h" #include "devicemanager.h" #include "loggingcategories.h" +#include "types/deviceclass.h" #include "plugin/device.h" -#include "plugin/deviceclass.h" #include "plugin/deviceplugin.h" #include diff --git a/libguh-core/jsonrpc/jsonrpcserver.cpp b/libguh-core/jsonrpc/jsonrpcserver.cpp index d3bca640..002a3f79 100644 --- a/libguh-core/jsonrpc/jsonrpcserver.cpp +++ b/libguh-core/jsonrpc/jsonrpcserver.cpp @@ -42,7 +42,7 @@ #include "guhcore.h" #include "devicemanager.h" #include "plugin/deviceplugin.h" -#include "plugin/deviceclass.h" +#include "types/deviceclass.h" #include "plugin/device.h" #include "rule.h" #include "ruleengine.h" diff --git a/libguh-core/jsonrpc/jsonrpcserver.h b/libguh-core/jsonrpc/jsonrpcserver.h index 184d9529..95dcb2c9 100644 --- a/libguh-core/jsonrpc/jsonrpcserver.h +++ b/libguh-core/jsonrpc/jsonrpcserver.h @@ -22,11 +22,11 @@ #ifndef JSONRPCSERVER_H #define JSONRPCSERVER_H -#include "plugin/deviceclass.h" #include "jsonhandler.h" #include "transportinterface.h" #include "usermanager.h" +#include "types/deviceclass.h" #include "types/action.h" #include "types/event.h" diff --git a/libguh-core/jsonrpc/jsontypes.cpp b/libguh-core/jsonrpc/jsontypes.cpp index 5ce22077..fa5011cb 100644 --- a/libguh-core/jsonrpc/jsontypes.cpp +++ b/libguh-core/jsonrpc/jsontypes.cpp @@ -168,12 +168,15 @@ void JsonTypes::init() s_param.insert("value", basicTypeRef()); // RuleAction - s_ruleAction.insert("actionTypeId", basicTypeToString(Uuid)); - s_ruleAction.insert("deviceId", basicTypeToString(Uuid)); + s_ruleAction.insert("o:deviceId", basicTypeToString(Uuid)); + s_ruleAction.insert("o:actionTypeId", basicTypeToString(Uuid)); + s_ruleAction.insert("o:interface", basicTypeToString(String)); + s_ruleAction.insert("o:interfaceAction", basicTypeToString(String)); s_ruleAction.insert("o:ruleActionParams", QVariantList() << ruleActionParamRef()); // RuleActionParam - s_ruleActionParam.insert("paramTypeId", basicTypeToString(Uuid)); + s_ruleActionParam.insert("o:paramTypeId", basicTypeToString(Uuid)); + s_ruleActionParam.insert("o:paramName", basicTypeToString(String)); s_ruleActionParam.insert("o:value", basicTypeRef()); s_ruleActionParam.insert("o:eventTypeId", basicTypeToString(Uuid)); s_ruleActionParam.insert("o:eventParamTypeId", basicTypeToString(Uuid)); @@ -228,8 +231,10 @@ void JsonTypes::init() s_event.insert("o:params", QVariantList() << paramRef()); // EventDescriptor - s_eventDescriptor.insert("eventTypeId", basicTypeToString(Uuid)); - s_eventDescriptor.insert("deviceId", basicTypeToString(Uuid)); + s_eventDescriptor.insert("o:eventTypeId", basicTypeToString(Uuid)); + s_eventDescriptor.insert("o:deviceId", basicTypeToString(Uuid)); + s_eventDescriptor.insert("o:interface", basicTypeToString(String)); + s_eventDescriptor.insert("o:interfaceEvent", basicTypeToString(String)); s_eventDescriptor.insert("o:paramDescriptors", QVariantList() << paramDescriptorRef()); // ActionType @@ -1320,10 +1325,16 @@ Rule JsonTypes::unpackRule(const QVariantMap &ruleMap) /*! Returns a \l{RuleAction} created from the given \a ruleActionMap. */ RuleAction JsonTypes::unpackRuleAction(const QVariantMap &ruleActionMap) { - RuleAction action(ActionTypeId(ruleActionMap.value("actionTypeId").toString()), DeviceId(ruleActionMap.value("deviceId").toString())); + ActionTypeId actionTypeId(ruleActionMap.value("actionTypeId").toString()); + DeviceId actionDeviceId(ruleActionMap.value("deviceId").toString()); + QString interface = ruleActionMap.value("interface").toString(); + QString interfaceAction = ruleActionMap.value("interfaceAction").toString(); RuleActionParamList actionParamList = JsonTypes::unpackRuleActionParams(ruleActionMap.value("ruleActionParams").toList()); - action.setRuleActionParams(actionParamList); - return action; + + if (!actionTypeId.isNull() && !actionDeviceId.isNull()) { + return RuleAction(actionTypeId, actionDeviceId, actionParamList); + } + return RuleAction(interface, interfaceAction, actionParamList); } /*! Returns a \l{RuleActionParam} created from the given \a ruleActionParamMap. */ @@ -1333,9 +1344,13 @@ RuleActionParam JsonTypes::unpackRuleActionParam(const QVariantMap &ruleActionPa return RuleActionParam(); 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()); + if (paramTypeId.isNull()) { + return RuleActionParam(paramName, value, eventTypeId, eventParamTypeId); + } return RuleActionParam(paramTypeId, value, eventTypeId, eventParamTypeId); } @@ -1377,9 +1392,13 @@ EventDescriptor JsonTypes::unpackEventDescriptor(const QVariantMap &eventDescrip { EventTypeId eventTypeId(eventDescriptorMap.value("eventTypeId").toString()); DeviceId eventDeviceId(eventDescriptorMap.value("deviceId").toString()); + QString interface = eventDescriptorMap.value("interface").toString(); + QString interfaceEvent = eventDescriptorMap.value("interfaceEvent").toString(); QList eventParams = JsonTypes::unpackParamDescriptors(eventDescriptorMap.value("paramDescriptors").toList()); - EventDescriptor eventDescriptor(eventTypeId, eventDeviceId, eventParams); - return eventDescriptor; + if (!eventDeviceId.isNull() && !eventTypeId.isNull()) { + return EventDescriptor(eventTypeId, eventDeviceId, eventParams); + } + return EventDescriptor(interface, interfaceEvent, eventParams); } /*! Returns a \l{StateEvaluator} created from the given \a stateEvaluatorMap. */ diff --git a/libguh-core/jsonrpc/jsontypes.h b/libguh-core/jsonrpc/jsontypes.h index 839bccba..601ae8d5 100644 --- a/libguh-core/jsonrpc/jsontypes.h +++ b/libguh-core/jsonrpc/jsontypes.h @@ -23,7 +23,6 @@ #ifndef JSONTYPES_H #define JSONTYPES_H -#include "plugin/deviceclass.h" #include "plugin/devicedescriptor.h" #include "rule.h" #include "devicemanager.h" @@ -31,6 +30,7 @@ #include "guhconfiguration.h" #include "usermanager.h" +#include "types/deviceclass.h" #include "types/event.h" #include "types/action.h" #include "types/actiontype.h" diff --git a/libguh-core/ruleengine.cpp b/libguh-core/ruleengine.cpp index 3b8150a6..94808616 100644 --- a/libguh-core/ruleengine.cpp +++ b/libguh-core/ruleengine.cpp @@ -369,7 +369,7 @@ QList RuleEngine::evaluateEvent(const Event &event) } } else { // Event based rule - if (containsEvent(rule, event) && rule.statesActive() && rule.timeActive()) { + if (containsEvent(rule, event, device->deviceClassId()) && rule.statesActive() && rule.timeActive()) { qCDebug(dcRuleEngine) << "Rule" << rule.id() << "contains event" << event.eventId() << "and all states match."; rules.append(rule); } @@ -465,25 +465,42 @@ RuleEngine::RuleError RuleEngine::addRule(const Rule &rule, bool fromEdit) // Check IDs in each EventDescriptor foreach (const EventDescriptor &eventDescriptor, rule.eventDescriptors()) { - // check deviceId - Device *device = GuhCore::instance()->deviceManager()->findConfiguredDevice(eventDescriptor.deviceId()); - if (!device) { - qCWarning(dcRuleEngine) << "Cannot create rule. No configured device for eventTypeId" << eventDescriptor.eventTypeId(); - return RuleErrorDeviceNotFound; - } - - // Check eventTypeId for this deivce - DeviceClass deviceClass = GuhCore::instance()->deviceManager()->findDeviceClass(device->deviceClassId()); - bool eventTypeFound = false; - foreach (const EventType &eventType, deviceClass.eventTypes()) { - if (eventType.id() == eventDescriptor.eventTypeId()) { - eventTypeFound = true; - } - } - if (!eventTypeFound) { - qCWarning(dcRuleEngine) << "Cannot create rule. Device " + device->name() + " has no event type:" << eventDescriptor.eventTypeId(); + if (!eventDescriptor.isValid()) { + qWarning(dcRuleEngine()) << "EventDescriptor is incomplete. It must have either eventTypeId and deviceId, or interface and interfaceEvent"; return RuleErrorEventTypeNotFound; } + if (eventDescriptor.type() == EventDescriptor::TypeDevice) { + // check deviceId + Device *device = GuhCore::instance()->deviceManager()->findConfiguredDevice(eventDescriptor.deviceId()); + if (!device) { + qCWarning(dcRuleEngine) << "Cannot create rule. No configured device for eventTypeId" << eventDescriptor.eventTypeId(); + return RuleErrorDeviceNotFound; + } + + // Check eventTypeId for this deivce + DeviceClass deviceClass = GuhCore::instance()->deviceManager()->findDeviceClass(device->deviceClassId()); + bool eventTypeFound = false; + foreach (const EventType &eventType, deviceClass.eventTypes()) { + if (eventType.id() == eventDescriptor.eventTypeId()) { + eventTypeFound = true; + } + } + if (!eventTypeFound) { + qCWarning(dcRuleEngine) << "Cannot create rule. Device " + device->name() + " has no event type:" << eventDescriptor.eventTypeId(); + return RuleErrorEventTypeNotFound; + } + } else { + // Interface based event + Interface iface = GuhCore::instance()->deviceManager()->supportedInterfaces().findByName(eventDescriptor.interface()); + if (!iface.isValid()) { + qWarning(dcRuleEngine()) << "No such interface:" << eventDescriptor.interface(); + return RuleErrorInterfaceNotFound; + } + if (iface.eventTypes().findByName(eventDescriptor.interfaceEvent()).name().isEmpty()) { + qWarning(dcRuleEngine()) << "Interface" << iface.name() << "has no such event:" << eventDescriptor.interfaceEvent(); + return RuleErrorEventTypeNotFound; + } + } } // Check state evaluator @@ -536,104 +553,165 @@ RuleEngine::RuleError RuleEngine::addRule(const Rule &rule, bool fromEdit) // Check actions foreach (const RuleAction &action, rule.actions()) { - Device *device = GuhCore::instance()->deviceManager()->findConfiguredDevice(action.deviceId()); - if (!device) { - qCWarning(dcRuleEngine) << "Cannot create rule. No configured device for action with actionTypeId" << action.actionTypeId(); - return RuleErrorDeviceNotFound; - } - - DeviceClass deviceClass = GuhCore::instance()->deviceManager()->findDeviceClass(device->deviceClassId()); - if (!deviceClass.hasActionType(action.actionTypeId())) { - qCWarning(dcRuleEngine) << "Cannot create rule. Device " + device->name() + " has no action type:" << action.actionTypeId(); + if (!action.isValid()) { + qWarning(dcRuleEngine()) << "Action is incomplete. It must have either actionTypeId and deviceId, or interface and interfaceAction"; return RuleErrorActionTypeNotFound; } + if (action.type() == RuleAction::TypeDevice) { + Device *device = GuhCore::instance()->deviceManager()->findConfiguredDevice(action.deviceId()); + if (!device) { + qCWarning(dcRuleEngine) << "Cannot create rule. No configured device for action with actionTypeId" << action.actionTypeId(); + return RuleErrorDeviceNotFound; + } + + DeviceClass deviceClass = GuhCore::instance()->deviceManager()->findDeviceClass(device->deviceClassId()); + if (!deviceClass.hasActionType(action.actionTypeId())) { + qCWarning(dcRuleEngine) << "Cannot create rule. Device " + device->name() + " has no action type:" << action.actionTypeId(); + 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::Type actionParamType = getActionParamType(action.actionTypeId(), ruleActionParam.paramTypeId()); + if (eventParamType != 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 = GuhCore::instance()->deviceManager()->verifyParams(actionType.paramTypes(), finalParams); + if (paramCheck != DeviceManager::DeviceErrorNoError) { + qCWarning(dcRuleEngine) << "Cannot create rule. Got an invalid actionParam."; + return RuleErrorInvalidRuleActionParameter; + } + } + } + } - // 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::Type actionParamType = getActionParamType(action.actionTypeId(), ruleActionParam.paramTypeId()); - if (eventParamType != 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; - } + if (!ruleActionParam.isValid()) { + qCWarning(dcRuleEngine) << "Cannot create rule. Got an actionParam with \"value\" AND \"eventTypeId\"."; + return RuleEngine::RuleErrorInvalidRuleActionParameter; } } - } else { - // verify action params - foreach (const ActionType &actionType, deviceClass.actionTypes()) { - if (actionType.id() == action.actionTypeId()) { - ParamList finalParams = action.toAction().params(); - DeviceManager::DeviceError paramCheck = GuhCore::instance()->deviceManager()->verifyParams(actionType.paramTypes(), finalParams); - if (paramCheck != DeviceManager::DeviceErrorNoError) { - qCWarning(dcRuleEngine) << "Cannot create rule. Got an invalid actionParam."; - return RuleErrorInvalidRuleActionParameter; - } + + } else { // Is TypeInterface + Interface iface = GuhCore::instance()->deviceManager()->supportedInterfaces().findByName(action.interface()); + if (!iface.isValid()) { + qCWarning(dcRuleEngine()) << "Cannot create rule. No such interface:" << action.interface(); + return RuleError::RuleErrorInterfaceNotFound; + } + ActionType ifaceActionType = iface.actionTypes().findByName(action.interfaceAction()); + if (ifaceActionType.name().isEmpty()) { + qCWarning(dcRuleEngine()) << "Cannot create rule. Interface" << iface.name() << "does not implement action" << action.interfaceAction(); + return RuleError::RuleErrorActionTypeNotFound; + } + foreach (const ParamType &ifaceActionParamType, ifaceActionType.paramTypes()) { + qWarning() << "iface requires param:" << ifaceActionParamType.name(); + if (!action.ruleActionParams().hasParam(ifaceActionParamType.name())) { + qCWarning(dcRuleEngine()) << "Cannot create rule. Interface action" << iface.name() << ":" << action.interfaceAction() << "requires a" << ifaceActionParamType.name() << "param of type" << QVariant::typeToName(ifaceActionParamType.type()); + return RuleError::RuleErrorMissingParameter; + } + if (!action.ruleActionParam(ifaceActionParamType.name()).value().canConvert(ifaceActionParamType.type())) { + qCWarning(dcRuleEngine()) << "Cannot create rule. Interface action parameter" << iface.name() << ":" << action.interfaceAction() << ":" << ifaceActionParamType.name() << "has wrong type. Expected" << QVariant::typeToName(ifaceActionParamType.type()); + return RuleError::RuleErrorInvalidParameter; } } - } - - foreach (const RuleActionParam &ruleActionParam, action.ruleActionParams()) { - if (!ruleActionParam.isValid()) { - qCWarning(dcRuleEngine) << "Cannot create rule. Got an actionParam with \"value\" AND \"eventTypeId\"."; - return RuleEngine::RuleErrorInvalidRuleActionParameter; - } + // TODO: Check params } } // Check exit actions - foreach (const RuleAction &action, rule.exitActions()) { - Device *device = GuhCore::instance()->deviceManager()->findConfiguredDevice(action.deviceId()); - if (!device) { - qCWarning(dcRuleEngine) << "Cannot create rule. No configured device for exit action with actionTypeId" << action.actionTypeId(); - return RuleErrorDeviceNotFound; - } - - DeviceClass deviceClass = GuhCore::instance()->deviceManager()->findDeviceClass(device->deviceClassId()); - if (!deviceClass.hasActionType(action.actionTypeId())) { - qCWarning(dcRuleEngine) << "Cannot create rule. Device " + device->name() + " has no action type:" << action.actionTypeId(); + foreach (const RuleAction &ruleAction, rule.exitActions()) { + if (!ruleAction.isValid()) { + qWarning(dcRuleEngine()) << "Exit Action is incomplete. It must have either actionTypeId and deviceId, or interface and interfaceAction"; return RuleErrorActionTypeNotFound; } - // verify action params - foreach (const ActionType &actionType, deviceClass.actionTypes()) { - if (actionType.id() == action.actionTypeId()) { - ParamList finalParams = action.toAction().params(); - DeviceManager::DeviceError paramCheck = GuhCore::instance()->deviceManager()->verifyParams(actionType.paramTypes(), finalParams); - if (paramCheck != DeviceManager::DeviceErrorNoError) { - qCWarning(dcRuleEngine) << "Cannot create rule. Got an invalid exit actionParam."; - return RuleErrorInvalidRuleActionParameter; + if (ruleAction.type() == RuleAction::TypeDevice) { + Device *device = GuhCore::instance()->deviceManager()->findConfiguredDevice(ruleAction.deviceId()); + if (!device) { + qCWarning(dcRuleEngine) << "Cannot create rule. No configured device for exit action with actionTypeId" << ruleAction.actionTypeId(); + return RuleErrorDeviceNotFound; + } + + DeviceClass deviceClass = GuhCore::instance()->deviceManager()->findDeviceClass(device->deviceClassId()); + if (!deviceClass.hasActionType(ruleAction.actionTypeId())) { + qCWarning(dcRuleEngine) << "Cannot create rule. Device " + device->name() + " has no action type:" << ruleAction.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 = GuhCore::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 (action.isEventBased()) { - qCWarning(dcRuleEngine) << "Cannot create rule. Got exitAction with an actionParam containing an eventTypeId. "; - 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; + // 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 = GuhCore::instance()->deviceManager()->supportedInterfaces().findByName(ruleAction.interface()); + if (!iface.isValid()) { + qCWarning(dcRuleEngine()) << "Cannot create rule. No such interface:" << ruleAction.interface(); + return RuleError::RuleErrorInterfaceNotFound; + } + ActionType ifaceActionType = iface.actionTypes().findByName(ruleAction.interfaceAction()); + if (ifaceActionType.name().isEmpty()) { + qCWarning(dcRuleEngine()) << "Cannot create rule. Interface" << iface.name() << "does not implement action" << ruleAction.interfaceAction(); + return RuleError::RuleErrorActionTypeNotFound; + } + foreach (const ParamType &ifaceActionParamType, ifaceActionType.paramTypes()) { + qWarning() << "iface requires param:" << ifaceActionParamType.name(); + 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()); + 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()); + return RuleError::RuleErrorInvalidParameter; + } + } + // TODO: Check params } } @@ -997,12 +1075,68 @@ void RuleEngine::removeDeviceFromRule(const RuleId &id, const DeviceId &deviceId emit ruleConfigurationChanged(newRule); } -bool RuleEngine::containsEvent(const Rule &rule, const Event &event) +bool RuleEngine::containsEvent(const Rule &rule, const Event &event, const DeviceClassId &deviceClassId) { foreach (const EventDescriptor &eventDescriptor, rule.eventDescriptors()) { - if (eventDescriptor == event) { - return true; + // If this is a device based rule, eventTypeId and deviceId must match + if (eventDescriptor.type() == EventDescriptor::TypeDevice) { + if (eventDescriptor.eventTypeId() != event.eventTypeId() || eventDescriptor.deviceId() != event.deviceId()) { + continue; + } } + + // If this is a interface based rule, the device must implement the interface + if (eventDescriptor.type() == EventDescriptor::TypeInterface) { + DeviceClass dc = GuhCore::instance()->deviceManager()->findDeviceClass(deviceClassId); + if (!dc.interfaces().contains(eventDescriptor.interface())) { + // DeviceClass for this event doesn't implement the interface for this eventDescriptor + continue; + } + + EventType et = dc.eventTypes().findById(event.eventTypeId()); + if (et.name() != eventDescriptor.interfaceEvent()) { + // The fired event name does not match with the eventDescriptor's interfaceEvent + continue; + } + } + + // Ok, either device/eventTypeId or interface/interfaceEvent are matching. Compare the paramdescriptor + foreach (const ParamDescriptor ¶mDescriptor, eventDescriptor.paramDescriptors()) { + switch (paramDescriptor.operatorType()) { + case Types::ValueOperatorEquals: + if (event.param(paramDescriptor.paramTypeId()).value() != paramDescriptor.value()) { + continue; + } + break; + case Types::ValueOperatorNotEquals: + if (event.param(paramDescriptor.paramTypeId()).value() == paramDescriptor.value()) { + continue; + } + break; + case Types::ValueOperatorGreater: + if (event.param(paramDescriptor.paramTypeId()).value() <= paramDescriptor.value()) { + continue; + } + break; + case Types::ValueOperatorGreaterOrEqual: + if (event.param(paramDescriptor.paramTypeId()).value() < paramDescriptor.value()) { + continue; + } + break; + case Types::ValueOperatorLess: + if (event.param(paramDescriptor.paramTypeId()).value() >= paramDescriptor.value()) { + continue; + } + break; + case Types::ValueOperatorLessOrEqual: + if (event.param(paramDescriptor.paramTypeId()).value() < paramDescriptor.value()) { + continue; + } + break; + } + } + // All matching! + return true; } return false; diff --git a/libguh-core/ruleengine.h b/libguh-core/ruleengine.h index 96cfd25a..c40b76a3 100644 --- a/libguh-core/ruleengine.h +++ b/libguh-core/ruleengine.h @@ -24,7 +24,7 @@ #include "rule.h" #include "types/event.h" -#include "plugin/deviceclass.h" +#include "types/deviceclass.h" #include "stateevaluator.h" #include @@ -59,7 +59,8 @@ public: RuleErrorInvalidCalendarItem, RuleErrorInvalidTimeEventItem, RuleErrorContainsEventBasesAction, - RuleErrorNoExitActions + RuleErrorNoExitActions, + RuleErrorInterfaceNotFound }; enum RemovePolicy { @@ -99,7 +100,7 @@ signals: void ruleConfigurationChanged(const Rule &rule); private: - bool containsEvent(const Rule &rule, const Event &event); + bool containsEvent(const Rule &rule, const Event &event, const DeviceClassId &deviceClassId); bool containsState(const StateEvaluator &stateEvaluator, const Event &stateChangeEvent); bool checkEventDescriptors(const QList eventDescriptors, const EventTypeId &eventTypeId); diff --git a/libguh/devicemanager.cpp b/libguh/devicemanager.cpp index 2475777d..9f513151 100644 --- a/libguh/devicemanager.cpp +++ b/libguh/devicemanager.cpp @@ -187,6 +187,10 @@ DeviceManager::DeviceManager(HardwareManager *hardwareManager, const QLocale &lo qRegisterMetaType(); qRegisterMetaType(); + foreach (const Interface &interface, DevicePlugin::allInterfaces()) { + m_supportedInterfaces.insert(interface.name(), interface); + } + // Give hardware a chance to start up before loading plugins etc. QMetaObject::invokeMethod(this, "loadPlugins", Qt::QueuedConnection); QMetaObject::invokeMethod(this, "loadConfiguredDevices", Qt::QueuedConnection); @@ -342,6 +346,18 @@ QList DeviceManager::supportedVendors() const return m_supportedVendors.values(); } +/*! Returns the list of all supported interfaces */ +Interfaces DeviceManager::supportedInterfaces() const +{ + return m_supportedInterfaces.values(); +} + +/*! Returns the interface with the given name. If the interface can't be found it will return an invalid interface. */ +Interface DeviceManager::findInterface(const QString &name) +{ + return m_supportedInterfaces.value(name); +} + /*! Returns all the supported \l{DeviceClass}{DeviceClasses} by all \l{DevicePlugin}{DevicePlugins} loaded in the system. * Optionally filtered by \a vendorId. */ QList DeviceManager::supportedDevices(const VendorId &vendorId) const @@ -769,6 +785,18 @@ QList DeviceManager::findConfiguredDevices(const DeviceClassId &device return ret; } +QList DeviceManager::findConfiguredDevices(const QString &interface) const +{ + QList ret; + foreach (Device *device, m_configuredDevices) { + DeviceClass dc = m_supportedDevices.value(device->deviceClassId()); + if (dc.interfaces().contains(interface)) { + ret.append(device); + } + } + return ret; +} + /*! Returns all child \l{Device}{Devices} of the given \a device. */ QList DeviceManager::findChildDevices(const DeviceId &id) const { @@ -793,7 +821,7 @@ DeviceClass DeviceManager::findDeviceClass(const DeviceClassId &deviceClassId) c return DeviceClass(); } -/*! Verify if the given \a params matche the given \a paramTypes. Ith \a requireAll +/*! Verify if the given \a params matches the given \a paramTypes. Ith \a requireAll * is true, all \l{ParamList}{Params} has to be valid. Returns \l{DeviceError} to inform about the result.*/ DeviceManager::DeviceError DeviceManager::verifyParams(const QList paramTypes, ParamList ¶ms, bool requireAll) { diff --git a/libguh/devicemanager.h b/libguh/devicemanager.h index af5e10bb..e9ae28f9 100644 --- a/libguh/devicemanager.h +++ b/libguh/devicemanager.h @@ -26,10 +26,11 @@ #include "libguh.h" -#include "plugin/deviceclass.h" #include "plugin/device.h" #include "plugin/devicedescriptor.h" +#include "types/deviceclass.h" +#include "types/interface.h" #include "types/event.h" #include "types/action.h" #include "types/vendor.h" @@ -104,7 +105,10 @@ public: DeviceError setPluginConfig(const PluginId &pluginId, const ParamList &pluginConfig); QList supportedVendors() const; + Interfaces supportedInterfaces() const; + Interface findInterface(const QString &name); QList supportedDevices(const VendorId &vendorId = VendorId()) const; + DeviceError discoverDevices(const DeviceClassId &deviceClassId, const ParamList ¶ms); QList configuredDevices() const; @@ -124,6 +128,7 @@ public: Device* findConfiguredDevice(const DeviceId &id) const; QList findConfiguredDevices(const DeviceClassId &deviceClassId) const; + QList findConfiguredDevices(const QString &interface) const; QList findChildDevices(const DeviceId &id) const; DeviceClass findDeviceClass(const DeviceClassId &deviceClassId) const; @@ -181,6 +186,7 @@ private: QLocale m_locale; QHash m_supportedVendors; + QHash m_supportedInterfaces; QHash > m_vendorDeviceMap; QHash m_supportedDevices; QHash m_configuredDevices; diff --git a/libguh/interfaces/battery.json b/libguh/interfaces/battery.json new file mode 100644 index 00000000..7385f5c9 --- /dev/null +++ b/libguh/interfaces/battery.json @@ -0,0 +1,14 @@ +{ + "states": [ + { + "name": "batteryLevel", + "type": "int", + "minValue": 0, + "maxValue": 100 + }, + { + "name": "batteryCritical", + "type": "bool" + } + ] +} diff --git a/libguh/interfaces/interfaces.qrc b/libguh/interfaces/interfaces.qrc index e0ac6c81..73da7add 100644 --- a/libguh/interfaces/interfaces.qrc +++ b/libguh/interfaces/interfaces.qrc @@ -13,5 +13,6 @@ extendedvolumecontroller.json mediametadataprovider.json mediaplayer.json + battery.json diff --git a/libguh/interfaces/light.json b/libguh/interfaces/light.json index af95d791..160f9432 100644 --- a/libguh/interfaces/light.json +++ b/libguh/interfaces/light.json @@ -2,8 +2,8 @@ "states": [ { "name": "power", - "type": "bool" + "type": "bool", + "writable": true } - ] } diff --git a/libguh/libguh.pro b/libguh/libguh.pro index f3dc5cd5..ab309c3a 100644 --- a/libguh/libguh.pro +++ b/libguh/libguh.pro @@ -20,7 +20,6 @@ HEADERS += devicemanager.h \ loggingcategories.h \ guhsettings.h \ plugin/device.h \ - plugin/deviceclass.h \ plugin/deviceplugin.h \ plugin/devicedescriptor.h \ plugin/devicepairinginfo.h \ @@ -53,6 +52,7 @@ HEADERS += devicemanager.h \ coap/corelinkparser.h \ coap/corelink.h \ coap/coapobserveresource.h \ + types/deviceclass.h \ types/action.h \ types/actiontype.h \ types/state.h \ @@ -67,6 +67,7 @@ HEADERS += devicemanager.h \ types/ruleaction.h \ types/ruleactionparam.h \ types/statedescriptor.h \ + types/interface.h \ hardwareresource.h \ plugintimer.h \ hardwaremanager.h \ @@ -75,7 +76,6 @@ SOURCES += devicemanager.cpp \ loggingcategories.cpp \ guhsettings.cpp \ plugin/device.cpp \ - plugin/deviceclass.cpp \ plugin/deviceplugin.cpp \ plugin/devicedescriptor.cpp \ plugin/devicepairinginfo.cpp \ @@ -108,6 +108,7 @@ SOURCES += devicemanager.cpp \ coap/corelinkparser.cpp \ coap/corelink.cpp \ coap/coapobserveresource.cpp \ + types/deviceclass.cpp \ types/action.cpp \ types/actiontype.cpp \ types/state.cpp \ @@ -122,6 +123,7 @@ SOURCES += devicemanager.cpp \ types/ruleaction.cpp \ types/ruleactionparam.cpp \ types/statedescriptor.cpp \ + types/interface.cpp \ hardwareresource.cpp \ plugintimer.cpp \ hardwaremanager.cpp \ diff --git a/libguh/plugin/device.h b/libguh/plugin/device.h index e50c1476..eb707acb 100644 --- a/libguh/plugin/device.h +++ b/libguh/plugin/device.h @@ -27,7 +27,7 @@ #include "typeutils.h" #include "libguh.h" -#include "plugin/deviceclass.h" +#include "types/deviceclass.h" #include "types/state.h" #include "types/param.h" diff --git a/libguh/plugin/deviceplugin.cpp b/libguh/plugin/deviceplugin.cpp index 05551f1a..6e199c25 100644 --- a/libguh/plugin/deviceplugin.cpp +++ b/libguh/plugin/deviceplugin.cpp @@ -797,102 +797,89 @@ void DevicePlugin::loadMetaData() QStringList interfaces; foreach (const QJsonValue &value, deviceClassObject.value("interfaces").toArray()) { - QVariantMap interfaceMap = loadInterface(value.toString()); - QVariantList states = interfaceMap.value("states").toList(); + Interface iface = loadInterface(value.toString()); StateTypes stateTypes(deviceClass.stateTypes()); ActionTypes actionTypes(deviceClass.actionTypes()); EventTypes eventTypes(deviceClass.eventTypes()); bool valid = true; - foreach (const QVariant &stateVariant, states) { - StateType stateType = stateTypes.findByName(stateVariant.toMap().value("name").toString()); - QVariantMap stateMap = stateVariant.toMap(); + foreach (const StateType &ifaceStateType, iface.stateTypes()) { + StateType stateType = stateTypes.findByName(ifaceStateType.name()); if (stateType.id().isNull()) { - qCWarning(dcDeviceManager) << "DeviceClass" << deviceClass.name() << "claims to implement interface" << value.toString() << "but doesn't implement state" << stateMap.value("name").toString(); + qCWarning(dcDeviceManager) << "DeviceClass" << deviceClass.name() << "claims to implement interface" << value.toString() << "but doesn't implement state" << ifaceStateType.name(); valid = false; continue; } - if (QVariant::nameToType(stateMap.value("type").toByteArray().data()) != stateType.type()) { - qCWarning(dcDeviceManager) << "DeviceClass" << deviceClass.name() << "claims to implement interface" << value.toString() << "but state" << stateMap.value("name").toString() << "has not matching type" << stateMap.value("type").toString(); + if (ifaceStateType.type() != stateType.type()) { + qCWarning(dcDeviceManager) << "DeviceClass" << deviceClass.name() << "claims to implement interface" << value.toString() << "but state" << stateType.name() << "has not matching type" << stateType.type() << "!=" << ifaceStateType.type(); valid = false; continue; } - if (stateMap.contains("minimumValue")) { - if (stateMap.value("minimumValue").toString() == "any") { + if (ifaceStateType.minValue().isValid() && !ifaceStateType.minValue().isNull()) { + if (ifaceStateType.minValue().toString() == "any") { if (stateType.minValue().isNull()) { - qCWarning(dcDeviceManager) << "DeviceClass" << deviceClass.name() << "claims to implement interface" << value.toString() << "but state" << stateMap.value("name").toString() << "has no minimum value defined."; + qCWarning(dcDeviceManager) << "DeviceClass" << deviceClass.name() << "claims to implement interface" << value.toString() << "but state" << stateType.name() << "has no minimum value defined."; valid = false; continue; } - } else if (stateMap.value("minimumValue") != stateType.minValue()) { - qCWarning(dcDeviceManager) << "DeviceClass" << deviceClass.name() << "claims to implement interface" << value.toString() << "but state" << stateMap.value("name").toString() << "has not matching minimum value:" << stateMap.value("minimumValue") << "!=" << stateType.minValue(); + } else if (ifaceStateType.minValue() != stateType.minValue()) { + qCWarning(dcDeviceManager) << "DeviceClass" << deviceClass.name() << "claims to implement interface" << value.toString() << "but state" << stateType.name() << "has not matching minimum value:" << ifaceStateType.minValue() << "!=" << stateType.minValue(); valid = false; continue; } } - if (stateMap.contains("maximumValue")) { - if (stateMap.value("maximumValue").toString() == "any") { + if (ifaceStateType.maxValue().isValid() && !ifaceStateType.maxValue().isNull()) { + if (ifaceStateType.maxValue().toString() == "any") { if (stateType.maxValue().isNull()) { - qCWarning(dcDeviceManager) << "DeviceClass" << deviceClass.name() << "claims to implement interface" << value.toString() << "but state" << stateMap.value("name").toString() << "has no maximum value defined."; + qCWarning(dcDeviceManager) << "DeviceClass" << deviceClass.name() << "claims to implement interface" << value.toString() << "but state" << stateType.name() << "has no maximum value defined."; valid = false; continue; } - } else if (stateMap.value("maximumValue") != stateType.maxValue()) { - qCWarning(dcDeviceManager) << "DeviceClass" << deviceClass.name() << "claims to implement interface" << value.toString() << "but state" << stateMap.value("name").toString() << "has not matching maximum value:" << stateMap.value("maximumValue") << "!=" << stateType.minValue(); + } else if (ifaceStateType.maxValue() != stateType.maxValue()) { + qCWarning(dcDeviceManager) << "DeviceClass" << deviceClass.name() << "claims to implement interface" << value.toString() << "but state" << stateType.name() << "has not matching maximum value:" << ifaceStateType.maxValue() << "!=" << stateType.minValue(); valid = false; continue; } } - if (stateMap.contains("allowedValues") && stateMap.value("allowedValues") != stateType.possibleValues()) { - qCWarning(dcDeviceManager) << "DeviceClass" << deviceClass.name() << "claims to implement interface" << value.toString() << "but state" << stateMap.value("name").toString() << "has not matching allowed values" << stateMap.value("allowedValues") << "!=" << stateType.possibleValues(); - valid = false; - continue; - } - if (stateMap.contains("writable") && stateMap.value("writable").toBool() && actionTypes.findById(ActionTypeId(stateType.id().toString())).id().isNull()) { - qCWarning(dcDeviceManager) << "DeviceClass" << deviceClass.name() << "claims to implement interface" << value.toString() << "but state" << stateMap.value("name").toString() << "is not writable while it should be"; + if (!ifaceStateType.possibleValues().isEmpty() && ifaceStateType.possibleValues() != stateType.possibleValues()) { + qCWarning(dcDeviceManager) << "DeviceClass" << deviceClass.name() << "claims to implement interface" << value.toString() << "but state" << stateType.name() << "has not matching allowed values" << ifaceStateType.possibleValues() << "!=" << stateType.possibleValues(); valid = false; continue; } } - QVariantList actions = interfaceMap.value("actions").toList(); - foreach (const QVariant &actionVariant, actions) { - QVariantMap actionMap = actionVariant.toMap(); - ActionType actionType = actionTypes.findByName(actionMap.value("name").toString()); + foreach (const ActionType &ifaceActionType, iface.actionTypes()) { + ActionType actionType = actionTypes.findByName(ifaceActionType.name()); if (actionType.id().isNull()) { - qCWarning(dcDeviceManager) << "DeviceClass" << deviceClass.name() << "claims to implement interface" << value.toString() << "but doesn't implement action" << actionMap.value("name").toString(); + qCWarning(dcDeviceManager) << "DeviceClass" << deviceClass.name() << "claims to implement interface" << value.toString() << "but doesn't implement action" << ifaceActionType.name(); valid = false; } - QVariantList params = actionMap.value("params").toList(); - foreach (const QVariant ¶mVariant, params) { - ParamType paramType = actionType.paramTypes().findByName(paramVariant.toMap().value("name").toString()); + foreach (const ParamType &ifaceActionParamType, ifaceActionType.paramTypes()) { + ParamType paramType = actionType.paramTypes().findByName(ifaceActionParamType.name()); if (!paramType.isValid()) { - qCWarning(dcDeviceManager) << "DeviceClass" << deviceClass.name() << "claims to implement interface" << value.toString() << "but doesn't implement action param" << actionMap.value("name").toString() << ":" << paramVariant.toMap().value("name").toString(); + qCWarning(dcDeviceManager) << "DeviceClass" << deviceClass.name() << "claims to implement interface" << value.toString() << "but doesn't implement action param" << ifaceActionType.name() << ":" << ifaceActionParamType.name(); valid = false; } else { - if (paramType.type() != QVariant::nameToType(paramVariant.toMap().value("type").toString().toLatin1())) { - qCWarning(dcDeviceManager()) << "DeviceClass" << deviceClass.name() << "claims to implement interface" << value.toString() << "but param" << paramType.name() << "is of wrong type:" << QVariant::typeToName(paramType.type()) << "expected:" << paramVariant.toMap().value("type").toString(); + if (paramType.type() != ifaceActionParamType.type()) { + qCWarning(dcDeviceManager()) << "DeviceClass" << deviceClass.name() << "claims to implement interface" << value.toString() << "but param" << paramType.name() << "is of wrong type:" << QVariant::typeToName(paramType.type()) << "expected:" << QVariant::typeToName(ifaceActionParamType.type()); valid = false; } } } } - QVariantList events = interfaceMap.value("events").toList(); - foreach (const QVariant &eventVariant, events) { - QVariantMap eventMap = eventVariant.toMap(); - EventType eventType = eventTypes.findByName(eventMap.value("name").toString()); - if (eventType.isValid()) { - qCWarning(dcDeviceManager) << "DeviceClass" << deviceClass.name() << "claims to implement interface" << value.toString() << "but doesn't implement event" << eventMap.value("name").toString(); + foreach (const EventType &ifaceEventType, iface.eventTypes()) { + EventType eventType = eventTypes.findByName(ifaceEventType.name()); + if (!eventType.isValid()) { + qCWarning(dcDeviceManager) << "DeviceClass" << deviceClass.name() << "claims to implement interface" << value.toString() << "but doesn't implement event" << ifaceEventType.name(); valid = false; } - QVariantList params = eventMap.value("params").toList(); - foreach (const QVariant ¶mVariant, params) { - ParamType paramType = eventType.paramTypes().findByName(paramVariant.toMap().value("name").toString()); + foreach (const ParamType &ifaceEventParamType, ifaceEventType.paramTypes()) { + ParamType paramType = eventType.paramTypes().findByName(ifaceEventParamType.name()); if (!paramType.isValid()) { - qCWarning(dcDeviceManager) << "DeviceClass" << deviceClass.name() << "claims to implement interface" << value.toString() << "but doesn't implement action param" << eventMap.value("name").toString() << ":" << paramVariant.toMap().value("name").toString(); + qCWarning(dcDeviceManager) << "DeviceClass" << deviceClass.name() << "claims to implement interface" << value.toString() << "but doesn't implement event param" << ifaceEventType.name() << ":" << ifaceEventParamType.name(); valid = false; } else { - if (paramType.type() != QVariant::nameToType(paramVariant.toMap().value("type").toString().toLatin1())) { - qCWarning(dcDeviceManager()) << "DeviceClass" << deviceClass.name() << "claims to implement interface" << value.toString() << "but param" << paramType.name() << "is of wrong type:" << QVariant::typeToName(paramType.type()) << "expected:" << paramVariant.toMap().value("type").toString(); + if (paramType.type() != ifaceEventParamType.type()) { + qCWarning(dcDeviceManager()) << "DeviceClass" << deviceClass.name() << "claims to implement interface" << value.toString() << "but param" << paramType.name() << "is of wrong type:" << QVariant::typeToName(paramType.type()) << "expected:" << QVariant::typeToName(ifaceEventParamType.type()); valid = false; } } @@ -1038,36 +1025,100 @@ QPair DevicePlugin::loadAndVerifyDeviceIcon(const return QPair(true, (DeviceClass::DeviceIcon)enumValue); } -QVariantMap DevicePlugin::loadInterface(const QString &name) +Interfaces DevicePlugin::allInterfaces() { + Interfaces ret; + QDir dir(":/interfaces/"); + foreach (const QFileInfo &ifaceFile, dir.entryInfoList()) { + ret.append(loadInterface(ifaceFile.baseName())); + } + return ret; +} + +Interface DevicePlugin::loadInterface(const QString &name) +{ + Interface iface; QFile f(QString(":/interfaces/%1.json").arg(name)); if (!f.open(QFile::ReadOnly)) { qCWarning(dcDeviceManager()) << "Failed to load interface" << name; - return QVariantMap(); + return iface; } QJsonParseError error; QJsonDocument jsonDoc = QJsonDocument::fromJson(f.readAll(), &error); if (error.error != QJsonParseError::NoError) { qCWarning(dcDeviceManager) << "Cannot load interface definition for interface" << name << ":" << error.errorString(); - return QVariantMap(); + return iface; } QVariantMap content = jsonDoc.toVariant().toMap(); if (content.contains("extends")) { - QVariantMap parentContent = loadInterface(content.value("extends").toString()); - - QVariantList statesList = content.value("states").toList(); - statesList.append(parentContent.value("states").toList()); - content["states"] = statesList; - - QVariantList actionsList = content.value("actions").toList(); - actionsList.append(parentContent.value("actions").toList()); - content["actions"] = actionsList; - - QVariantList eventsList = content.value("events").toList(); - eventsList.append(parentContent.value("events").toList()); - content["events"] = eventsList; + iface = loadInterface(content.value("extends").toString()); } - return content; + + StateTypes stateTypes; + ActionTypes actionTypes; + EventTypes eventTypes; + foreach (const QVariant &stateVariant, content.value("states").toList()) { + StateType stateType(StateTypeId::fromUuid(QUuid())); + stateType.setName(stateVariant.toMap().value("name").toString()); + stateType.setType(QVariant::nameToType(stateVariant.toMap().value("type").toByteArray())); + stateType.setPossibleValues(stateVariant.toMap().value("allowedValues").toList()); + stateType.setMinValue(stateVariant.toMap().value("minValue")); + stateType.setMaxValue(stateVariant.toMap().value("maxValue")); + stateTypes.append(stateType); + + EventType stateChangeEventType(EventTypeId::fromUuid(QUuid())); + stateChangeEventType.setName(stateType.name()); + ParamType stateChangeEventParamType; + stateChangeEventParamType.setName(stateType.name()); + stateChangeEventParamType.setType(stateType.type()); + stateChangeEventParamType.setAllowedValues(stateType.possibleValues()); + stateChangeEventParamType.setMinValue(stateType.minValue()); + stateChangeEventParamType.setMaxValue(stateType.maxValue()); + stateChangeEventType.setParamTypes(ParamTypes() << stateChangeEventParamType); + eventTypes.append(stateChangeEventType); + + if (stateVariant.toMap().value("writable", false).toBool()) { + ActionType stateChangeActionType(ActionTypeId::fromUuid(QUuid())); + stateChangeActionType.setName(stateType.name()); + stateChangeActionType.setParamTypes(ParamTypes() << stateChangeEventParamType); + actionTypes.append(stateChangeActionType); + } + } + + foreach (const QVariant &actionVariant, content.value("actions").toList()) { + ActionType actionType(ActionTypeId::fromUuid(QUuid())); + actionType.setName(actionVariant.toMap().value("name").toString()); + ParamTypes paramTypes; + foreach (const QVariant &actionParamVariant, actionVariant.toMap().value("params").toList()) { + ParamType paramType; + paramType.setName(actionParamVariant.toMap().value("name").toString()); + paramType.setType(QVariant::nameToType(actionParamVariant.toMap().value("type").toByteArray())); + paramType.setAllowedValues(actionParamVariant.toMap().value("allowedValues").toList()); + paramType.setMinValue(actionParamVariant.toMap().value("min")); + paramTypes.append(paramType); + } + actionType.setParamTypes(paramTypes); + actionTypes.append(actionType); + } + + foreach (const QVariant &eventVariant, content.value("events").toList()) { + EventType eventType(EventTypeId::fromUuid(QUuid())); + eventType.setName(eventVariant.toMap().value("name").toString()); + ParamTypes paramTypes; + foreach (const QVariant &eventParamVariant, eventVariant.toMap().value("params").toList()) { + ParamType paramType; + paramType.setName(eventParamVariant.toMap().value("name").toString()); + paramType.setType(QVariant::nameToType(eventParamVariant.toMap().value("type").toByteArray())); + paramType.setAllowedValues(eventParamVariant.toMap().value("allowedValues").toList()); + paramType.setMinValue(eventParamVariant.toMap().value("minValue")); + paramType.setMaxValue(eventParamVariant.toMap().value("maxValue")); + paramTypes.append(paramType); + } + eventType.setParamTypes(paramTypes); + eventTypes.append(eventType); + } + + return Interface(name, iface.actionTypes() << actionTypes, iface.eventTypes() << eventTypes, iface.stateTypes() << stateTypes); } QStringList DevicePlugin::generateInterfaceParentList(const QString &interface) diff --git a/libguh/plugin/deviceplugin.h b/libguh/plugin/deviceplugin.h index a6633f4f..3d615fe3 100644 --- a/libguh/plugin/deviceplugin.h +++ b/libguh/plugin/deviceplugin.h @@ -25,11 +25,11 @@ #define DEVICEPLUGIN_H #include "devicemanager.h" -#include "deviceclass.h" #include "libguh.h" #include "typeutils.h" +#include "types/deviceclass.h" #include "types/event.h" #include "types/action.h" #include "types/vendor.h" @@ -119,7 +119,11 @@ private: QPair loadAndVerifyBasicTag(const QString &basicTag) const; QPair loadAndVerifyDeviceIcon(const QString &deviceIcon) const; - static QVariantMap loadInterface(const QString &name); + // FIXME: This is expensive because it will open all the files. + // Once DeviceManager is in libguh-core this should probably be there too. + // I didn't want to add even more dependencies on the devicemanager into here, so reading the list here for now. + static Interfaces allInterfaces(); + static Interface loadInterface(const QString &name); static QStringList generateInterfaceParentList(const QString &interface); QTranslator *m_translator = nullptr; diff --git a/libguh/types/actiontype.h b/libguh/types/actiontype.h index f3fb9a6b..31234b98 100644 --- a/libguh/types/actiontype.h +++ b/libguh/types/actiontype.h @@ -60,6 +60,7 @@ private: class ActionTypes: public QList { public: + ActionTypes() = default; ActionTypes(const QList &other); ActionType findByName(const QString &name); ActionType findById(const ActionTypeId &id); diff --git a/libguh/plugin/deviceclass.cpp b/libguh/types/deviceclass.cpp similarity index 99% rename from libguh/plugin/deviceclass.cpp rename to libguh/types/deviceclass.cpp index f7036064..7e9454b1 100644 --- a/libguh/plugin/deviceclass.cpp +++ b/libguh/types/deviceclass.cpp @@ -336,7 +336,7 @@ bool DeviceClass::hasStateType(const StateTypeId &stateTypeId) /*! Returns the eventTypes of this DeviceClass. \{Device}{Devices} created from this \l{DeviceClass} must have their events matching to this template. */ -QList DeviceClass::eventTypes() const +EventTypes DeviceClass::eventTypes() const { return m_eventTypes; } @@ -361,7 +361,7 @@ bool DeviceClass::hasEventType(const EventTypeId &eventTypeId) /*! Returns the actionTypes of this DeviceClass. \{Device}{Devices} created from this \l{DeviceClass} must have their actions matching to this template. */ -QList DeviceClass::actionTypes() const +ActionTypes DeviceClass::actionTypes() const { return m_actionTypes; } diff --git a/libguh/plugin/deviceclass.h b/libguh/types/deviceclass.h similarity index 98% rename from libguh/plugin/deviceclass.h rename to libguh/types/deviceclass.h index 6cf5e069..9e72acaa 100644 --- a/libguh/plugin/deviceclass.h +++ b/libguh/types/deviceclass.h @@ -152,11 +152,11 @@ public: void setStateTypes(const QList &stateTypes); bool hasStateType(const StateTypeId &stateTypeId); - QList eventTypes() const; + EventTypes eventTypes() const; void setEventTypes(const QList &eventTypes); bool hasEventType(const EventTypeId &eventTypeId); - QList actionTypes() const; + ActionTypes actionTypes() const; void setActionTypes(const QList &actionTypes); bool hasActionType(const ActionTypeId &actionTypeId); diff --git a/libguh/types/eventdescriptor.cpp b/libguh/types/eventdescriptor.cpp index 7bfb2997..789c891d 100644 --- a/libguh/types/eventdescriptor.cpp +++ b/libguh/types/eventdescriptor.cpp @@ -31,6 +31,11 @@ An EventDescriptor describes an \l{Event} in order to match it with a \l{guhserver::Rule}. + An EventDescriptor can either be bound to a certain device/eventtype, or to an interface. + If an event is bound to a device, it will only match when the given device fires the given event. + If an event is bound to an interface, it will match the given event for all the devices implementing + the given interface. + \sa Event, EventType, guhserver::Rule */ @@ -44,6 +49,25 @@ EventDescriptor::EventDescriptor(const EventTypeId &eventTypeId, const DeviceId { } +EventDescriptor::EventDescriptor(const QString &interface, const QString &interfaceEvent, const QList ¶mDescriptors): + m_interface(interface), + m_interfaceEvent(interfaceEvent), + m_paramDescriptors(paramDescriptors) +{ + +} + +EventDescriptor::Type EventDescriptor::type() const +{ + return (!m_deviceId.isNull() && !m_eventTypeId.isNull()) ? TypeDevice : TypeInterface; +} + +/*! Returns true if the EventDescriptor is valid, that is, when it has either enough data to describe a device/eventType or an interface/interfaceEvent pair. */ +bool EventDescriptor::isValid() const +{ + return (!m_deviceId.isNull() && !m_eventTypeId.isNull()) || (!m_interface.isEmpty() && !m_interfaceEvent.isEmpty()); +} + /*! Returns the id of the \l{EventType} which describes this Event. */ EventTypeId EventDescriptor::eventTypeId() const { @@ -56,6 +80,18 @@ DeviceId EventDescriptor::deviceId() const return m_deviceId; } +/*! Returns the interface associated with this EventDescriptor. */ +QString EventDescriptor::interface() const +{ + return m_interface; +} + +/*! Returns the interface's event name associated with this EventDescriptor.*/ +QString EventDescriptor::interfaceEvent() const +{ + return m_interfaceEvent; +} + /*! Returns the parameters of this Event. */ QList EventDescriptor::paramDescriptors() const { @@ -97,51 +133,6 @@ bool EventDescriptor::operator ==(const EventDescriptor &other) const && paramsMatch; } -/*! Compare this EventDescriptor to the Event given by \a event. - * Events are equal (returns true) if eventTypeId, deviceId and params match. */ -bool EventDescriptor::operator ==(const Event &event) const -{ - if (m_eventTypeId != event.eventTypeId() || m_deviceId != event.deviceId()) { - return false; - } - - foreach (const ParamDescriptor ¶mDescriptor, m_paramDescriptors) { - switch (paramDescriptor.operatorType()) { - case Types::ValueOperatorEquals: - if (event.param(paramDescriptor.paramTypeId()).value() != paramDescriptor.value()) { - return false; - } - break; - case Types::ValueOperatorNotEquals: - if (event.param(paramDescriptor.paramTypeId()).value() == paramDescriptor.value()) { - return false; - } - break; - case Types::ValueOperatorGreater: - if (event.param(paramDescriptor.paramTypeId()).value() <= paramDescriptor.value()) { - return false; - } - break; - case Types::ValueOperatorGreaterOrEqual: - if (event.param(paramDescriptor.paramTypeId()).value() < paramDescriptor.value()) { - return false; - } - break; - case Types::ValueOperatorLess: - if (event.param(paramDescriptor.paramTypeId()).value() >= paramDescriptor.value()) { - return false; - } - break; - case Types::ValueOperatorLessOrEqual: - if (event.param(paramDescriptor.paramTypeId()).value() < paramDescriptor.value()) { - return false; - } - break; - } - } - return true; -} - /*! Writes the eventTypeId and the deviceId of the given \a eventDescriptor to \a dbg. */ QDebug operator<<(QDebug dbg, const EventDescriptor &eventDescriptor) { diff --git a/libguh/types/eventdescriptor.h b/libguh/types/eventdescriptor.h index ce60e4ac..b10660c9 100644 --- a/libguh/types/eventdescriptor.h +++ b/libguh/types/eventdescriptor.h @@ -36,22 +36,34 @@ class LIBGUH_EXPORT EventDescriptor { public: + enum Type { + TypeDevice, + TypeInterface + }; + EventDescriptor(const EventTypeId &eventTypeId, const DeviceId &deviceId, const QList ¶mDescriptors = QList()); + EventDescriptor(const QString &interface, const QString &interfaceEvent, const QList ¶mDescriptors = QList()); + + Type type() const; + bool isValid() const; EventTypeId eventTypeId() const; DeviceId deviceId() const; + QString interface() const; + QString interfaceEvent() const; + QList paramDescriptors() const; void setParamDescriptors(const QList ¶mDescriptors); ParamDescriptor paramDescriptor(const ParamTypeId ¶mTypeId) const; bool operator ==(const EventDescriptor &other) const; - bool operator ==(const Event &event) const; - private: EventTypeId m_eventTypeId; DeviceId m_deviceId; + QString m_interface; + QString m_interfaceEvent; QList m_paramDescriptors; }; diff --git a/libguh/types/eventtype.h b/libguh/types/eventtype.h index e6ad3818..c1b764f5 100644 --- a/libguh/types/eventtype.h +++ b/libguh/types/eventtype.h @@ -69,6 +69,7 @@ private: class EventTypes: public QList { public: + EventTypes() = default; EventTypes(const QList &other); EventType findByName(const QString &name); EventType findById(const EventTypeId &id); diff --git a/libguh/types/interface.cpp b/libguh/types/interface.cpp new file mode 100644 index 00000000..e3a6b787 --- /dev/null +++ b/libguh/types/interface.cpp @@ -0,0 +1,74 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2017 Michael Zanetti * + * * + * This file is part of guh. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; If not, see * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "interface.h" + +Interface::Interface(const QString &name, const ActionTypes &actionTypes, const EventTypes &eventTypes, const StateTypes &stateTypes): + m_name(name), + m_actionTypes(actionTypes), + m_eventTypes(eventTypes), + m_stateTypes(stateTypes) +{ + +} + +QString Interface::name() const +{ + return m_name; +} + +ActionTypes Interface::actionTypes() const +{ + return m_actionTypes; +} + +EventTypes Interface::eventTypes() const +{ + return m_eventTypes; +} + +StateTypes Interface::stateTypes() const +{ + return m_stateTypes; +} + +bool Interface::isValid() const +{ + return !m_name.isEmpty(); +} + +Interfaces::Interfaces(const QList &other) +{ + foreach (const Interface &iface, other) { + append(iface); + } +} + +Interface Interfaces::findByName(const QString &name) +{ + foreach (const Interface &interface, *this) { + if (interface.name() == name) { + return interface; + } + } + return Interface(); +} diff --git a/libguh/types/interface.h b/libguh/types/interface.h new file mode 100644 index 00000000..027ae5be --- /dev/null +++ b/libguh/types/interface.h @@ -0,0 +1,58 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2017 Michael Zanetti * + * * + * This file is part of guh. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; If not, see * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef INTERFACE_H +#define INTERFACE_H + +#include "eventtype.h" +#include "actiontype.h" +#include "statetype.h" + +class Interface{ +public: + Interface() = default; + Interface(const QString &name, const ActionTypes &actionTypes, const EventTypes &eventTypes, const StateTypes &stateTypes); + + QString name() const; + + ActionTypes actionTypes() const; + EventTypes eventTypes() const; + StateTypes stateTypes() const; + + bool isValid() const; +private: + QUuid m_id; + QString m_name; + ActionTypes m_actionTypes; + EventTypes m_eventTypes; + StateTypes m_stateTypes; +}; + +class Interfaces: public QList +{ +public: + Interfaces() = default; + Interfaces(const QList &other); + Interface findByName(const QString &name); +}; + +#endif // INTERFACE_H diff --git a/libguh/types/ruleaction.cpp b/libguh/types/ruleaction.cpp index 30af70e3..961f502f 100644 --- a/libguh/types/ruleaction.cpp +++ b/libguh/types/ruleaction.cpp @@ -38,18 +38,31 @@ #include "ruleaction.h" /*! Constructs a RuleAction with the given by \a actionTypeId and \a deviceId. */ -RuleAction::RuleAction(const ActionTypeId &actionTypeId, const DeviceId &deviceId) : +RuleAction::RuleAction(const ActionTypeId &actionTypeId, const DeviceId &deviceId, const RuleActionParamList ¶ms): m_id(ActionId::createActionId()), m_actionTypeId(actionTypeId), - m_deviceId(deviceId) + m_deviceId(deviceId), + m_ruleActionParams(params) { } + +/*! Constructs a RuleAction with the given by \a interface and \a interfaceAction. */ +RuleAction::RuleAction(const QString &interface, const QString &interfaceAction, const RuleActionParamList ¶ms) : + m_interface(interface), + m_interfaceAction(interfaceAction), + m_ruleActionParams(params) +{ + +} + /*! Constructs a copy of the given \a other RuleAction. */ RuleAction::RuleAction(const RuleAction &other) : m_id(other.id()), m_actionTypeId(other.actionTypeId()), m_deviceId(other.deviceId()), + m_interface(other.interface()), + m_interfaceAction(other.interfaceAction()), m_ruleActionParams(other.ruleActionParams()) { @@ -64,7 +77,13 @@ ActionId RuleAction::id() const /*! Return true, if the actionTypeId and the deviceId of this RuleAction are valid (set).*/ bool RuleAction::isValid() const { - return !m_actionTypeId.isNull() && !m_deviceId.isNull(); + return (!m_actionTypeId.isNull() && !m_deviceId.isNull()) || (!m_interface.isEmpty() && !m_interfaceAction.isEmpty()); +} + +/*! Returns whether this RuleAction is targetting a specific device or rather an interface. */ +RuleAction::Type RuleAction::type() const +{ + return (!m_actionTypeId.isNull() && !m_deviceId.isNull()) ? TypeDevice : TypeInterface; } /*! Return true, if this RuleAction contains a \l{RuleActionParam} which is based on an EventTypeId.*/ @@ -103,6 +122,18 @@ DeviceId RuleAction::deviceId() const return m_deviceId; } +/*! Returns the name of the interface associated with this RuleAction. */ +QString RuleAction::interface() const +{ + return m_interface; +} + +/*! Returns the name of the action of the associated interface. */ +QString RuleAction::interfaceAction() const +{ + return m_interfaceAction; +} + /*! Returns the \l{RuleActionParamList} of this RuleAction. * \sa RuleActionParam, */ RuleActionParamList RuleAction::ruleActionParams() const @@ -118,7 +149,7 @@ void RuleAction::setRuleActionParams(const RuleActionParamList &ruleActionParams } /*! Returns the \l{RuleActionParam} of this RuleAction with the given \a ruleActionParamTypeId. - * If there is no \l{RuleActionParam} with th given name an invalid \l{RuleActionParam} will be returnend. + * If there is no \l{RuleActionParam} with th given id an invalid \l{RuleActionParam} will be returnend. * \sa RuleActionParam, */ RuleActionParam RuleAction::ruleActionParam(const ParamTypeId &ruleActionParamTypeId) const { @@ -130,6 +161,19 @@ RuleActionParam RuleAction::ruleActionParam(const ParamTypeId &ruleActionParamTy return RuleActionParam(QString()); } +/*! Returns the \l{RuleActionParam} of this RuleAction with the given \a ruleActionParamName. + * If there is no \l{RuleActionParam} with th given name an invalid \l{RuleActionParam} will be returnend. + * \sa RuleActionParam, */ +RuleActionParam RuleAction::ruleActionParam(const QString &ruleActionParamName) const +{ + foreach (const RuleActionParam &ruleActionParam, m_ruleActionParams) { + if (ruleActionParam.paramName() == ruleActionParamName) { + return ruleActionParam; + } + } + return RuleActionParam(QString()); +} + /*! Copy the data to a \l{RuleAction} from an \a other rule action. */ void RuleAction::operator=(const RuleAction &other) { diff --git a/libguh/types/ruleaction.h b/libguh/types/ruleaction.h index f8944485..a9b14472 100644 --- a/libguh/types/ruleaction.h +++ b/libguh/types/ruleaction.h @@ -31,12 +31,19 @@ class LIBGUH_EXPORT RuleAction { public: - explicit RuleAction(const ActionTypeId &actionTypeId = ActionTypeId(), const DeviceId &deviceId = DeviceId()); + enum Type { + TypeDevice, + TypeInterface + }; + explicit RuleAction(const ActionTypeId &actionTypeId = ActionTypeId(), const DeviceId &deviceId = DeviceId(), const RuleActionParamList ¶ms = RuleActionParamList()); + explicit RuleAction(const QString &interface, const QString &interfaceAction, const RuleActionParamList ¶ms = RuleActionParamList()); RuleAction(const RuleAction &other); ActionId id() const; bool isValid() const; + Type type() const; + bool isEventBased() const; Action toAction() const; @@ -44,9 +51,13 @@ public: ActionTypeId actionTypeId() const; DeviceId deviceId() const; + QString interface() const; + QString interfaceAction() const; + RuleActionParamList ruleActionParams() const; void setRuleActionParams(const RuleActionParamList &ruleActionParams); RuleActionParam ruleActionParam(const ParamTypeId &ruleActionParamTypeId) const; + RuleActionParam ruleActionParam(const QString &ruleActionParamName) const; void operator=(const RuleAction &other); @@ -54,6 +65,8 @@ private: ActionId m_id; ActionTypeId m_actionTypeId; DeviceId m_deviceId; + QString m_interface; + QString m_interfaceAction; RuleActionParamList m_ruleActionParams; }; diff --git a/libguh/types/ruleactionparam.cpp b/libguh/types/ruleactionparam.cpp index 7b4a1b45..33b5430c 100644 --- a/libguh/types/ruleactionparam.cpp +++ b/libguh/types/ruleactionparam.cpp @@ -57,12 +57,28 @@ RuleActionParam::RuleActionParam(const ParamTypeId ¶mTypeId, const QVariant { } +/*! Constructs a \l{RuleActionParam} with the given \a paramName, \a value, \a eventTypeId and \a eventParamTypeId. + * \sa Param, Event, */ +RuleActionParam::RuleActionParam(const QString ¶mName, const QVariant &value, const EventTypeId &eventTypeId, const ParamTypeId &eventParamTypeId): + m_paramName(paramName), + m_value(value), + m_eventTypeId(eventTypeId), + m_eventParamTypeId(eventParamTypeId) +{ + +} + /*! Returns the \l ParamTypeId of this \l RuleActionParam. */ ParamTypeId RuleActionParam::paramTypeId() const { return m_paramTypeId; } +QString RuleActionParam::paramName() const +{ + return m_paramName; +} + /*! Returns the eventParamTypeId of this RuleActionParam. */ ParamTypeId RuleActionParam::eventParamTypeId() const { @@ -136,6 +152,17 @@ bool RuleActionParamList::hasParam(const ParamTypeId &ruleActionParamTypeId) con return m_ids.contains(ruleActionParamTypeId); } +/*! Returns true if this \l{RuleActionParamList} contains a \l{RuleActionParam} with the given \a ruleActionParamName. */ +bool RuleActionParamList::hasParam(const QString &ruleActionParamName) const +{ + foreach (const RuleActionParam ¶m, *this) { + if (param.paramName() == ruleActionParamName) { + return true; + } + } + return false; +} + /*! Returns the value of the \l{RuleActionParam} with the given \a ruleActionParamTypeId. */ QVariant RuleActionParamList::paramValue(const ParamTypeId &ruleActionParamTypeId) const { diff --git a/libguh/types/ruleactionparam.h b/libguh/types/ruleactionparam.h index a937de69..c096e399 100644 --- a/libguh/types/ruleactionparam.h +++ b/libguh/types/ruleactionparam.h @@ -36,9 +36,11 @@ class LIBGUH_EXPORT RuleActionParam { public: RuleActionParam(const Param ¶m = Param()); - RuleActionParam(const ParamTypeId ¶mTypeId, const QVariant &value = QVariant(), const EventTypeId &eventTypeId = EventTypeId(), const ParamTypeId &eventParamName = ParamTypeId()); + 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()); ParamTypeId paramTypeId() const; + QString paramName() const; ParamTypeId eventParamTypeId() const; void setEventParamTypeId(const ParamTypeId &eventParamTypeId); @@ -53,6 +55,7 @@ public: private: ParamTypeId m_paramTypeId; + QString m_paramName; QVariant m_value; EventTypeId m_eventTypeId; ParamTypeId m_eventParamTypeId; @@ -65,6 +68,7 @@ class LIBGUH_EXPORT RuleActionParamList: public QList { public: bool hasParam(const ParamTypeId &ruleActionParamTypeId) const; + bool hasParam(const QString &ruleActionParamName) const; QVariant paramValue(const ParamTypeId &ruleActionParamName) const; bool setParamValue(const ParamTypeId &ruleActionParamTypeId, const QVariant &value); RuleActionParamList operator<<(const RuleActionParam &ruleActionParam); diff --git a/libguh/types/statetype.h b/libguh/types/statetype.h index de7fe03b..2e0d9670 100644 --- a/libguh/types/statetype.h +++ b/libguh/types/statetype.h @@ -92,6 +92,7 @@ private: class StateTypes: public QList { public: + StateTypes() = default; StateTypes(const QList &other); StateType findByName(const QString &name); StateType findById(const StateTypeId &id); diff --git a/plugins/mock/devicepluginmock.json b/plugins/mock/devicepluginmock.json index 8e054b19..c7d94aba 100644 --- a/plugins/mock/devicepluginmock.json +++ b/plugins/mock/devicepluginmock.json @@ -31,7 +31,7 @@ "name": "mock", "displayName": "Mock Device", "deviceIcon": "Tune", - "interfaces": ["gateway", "light", "mediacontroller"], + "interfaces": ["gateway", "light", "mediacontroller", "battery"], "basicTags": [ "Device", "Actuator", @@ -91,7 +91,36 @@ "defaultValue": false, "type": "bool", "cached": false + }, + { + "id": "6c8ab9a6-0164-4795-b829-f4394fe4edc4", + "name": "batteryLevel", + "displayName": "battery level", + "displayNameEvent": "battery level", + "type": "int", + "minValue": 0, + "maxValue": 100, + "defaultValue": 0 + }, + { + "id": "580bc611-1a55-41f3-996f-8d3ccf543db3", + "name": "batteryCritical", + "displayName": "battery level critical", + "displayNameEvent": "battery level critical", + "type": "bool", + "defaultValue": false + }, + { + "id": "064aed0d-da4c-49d4-b236-60f97e98ff84", + "name": "power", + "displayName": "powered", + "displayNameEvent": "powered changed", + "displayNameAction": "set power", + "type": "bool", + "defaultValue": false, + "writable": true } + ], "eventTypes": [ { diff --git a/plugins/mock/httpdaemon.cpp b/plugins/mock/httpdaemon.cpp index d42e7829..6cc9d53f 100644 --- a/plugins/mock/httpdaemon.cpp +++ b/plugins/mock/httpdaemon.cpp @@ -24,8 +24,8 @@ #include "httpdaemon.h" #include "plugin/device.h" -#include "plugin/deviceclass.h" #include "plugin/deviceplugin.h" +#include "types/deviceclass.h" #include "types/statetype.h" #include "extern-plugininfo.h" @@ -137,7 +137,13 @@ QString HttpDaemon::generateHeader() QString HttpDaemon::generateWebPage() { - DeviceClass deviceClass = m_plugin->supportedDevices().first(); + DeviceClass deviceClass; + foreach (const DeviceClass &dc, m_plugin->supportedDevices()) { + if (dc.id() == m_device->deviceClassId()) { + deviceClass = dc; + } + } + Q_ASSERT(deviceClass.isValid()); QString body = QString( "" @@ -156,7 +162,7 @@ QString HttpDaemon::generateWebPage() for (int i = 0; i < deviceClass.stateTypes().count(); ++i) { body.append(""); body.append("
"); - const StateType &stateType = deviceClass.stateTypes().at(i); + StateType stateType = deviceClass.stateTypes().at(i); body.append("" + stateType.name() + ""); body.append(QString("").arg(stateType.id().toString()).arg(m_device->states().at(i).value().toString())); body.append(""); @@ -170,14 +176,14 @@ QString HttpDaemon::generateWebPage() body.append(""); for (int i = 0; i < deviceClass.eventTypes().count(); ++i) { - const EventType &eventType = deviceClass.eventTypes().at(i); + EventType eventType = deviceClass.eventTypes().at(i); body.append(QString( "" "" "" "" "" @@ -211,7 +217,6 @@ QString HttpDaemon::generateWebPage() } body.append("
%1").arg(eventType.name()).arg(eventType.id().toString())); - if (!eventType.name().endsWith(" changed")) { - body.append(""); + if (!eventType.displayName().endsWith(" changed")) { + body.append(QStringLiteral("")); } body.append("
"); - body.append("\n"); return generateHeader() + body; diff --git a/tests/auto/api.json b/tests/auto/api.json index 2c159703..809be9c3 100644 --- a/tests/auto/api.json +++ b/tests/auto/api.json @@ -1,4 +1,4 @@ -1.0 +1.1 { "methods": { "Actions.ExecuteAction": { @@ -1152,8 +1152,10 @@ ] }, "EventDescriptor": { - "deviceId": "Uuid", - "eventTypeId": "Uuid", + "o:deviceId": "Uuid", + "o:eventTypeId": "Uuid", + "o:interface": "String", + "o:interfaceEvent": "String", "o:paramDescriptors": [ "$ref:ParamDescriptor" ] @@ -1325,8 +1327,10 @@ "timeDescriptor": "$ref:TimeDescriptor" }, "RuleAction": { - "actionTypeId": "Uuid", - "deviceId": "Uuid", + "o:actionTypeId": "Uuid", + "o:deviceId": "Uuid", + "o:interface": "String", + "o:interfaceAction": "String", "o:ruleActionParams": [ "$ref:RuleActionParam" ] @@ -1334,8 +1338,9 @@ "RuleActionParam": { "o:eventParamTypeId": "Uuid", "o:eventTypeId": "Uuid", - "o:value": "$ref:BasicType", - "paramTypeId": "Uuid" + "o:paramName": "String", + "o:paramTypeId": "Uuid", + "o:value": "$ref:BasicType" }, "RuleDescription": { "active": "Bool", @@ -1364,7 +1369,8 @@ "RuleErrorInvalidCalendarItem", "RuleErrorInvalidTimeEventItem", "RuleErrorContainsEventBasesAction", - "RuleErrorNoExitActions" + "RuleErrorNoExitActions", + "RuleErrorInterfaceNotFound" ], "ServerConfiguration": { "address": "String", diff --git a/tests/auto/devices/testdevices.cpp b/tests/auto/devices/testdevices.cpp index 15547289..fb6eee43 100644 --- a/tests/auto/devices/testdevices.cpp +++ b/tests/auto/devices/testdevices.cpp @@ -241,9 +241,13 @@ void TestDevices::verifyInterfaces() QVERIFY(!mockDevice.isEmpty()); QVariantList interfaces = mockDevice.value("interfaces").toList(); - // Must contain gateway, but must not contain anything else as device manager should filter it away - QCOMPARE(interfaces.count() == 1, true); + // Must contain gateway, light and battery, but must not contain mediacontroller as the device manager should filter + // that away because it doesn't implement all the required states. + QCOMPARE(interfaces.count(), 3); QVERIFY(interfaces.contains("gateway")); + QVERIFY(interfaces.contains("battery")); + QVERIFY(interfaces.contains("light")); + QVERIFY(!interfaces.contains("mediacontroller")); } void TestDevices::addConfiguredDevice_data() @@ -631,25 +635,33 @@ void TestDevices::parentChildDevices() void TestDevices::getActionTypes_data() { QTest::addColumn("deviceClassId"); - QTest::addColumn("resultCount"); + QTest::addColumn >("actionTypeTestData"); - QTest::newRow("valid deviceclass") << mockDeviceClassId << 5; - QTest::newRow("invalid deviceclass") << DeviceClassId("094f8024-5caa-48c1-ab6a-de486a92088f") << 0; + QTest::newRow("valid deviceclass") << mockDeviceClassId + << (QList() << mockActionIdAsync << mockActionIdAsyncFailing << mockActionIdFailing << mockActionIdNoParams << mockActionIdPower << mockActionIdWithParams); + QTest::newRow("invalid deviceclass") << DeviceClassId("094f8024-5caa-48c1-ab6a-de486a92088f") << QList(); } void TestDevices::getActionTypes() { QFETCH(DeviceClassId, deviceClassId); - QFETCH(int, resultCount); + QFETCH(QList, actionTypeTestData); QVariantMap params; params.insert("deviceClassId", deviceClassId); QVariant response = injectAndWait("Devices.GetActionTypes", params); QVariantList actionTypes = response.toMap().value("params").toMap().value("actionTypes").toList(); - QCOMPARE(actionTypes.count(), resultCount); - if (resultCount > 0) { - QCOMPARE(actionTypes.first().toMap().value("id").toString(), mockActionIdWithParams.toString()); + QCOMPARE(actionTypes.count(), actionTypeTestData.count()); + foreach (const ActionTypeId &testDataId, actionTypeTestData) { + bool found = false; + foreach (const QVariant &at, actionTypes) { + if (testDataId.toString() == at.toMap().value("id").toString()) { + found = true; + break; + } + } + QVERIFY(found); } } @@ -658,7 +670,7 @@ void TestDevices::getEventTypes_data() QTest::addColumn("deviceClassId"); QTest::addColumn("resultCount"); - QTest::newRow("valid deviceclass") << mockDeviceClassId << 4; + QTest::newRow("valid deviceclass") << mockDeviceClassId << 7; QTest::newRow("invalid deviceclass") << DeviceClassId("094f8024-5caa-48c1-ab6a-de486a92088f") << 0; } @@ -683,7 +695,7 @@ void TestDevices::getStateTypes_data() QTest::addColumn("deviceClassId"); QTest::addColumn("resultCount"); - QTest::newRow("valid deviceclass") << mockDeviceClassId << 2; + QTest::newRow("valid deviceclass") << mockDeviceClassId << 5; QTest::newRow("invalid deviceclass") << DeviceClassId("094f8024-5caa-48c1-ab6a-de486a92088f") << 0; } @@ -784,7 +796,7 @@ void TestDevices::getStateValues() QCOMPARE(response.toMap().value("params").toMap().value("deviceError").toString(), JsonTypes::deviceErrorToString(statusCode)); if (statusCode == DeviceManager::DeviceErrorNoError) { QVariantList values = response.toMap().value("params").toMap().value("values").toList(); - QCOMPARE(values.count(), 2); // Mock device has two states... + QCOMPARE(values.count(), 5); // Mock device has two states... } } @@ -1231,6 +1243,8 @@ void TestDevices::removeAutoDevice() QVERIFY2(devices.count() > 0, "There needs to be at least one auto-created Mock Device for this test"); device = devices.first(); + DeviceClass dc = GuhCore::instance()->deviceManager()->findDeviceClass(device->deviceClassId()); + // trigger disappear signal in mock device spy.clear(); port = device->paramValue(httpportParamTypeId).toInt(); diff --git a/tests/auto/guhtestbase.cpp b/tests/auto/guhtestbase.cpp index 601cb9ae..d875388a 100644 --- a/tests/auto/guhtestbase.cpp +++ b/tests/auto/guhtestbase.cpp @@ -56,6 +56,8 @@ EventTypeId mockEvent1Id = EventTypeId("45bf3752-0fc6-46b9-89fd-ffd878b5b22b"); EventTypeId mockEvent2Id = EventTypeId("863d5920-b1cf-4eb9-88bd-8f7b8583b1cf"); StateTypeId mockIntStateId = StateTypeId("80baec19-54de-4948-ac46-31eabfaceb83"); StateTypeId mockBoolStateId = StateTypeId("9dd6a97c-dfd1-43dc-acbd-367932742310"); +StateTypeId mockBatteryCriticalStateId = StateTypeId("580bc611-1a55-41f3-996f-8d3ccf543db3"); +ActionTypeId mockActionIdPower = ActionTypeId("064aed0d-da4c-49d4-b236-60f97e98ff84"); ActionTypeId mockActionIdWithParams = ActionTypeId("dea0f4e1-65e3-4981-8eaa-2701c53a9185"); ActionTypeId mockActionIdNoParams = ActionTypeId("defd3ed6-1a0d-400b-8879-a0202cf39935"); ActionTypeId mockActionIdAsync = ActionTypeId("fbae06d3-7666-483e-a39e-ec50fe89054e"); diff --git a/tests/auto/guhtestbase.h b/tests/auto/guhtestbase.h index 667af531..728ba901 100644 --- a/tests/auto/guhtestbase.h +++ b/tests/auto/guhtestbase.h @@ -47,6 +47,7 @@ extern DeviceClassId mockDeviceDiscoveryClassId; extern DeviceClassId mockDeviceAsyncSetupClassId; extern DeviceClassId mockDeviceBrokenClassId; extern DeviceClassId mockDeviceBrokenAsyncSetupClassId; +extern ActionTypeId mockActionIdPower; extern ActionTypeId mockActionIdWithParams; extern ActionTypeId mockActionIdNoParams; extern ActionTypeId mockActionIdAsync; @@ -55,6 +56,7 @@ extern ActionTypeId mockActionIdAsyncFailing; extern EventTypeId mockEvent1Id; extern EventTypeId mockEvent2Id; extern StateTypeId mockIntStateId; +extern StateTypeId mockBatteryCriticalStateId; extern StateTypeId mockBoolStateId; // ParamTypes from mock devices diff --git a/tests/auto/rules/testrules.cpp b/tests/auto/rules/testrules.cpp index 649fa6ca..bc2c5c93 100644 --- a/tests/auto/rules/testrules.cpp +++ b/tests/auto/rules/testrules.cpp @@ -99,6 +99,8 @@ private slots: void testRuleActionParams_data(); void testRuleActionParams(); + void testInterfaceBasedRule(); + void testHousekeeping_data(); void testHousekeeping(); @@ -488,7 +490,7 @@ void TestRules::addRemoveRules_data() QTest::newRow("valid rule. diabled, 1 EventDescriptor, StateEvaluator, 1 Action, name") << false << validActionNoParams << QVariantMap() << validEventDescriptor1 << QVariantList() << validStateEvaluator << RuleEngine::RuleErrorNoError << true << "TestRule"; QTest::newRow("valid rule. 2 EventDescriptors, 1 Action, name") << true << validActionNoParams << QVariantMap() << QVariantMap() << eventDescriptorList << validStateEvaluator << RuleEngine::RuleErrorNoError << true << "TestRule"; QTest::newRow("invalid action") << true << invalidAction << QVariantMap() << validEventDescriptor1 << QVariantList() << validStateEvaluator << RuleEngine::RuleErrorActionTypeNotFound << false << "TestRule"; - QTest::newRow("invalid event descriptor") << true << validActionNoParams << QVariantMap() << invalidEventDescriptor << QVariantList() << validStateEvaluator << RuleEngine::RuleErrorDeviceNotFound << false << "TestRule"; + QTest::newRow("invalid event descriptor") << true << validActionNoParams << QVariantMap() << invalidEventDescriptor << QVariantList() << validStateEvaluator << RuleEngine::RuleErrorEventTypeNotFound << false << "TestRule"; QTest::newRow("invalid StateDescriptor") << true << validActionNoParams << QVariantMap() << validEventDescriptor1 << QVariantList() << invalidStateEvaluator << RuleEngine::RuleErrorInvalidParameter << true << "TestRule"; } @@ -2065,6 +2067,44 @@ void TestRules::testRuleActionParams() verifyRuleError(response, error); } +void TestRules::testInterfaceBasedRule() +{ + QVariantMap powerAction; + powerAction.insert("interface", "light"); + powerAction.insert("interfaceAction", "power"); + QVariantMap powerActionParam; + powerActionParam.insert("paramName", "power"); + powerActionParam.insert("value", true); + powerAction.insert("ruleActionParams", QVariantList() << powerActionParam); + + QVariantMap lowBatteryEvent; + lowBatteryEvent.insert("interface", "battery"); + lowBatteryEvent.insert("interfaceEvent", "batteryCritical"); + + QVariantMap addRuleParams; + addRuleParams.insert("name", "TestInterfaceBasedRule"); + addRuleParams.insert("enabled", true); + addRuleParams.insert("actions", QVariantList() << powerAction); + addRuleParams.insert("eventDescriptors", QVariantList() << lowBatteryEvent); + + QVariant response = injectAndWait("Rules.AddRule", addRuleParams); + + + // Change the state + QNetworkAccessManager nam; + QSignalSpy spy(&nam, SIGNAL(finished(QNetworkReply*))); + + // state battery critical state to true + qDebug() << "setting battery critical state to true"; + QNetworkRequest request(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(m_mockDevice1Port).arg(mockBatteryCriticalStateId.toString()).arg(true))); + QNetworkReply *reply = nam.get(request); + spy.wait(); + QCOMPARE(spy.count(), 1); + reply->deleteLater(); + + qDebug() << "response" << response; +} + void TestRules::testHousekeeping_data() { QTest::addColumn("testAction");