diff --git a/debian/changelog b/debian/changelog index 0e799ac4..17d522f2 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,7 @@ +nymea (0.21.0) UNRELEASED; urgency=medium + + -- Michael Zanetti Thu, 09 Apr 2020 18:19:52 +0200 + nymea (0.20.0) xenial; urgency=medium [ Michael Zanetti ] diff --git a/libnymea-core/cloud/cloudnotifications.cpp b/libnymea-core/cloud/cloudnotifications.cpp index 3dd581a0..38a426e5 100644 --- a/libnymea-core/cloud/cloudnotifications.cpp +++ b/libnymea-core/cloud/cloudnotifications.cpp @@ -168,7 +168,7 @@ void CloudNotifications::startMonitoringAutoThings() void CloudNotifications::executeAction(ThingActionInfo *info) { - qCDebug(dcCloud()) << "executeAction" << info->thing() << info->action().id() << info->action().params(); + qCDebug(dcCloud()) << "executeAction" << info->thing() << info->action().params(); QString userId = info->thing()->paramValue(cloudNotificationsThingClassUserParamId).toString(); QString endpointId = info->thing()->paramValue(cloudNotificationsThingClassEndpointParamId).toString(); int id = m_awsConnector->sendPushNotification(userId, endpointId, info->action().param(notifyActionParamTitleId).value().toString(), info->action().param(notifyActionParamBodyId).value().toString()); diff --git a/libnymea-core/integrations/thingmanagerimplementation.cpp b/libnymea-core/integrations/thingmanagerimplementation.cpp index 4f0d4380..8bba515a 100644 --- a/libnymea-core/integrations/thingmanagerimplementation.cpp +++ b/libnymea-core/integrations/thingmanagerimplementation.cpp @@ -760,6 +760,13 @@ Thing::ThingError ThingManagerImplementation::removeConfiguredThing(const ThingI NymeaSettings stateCache(NymeaSettings::SettingsRoleThingStates); stateCache.remove(thingId.toString()); + foreach (const IOConnectionId &ioConnectionId, m_ioConnections.keys()) { + IOConnection ioConnection = m_ioConnections.value(ioConnectionId); + if (ioConnection.inputThingId() == thing->id() || ioConnection.outputThingId() == thing->id()) { + disconnectIO(ioConnectionId); + } + } + emit thingRemoved(thingId); return Thing::ThingErrorNoError; @@ -907,6 +914,108 @@ BrowserItemActionInfo* ThingManagerImplementation::executeBrowserItemAction(cons return info; } +IOConnections ThingManagerImplementation::ioConnections(const ThingId &thingId) const +{ + if (thingId.isNull()) { + return m_ioConnections.values(); + } + IOConnections ioConnections; + foreach (const IOConnection &ioConnection, m_ioConnections) { + if (ioConnection.inputThingId() == thingId || ioConnection.outputThingId() == thingId) { + ioConnections.append(ioConnection); + } + } + return ioConnections; +} + +Thing::ThingError ThingManagerImplementation::connectIO(const IOConnection &connection) +{ + // Do some sanity checks + Thing *inputThing = m_configuredThings.value(connection.inputThingId()); + if (!inputThing) { + qCWarning(dcThingManager()) << "Could not find inputThing" << connection.inputThingId() << "configured things. Not adding IO connection."; + return Thing::ThingErrorThingNotFound; + } + if (!inputThing->thingClass().stateTypes().contains(connection.inputStateTypeId())) { + qCWarning(dcThingManager()) << "Input thing" << inputThing->name() << "does not have a state with id" << connection.inputStateTypeId(); + return Thing::ThingErrorStateTypeNotFound; + } + StateType inputStateType = inputThing->thingClass().stateTypes().findById(connection.inputStateTypeId()); + + // Check if this is actually an input + if (inputStateType.ioType() != Types::IOTypeDigitalInput && inputStateType.ioType() != Types::IOTypeAnalogInput) { + qCWarning(dcThingManager()) << "The given input state is neither a digital nor an analog input."; + return Thing::ThingErrorInvalidParameter; + } + + Thing *outputThing = m_configuredThings.value(connection.outputThingId()); + if (!outputThing) { + qCWarning(dcThingManager()) << "Could not find outputThing" << connection.outputThingId() << "configured things. Not adding IO connection."; + return Thing::ThingErrorThingNotFound; + } + if (!outputThing->thingClass().stateTypes().contains(connection.outputStateTypeId())) { + qCWarning(dcThingManager()) << "Output thing" << outputThing->name() << "does not have a state with id" << connection.outputStateTypeId(); + return Thing::ThingErrorStateTypeNotFound; + } + StateType outputStateType = outputThing->thingClass().stateTypes().findById(connection.outputStateTypeId()); + + // Check if this is actually an output + if (outputStateType.ioType() != Types::IOTypeDigitalOutput && outputStateType.ioType() != Types::IOTypeAnalogOutput) { + qCWarning(dcThingManager()) << "The given output state is neither a digital nor an analog output."; + return Thing::ThingErrorInvalidParameter; + } + + // Check if io types are compatible + if (inputStateType.ioType() == Types::IOTypeDigitalInput && outputStateType.ioType() != Types::IOTypeDigitalOutput) { + qCWarning(dcThingManager()) << "Cannot connect IOs of different type:" << inputStateType.ioType() << "is not compatible with" << outputStateType.ioType(); + return Thing::ThingErrorInvalidParameter; + } + if (inputStateType.ioType() == Types::IOTypeAnalogInput && outputStateType.ioType() != Types::IOTypeAnalogOutput) { + qCWarning(dcThingManager()) << "Cannot connect IOs of different type:" << inputStateType.ioType() << "is not compatible with" << outputStateType.ioType(); + return Thing::ThingErrorInvalidParameter; + } + + // Check if either input or output is already connected + foreach (const IOConnectionId &id, m_ioConnections.keys()) { + if (m_ioConnections.value(id).inputThingId() == connection.inputThingId() && m_ioConnections.value(id).inputStateTypeId() == connection.inputStateTypeId()) { + qCDebug(dcThingManager()).nospace() << "Thing " << inputThing->name() << " already has an IO connection on " << inputStateType.displayName() << ". Replacing old connection."; + disconnectIO(id); + continue; + } + if (m_ioConnections.value(id).outputThingId() == connection.outputThingId() && m_ioConnections.value(id).outputStateTypeId() == connection.outputStateTypeId()) { + qCDebug(dcThingManager()).nospace() << "Thing " << inputThing->name() << " already has an IO connection on " << inputStateType.displayName() << ". Replacing old connection."; + disconnectIO(id); + } + } + + // Finally add the connection + m_ioConnections.insert(connection.id(), connection); + + storeIOConnections(); + + emit ioConnectionAdded(connection); + return Thing::ThingErrorNoError; +} + +Thing::ThingError ThingManagerImplementation::disconnectIO(const IOConnectionId &ioConnectionId) +{ + if (!m_ioConnections.contains(ioConnectionId)) { + qCWarning(dcThingManager()) << "IO connection" << ioConnectionId << "not found. Cannot disconnect."; + return Thing::ThingErrorItemNotFound; + } + m_ioConnections.remove(ioConnectionId); + + NymeaSettings settings(NymeaSettings::SettingsRoleIOConnections); + settings.beginGroup("IOConnections"); + settings.remove(ioConnectionId.toString()); + settings.endGroup(); + + qCDebug(dcThingManager()) << "IO connection disconnected:" << ioConnectionId; + + emit ioConnectionRemoved(ioConnectionId); + return Thing::ThingErrorNoError; +} + QString ThingManagerImplementation::translate(const PluginId &pluginId, const QString &string, const QLocale &locale) { return m_translator->translate(pluginId, string, locale); @@ -1423,6 +1532,8 @@ void ThingManagerImplementation::loadConfiguredThings() postSetupThing(info->thing()); }); } + + loadIOConnections(); } void ThingManagerImplementation::storeConfiguredThings() @@ -1611,6 +1722,115 @@ void ThingManagerImplementation::slotThingStateValueChanged(const StateTypeId &s Param valueParam(ParamTypeId(stateTypeId.toString()), value); Event event(EventTypeId(stateTypeId.toString()), thing->id(), ParamList() << valueParam, true); emit eventTriggered(event); + + foreach (const IOConnection &ioConnection, m_ioConnections) { + // Check if this state is an input to an IO connection. + if (ioConnection.inputThingId() == thing->id() && ioConnection.inputStateTypeId() == stateTypeId) { + Thing *outputThing = m_configuredThings.value(ioConnection.outputThingId()); + if (!outputThing) { + qCWarning(dcThingManager()) << "IO connection contains invalid output thing!"; + continue; + } + IntegrationPlugin *plugin = m_integrationPlugins.value(outputThing->pluginId()); + if (!plugin) { + qCWarning(dcThingManager()) << "Plugin not found for IO connection's output action."; + continue; + } + StateType inputStateType = thing->thingClass().getStateType(stateTypeId); + + StateType outputStateType = outputThing->thingClass().getStateType(ioConnection.outputStateTypeId()); + if (outputStateType.id().isNull()) { + qCWarning(dcThingManager()) << "Could not find output state type for IO connection."; + continue; + } + QVariant outputValue; + if (outputStateType.ioType() == Types::IOTypeDigitalOutput) { + // Digital IOs are mapped as-is + outputValue = value; + + // We're already in sync! Skipping action. + if (outputThing->stateValue(outputStateType.id()) == outputValue) { + continue; + } + } else { + // Analog IOs are mapped within the according min/max ranges + outputValue = mapValue(value, inputStateType, outputStateType); + + // We're already in sync (fuzzy, good enough)! Skipping action. + if (qFuzzyCompare(outputThing->stateValue(outputStateType.id()).toDouble(), outputValue.toDouble())) { + continue; + } + } + Action outputAction(ActionTypeId(ioConnection.outputStateTypeId()), ioConnection.outputThingId()); + + Param outputParam(ioConnection.outputStateTypeId(), outputValue); + outputAction.setParams(ParamList() << outputParam); + qCDebug(dcThingManager()) << "Executing IO connection action on" << outputThing->name() << outputParam; + ThingActionInfo* info = executeAction(outputAction); + connect(info, &ThingActionInfo::finished, this, [=](){ + if (info->status() != Thing::ThingErrorNoError) { + // An error happened... let's switch the input back to be in sync with the output + qCWarning(dcThingManager()) << "Error syncing IO connection state. Reverting input back to old value."; + if (inputStateType.ioType() == Types::IOTypeDigitalInput) { + thing->setStateValue(inputStateType.id(), outputThing->stateValue(outputStateType.id())); + } else { + thing->setStateValue(inputStateType.id(), mapValue(outputThing->stateValue(outputStateType.id()), outputStateType, inputStateType)); + } + } + }); + } + + // Now check if this is an output state type and - if possible - update the inputs for bidirectional connections + if (ioConnection.outputThingId() == thing->id() && ioConnection.outputStateTypeId() == stateTypeId) { + Thing *inputThing = m_configuredThings.value(ioConnection.inputThingId()); + if (!inputThing) { + qCWarning(dcThingManager()) << "IO connection contains invalid input thing!"; + continue; + } + IntegrationPlugin *plugin = m_integrationPlugins.value(inputThing->pluginId()); + if (!plugin) { + qCWarning(dcThingManager()) << "Plugin not found for IO connection's input action."; + continue; + } + StateType outputStateType = thing->thingClass().getStateType(stateTypeId); + + StateType inputStateType = inputThing->thingClass().getStateType(ioConnection.inputStateTypeId()); + if (inputStateType.id().isNull()) { + qCWarning(dcThingManager()) << "Could not find input state type for IO connection."; + continue; + } + + if (!inputStateType.writable()) { + qCDebug(dcThingManager()) << "Input state is not writable. This connection is unidirectional."; + continue; + } + + QVariant inputValue; + if (inputStateType.ioType() == Types::IOTypeDigitalInput) { + // Digital IOs are mapped as-is + inputValue = value; + + // Prevent looping + if (inputThing->stateValue(inputStateType.id()) == inputValue) { + continue; + } + } else { + // Analog IOs are mapped within the according min/max ranges + inputValue = mapValue(value, outputStateType, inputStateType); + + // Prevent looping even if the above calculation has rounding errors... Just skip this action if we're close enough already + if (qFuzzyCompare(inputThing->stateValue(inputStateType.id()).toDouble(), inputValue.toDouble())) { + continue; + } + } + Action inputAction(ActionTypeId(ioConnection.inputStateTypeId()), ioConnection.inputThingId()); + + Param inputParam(ioConnection.inputStateTypeId(), inputValue); + inputAction.setParams(ParamList() << inputParam); + qCDebug(dcThingManager()) << "Executing reverse IO connection action on" << inputThing->name() << inputParam; + executeAction(inputAction); + } + } } void ThingManagerImplementation::slotThingSettingChanged(const ParamTypeId ¶mTypeId, const QVariant &value) @@ -1756,6 +1976,53 @@ void ThingManagerImplementation::loadThingStates(Thing *thing) settings.endGroup(); } +void ThingManagerImplementation::storeIOConnections() +{ + NymeaSettings connectionSettings(NymeaSettings::SettingsRoleIOConnections); + connectionSettings.beginGroup("IOConnections"); + foreach (const IOConnection &ioConnection, m_ioConnections) { + connectionSettings.beginGroup(ioConnection.id().toString()); + + connectionSettings.setValue("inputThingId", ioConnection.inputThingId().toString()); + connectionSettings.setValue("inputStateTypeId", ioConnection.inputStateTypeId().toString()); + connectionSettings.setValue("outputThingId", ioConnection.outputThingId().toString()); + connectionSettings.setValue("outputStateTypeId", ioConnection.outputStateTypeId().toString()); + + connectionSettings.endGroup(); + } + connectionSettings.endGroup(); +} + +void ThingManagerImplementation::loadIOConnections() +{ + NymeaSettings connectionSettings(NymeaSettings::SettingsRoleIOConnections); + connectionSettings.beginGroup("IOConnections"); + foreach (const QString &idString, connectionSettings.childGroups()) { + connectionSettings.beginGroup(idString); + IOConnectionId id(idString); + ThingId inputThingId = connectionSettings.value("inputThingId").toUuid(); + StateTypeId inputStateTypeId = connectionSettings.value("inputStateTypeId").toUuid(); + ThingId outputThingId = connectionSettings.value("outputThingId").toUuid(); + StateTypeId outputStateTypeId = connectionSettings.value("outputStateTypeId").toUuid(); + IOConnection ioConnection(id, inputThingId, inputStateTypeId, outputThingId, outputStateTypeId); + m_ioConnections.insert(id, ioConnection); + connectionSettings.endGroup(); + } + connectionSettings.endGroup(); +} + +QVariant ThingManagerImplementation::mapValue(const QVariant &value, const StateType &fromStateType, const StateType &toStateType) const +{ + double fromMin = fromStateType.minValue().toDouble(); + double fromMax = fromStateType.maxValue().toDouble(); + double toMin = toStateType.minValue().toDouble(); + double toMax = toStateType.maxValue().toDouble(); + double fromValue = value.toDouble(); + double fromPercent = (fromValue - fromMin) / (fromMax - fromMin); + double toValue = toMin + (toMax - toMin) * fromPercent; + return toValue; +} + void ThingManagerImplementation::storeThingStates(Thing *thing) { NymeaSettings settings(NymeaSettings::SettingsRoleThingStates); diff --git a/libnymea-core/integrations/thingmanagerimplementation.h b/libnymea-core/integrations/thingmanagerimplementation.h index f9b0f093..8cb343ef 100644 --- a/libnymea-core/integrations/thingmanagerimplementation.h +++ b/libnymea-core/integrations/thingmanagerimplementation.h @@ -36,6 +36,7 @@ #include "integrations/thing.h" #include "integrations/thingdescriptor.h" #include "integrations/pluginmetadata.h" +#include "integrations/ioconnection.h" #include "types/thingclass.h" #include "types/interface.h" @@ -113,6 +114,10 @@ public: BrowserActionInfo *executeBrowserItem(const BrowserAction &browserAction) override; BrowserItemActionInfo *executeBrowserItemAction(const BrowserItemAction &browserItemAction) override; + IOConnections ioConnections(const ThingId &thingId = ThingId()) const override; + Thing::ThingError connectIO(const IOConnection &connection) override; + Thing::ThingError disconnectIO(const IOConnectionId &ioConnectionId) override; + QString translate(const PluginId &pluginId, const QString &string, const QLocale &locale) override; ParamType translateParamType(const PluginId &pluginId, const ParamType ¶mType, const QLocale &locale) override; ThingClass translateThingClass(const ThingClass &thingClass, const QLocale &locale) override; @@ -149,6 +154,10 @@ private: void postSetupThing(Thing *thing); void storeThingStates(Thing *thing); void loadThingStates(Thing *thing); + void storeIOConnections(); + void loadIOConnections(); + + QVariant mapValue(const QVariant &value, const StateType &fromStateType, const StateType &toStateType) const; private: HardwareManager *m_hardwareManager; @@ -173,6 +182,8 @@ private: QString thingName; }; QHash m_pendingPairings; + + QHash m_ioConnections; }; #endif // THINGMANAGERIMPLEMENTATION_H diff --git a/libnymea-core/jsonrpc/devicehandler.cpp b/libnymea-core/jsonrpc/devicehandler.cpp index f13b9f70..120992b1 100644 --- a/libnymea-core/jsonrpc/devicehandler.cpp +++ b/libnymea-core/jsonrpc/devicehandler.cpp @@ -60,6 +60,7 @@ DeviceHandler::DeviceHandler(QObject *parent) : registerEnum(); registerEnum(); registerEnum(); + registerEnum(); registerEnum(); registerEnum(); registerEnum(); diff --git a/libnymea-core/jsonrpc/integrationshandler.cpp b/libnymea-core/jsonrpc/integrationshandler.cpp index 639af631..0aa6b400 100644 --- a/libnymea-core/jsonrpc/integrationshandler.cpp +++ b/libnymea-core/jsonrpc/integrationshandler.cpp @@ -59,6 +59,7 @@ IntegrationsHandler::IntegrationsHandler(ThingManager *thingManager, QObject *pa registerEnum(); registerEnum(); registerEnum(); + registerEnum(); registerEnum(); registerEnum(); registerEnum(); @@ -77,6 +78,7 @@ IntegrationsHandler::IntegrationsHandler(ThingManager *thingManager, QObject *pa registerObject(); registerObject(); registerUncreatableObject(); + registerObject(); // Registering browseritem manually for now. Not sure how to deal with the // polymorphism in it (e.g MediaBrowserItem) @@ -346,6 +348,28 @@ IntegrationsHandler::IntegrationsHandler(ThingManager *thingManager, QObject *pa returns.insert("o:displayMessage", enumValueName(String)); registerMethod("ExecuteBrowserItemAction", description, params, returns); + params.clear(); returns.clear(); + description = "Fetch IO connections. Optionally filtered by thingId and stateTypeId."; + params.insert("o:thingId", enumValueName(Uuid)); + returns.insert("ioConnections", objectRef()); + registerMethod("GetIOConnections", description, params, returns); + + params.clear(); returns.clear(); + description = "Connect two generic IO states."; + params.insert("inputThingId", enumValueName(Uuid)); + params.insert("inputStateTypeId", enumValueName(Uuid)); + params.insert("outputThingId", enumValueName(Uuid)); + params.insert("outputStateTypeId", enumValueName(Uuid)); + returns.insert("thingError", enumRef()); + registerMethod("ConnectIO", description, params, returns); + + params.clear(); returns.clear(); + description = "Disconnect an existing IO connection."; + params.insert("ioConnectionId", enumValueName(Uuid)); + returns.insert("thingError", enumRef()); + registerMethod("DisconnectIO", description, params, returns); + + // Notifications params.clear(); returns.clear(); description = "Emitted whenever a state of a thing changes."; @@ -392,6 +416,26 @@ IntegrationsHandler::IntegrationsHandler(ThingManager *thingManager, QObject *pa emit EventTriggered(params); }); + params.clear(); returns.clear(); + description = "Emitted whenever a IO connection is added."; + params.insert("ioConnection", objectRef()); + registerNotification("IOConnectionAdded", description, params); + connect(m_thingManager, &ThingManager::ioConnectionAdded, this, [this](const IOConnection &connection) { + QVariantMap params; + params.insert("ioConnection", pack(connection)); + emit IOConnectionAdded(params); + }); + + params.clear(); returns.clear(); + description = "Emitted whenever a IO connection is removed."; + params.insert("ioConnectionId", enumValueName(Uuid)); + registerNotification("IOConnectionRemoved", description, params); + connect(m_thingManager, &ThingManager::ioConnectionRemoved, this, [this](const IOConnectionId &ioConnectionId) { + QVariantMap params; + params.insert("ioConnectionId", ioConnectionId); + emit IOConnectionRemoved(params); + }); + connect(NymeaCore::instance(), &NymeaCore::pluginConfigChanged, this, &IntegrationsHandler::pluginConfigChanged); connect(NymeaCore::instance(), &NymeaCore::thingStateChanged, this, &IntegrationsHandler::thingStateChanged); connect(NymeaCore::instance(), &NymeaCore::thingRemoved, this, &IntegrationsHandler::thingRemovedNotification); @@ -930,6 +974,33 @@ JsonReply *IntegrationsHandler::ExecuteBrowserItemAction(const QVariantMap ¶ return jsonReply; } +JsonReply *IntegrationsHandler::GetIOConnections(const QVariantMap ¶ms) +{ + ThingId thingId = params.value("thingId").toUuid(); + IOConnections ioConnections = m_thingManager->ioConnections(thingId); + QVariantMap returns; + QVariant bla = pack(ioConnections); + returns.insert("ioConnections", pack(ioConnections)); + return createReply(returns); +} + +JsonReply *IntegrationsHandler::ConnectIO(const QVariantMap ¶ms) +{ + ThingId inputThingId = params.value("inputThingId").toUuid(); + StateTypeId inputStateTypeId = params.value("inputStateTypeId").toUuid(); + ThingId outputThingId = params.value("outputThingId").toUuid(); + StateTypeId outputStateTypeId = params.value("outputStateTypeId").toUuid(); + Thing::ThingError error = m_thingManager->connectIO(inputThingId, inputStateTypeId, outputThingId, outputStateTypeId); + return createReply(statusToReply(error)); +} + +JsonReply *IntegrationsHandler::DisconnectIO(const QVariantMap ¶ms) +{ + IOConnectionId ioConnectionId = params.value("ioConnectionId").toUuid(); + Thing::ThingError error = m_thingManager->disconnectIO(ioConnectionId); + return createReply(statusToReply(error)); +} + QVariantMap IntegrationsHandler::packBrowserItem(const BrowserItem &item) { QVariantMap ret; diff --git a/libnymea-core/jsonrpc/integrationshandler.h b/libnymea-core/jsonrpc/integrationshandler.h index 47f88fab..cad64521 100644 --- a/libnymea-core/jsonrpc/integrationshandler.h +++ b/libnymea-core/jsonrpc/integrationshandler.h @@ -72,6 +72,10 @@ public: Q_INVOKABLE JsonReply *ExecuteBrowserItem(const QVariantMap ¶ms, const JsonContext &context); Q_INVOKABLE JsonReply *ExecuteBrowserItemAction(const QVariantMap ¶ms, const JsonContext &context); + Q_INVOKABLE JsonReply *GetIOConnections(const QVariantMap ¶ms); + Q_INVOKABLE JsonReply *ConnectIO(const QVariantMap ¶ms); + Q_INVOKABLE JsonReply *DisconnectIO(const QVariantMap ¶ms); + static QVariantMap packBrowserItem(const BrowserItem &item); signals: @@ -82,6 +86,8 @@ signals: void ThingChanged(const QVariantMap ¶ms); void ThingSettingChanged(const QVariantMap ¶ms); void EventTriggered(const QVariantMap ¶ms); + void IOConnectionAdded(const QVariantMap ¶ms); + void IOConnectionRemoved(const QVariantMap ¶ms); private slots: void pluginConfigChanged(const PluginId &id, const ParamList &config); diff --git a/libnymea-core/nymeacore.cpp b/libnymea-core/nymeacore.cpp index ae246218..ee4b8144 100644 --- a/libnymea-core/nymeacore.cpp +++ b/libnymea-core/nymeacore.cpp @@ -424,7 +424,7 @@ void NymeaCore::executeRuleActions(const QList ruleActions) } else if (ruleActionParam.isStateBased()) { Thing *stateThing = m_thingManager->findConfiguredThing(ruleActionParam.stateThingId()); if (!stateThing) { - qCWarning(dcRuleEngine()) << "Cannot find thing" << ruleActionParam.stateThingId() << "required by rule action" << ruleAction.id(); + qCWarning(dcRuleEngine()) << "Cannot find thing" << ruleActionParam.stateThingId() << "required by rule action"; ok = false; break; } @@ -438,7 +438,7 @@ void NymeaCore::executeRuleActions(const QList ruleActions) } } if (!ok) { - qCWarning(dcRuleEngine()) << "Not executing rule action" << ruleAction.id(); + qCWarning(dcRuleEngine()) << "Not executing rule action"; continue; } Action action(actionTypeId, thing->id()); @@ -476,7 +476,7 @@ void NymeaCore::executeRuleActions(const QList ruleActions) } else if (ruleActionParam.isStateBased()) { Thing *stateThing = m_thingManager->findConfiguredThing(ruleActionParam.stateThingId()); if (!stateThing) { - qCWarning(dcRuleEngine()) << "Cannot find thing" << ruleActionParam.stateThingId() << "required by rule action" << ruleAction.id(); + qCWarning(dcRuleEngine()) << "Cannot find thing" << ruleActionParam.stateThingId() << "required by rule action"; ok = false; break; } @@ -490,7 +490,7 @@ void NymeaCore::executeRuleActions(const QList ruleActions) } } if (!ok) { - qCWarning(dcRuleEngine()) << "Not executing rule action" << ruleAction.id(); + qCWarning(dcRuleEngine()) << "Not executing rule action"; continue; } diff --git a/libnymea-core/ruleengine/ruleaction.cpp b/libnymea-core/ruleengine/ruleaction.cpp index 385fa1c0..29b3fc77 100644 --- a/libnymea-core/ruleengine/ruleaction.cpp +++ b/libnymea-core/ruleengine/ruleaction.cpp @@ -31,7 +31,6 @@ #include "ruleaction.h" RuleAction::RuleAction(const ActionTypeId &actionTypeId, const ThingId &thingId, const RuleActionParams ¶ms): - m_id(ActionId::createActionId()), m_thingId(thingId), m_actionTypeId(actionTypeId), m_ruleActionParams(params) @@ -55,7 +54,6 @@ RuleAction::RuleAction(const ThingId &thingId, const QString &browserItemId): } RuleAction::RuleAction(const RuleAction &other) : - m_id(other.id()), m_thingId(other.thingId()), m_actionTypeId(other.actionTypeId()), m_browserItemId(other.browserItemId()), @@ -66,11 +64,6 @@ RuleAction::RuleAction(const RuleAction &other) : } -ActionId RuleAction::id() const -{ - return m_id; -} - bool RuleAction::isValid() const { return (!m_actionTypeId.isNull() && !m_thingId.isNull()) @@ -211,7 +204,6 @@ RuleActionParam RuleAction::ruleActionParam(const QString &ruleActionParamName) void RuleAction::operator=(const RuleAction &other) { - m_id = other.id(); m_actionTypeId = other.actionTypeId(); m_ruleActionParams = other.ruleActionParams(); } diff --git a/libnymea-core/ruleengine/ruleaction.h b/libnymea-core/ruleengine/ruleaction.h index 29e1a96a..017a05ab 100644 --- a/libnymea-core/ruleengine/ruleaction.h +++ b/libnymea-core/ruleengine/ruleaction.h @@ -58,7 +58,6 @@ public: explicit RuleAction(const ThingId &thingId, const QString &browserItemId); RuleAction(const RuleAction &other); - ActionId id() const; bool isValid() const; Type type() const; @@ -92,7 +91,6 @@ public: void operator=(const RuleAction &other); private: - ActionId m_id; ThingId m_thingId; ActionTypeId m_actionTypeId; QString m_browserItemId; diff --git a/libnymea-core/ruleengine/ruleengine.cpp b/libnymea-core/ruleengine/ruleengine.cpp index 72f15482..9a75797d 100644 --- a/libnymea-core/ruleengine/ruleengine.cpp +++ b/libnymea-core/ruleengine/ruleengine.cpp @@ -201,12 +201,12 @@ QList RuleEngine::evaluateEvent(const Event &event) } else { // Event based rule if (containsEvent(rule, event, thing->thingClassId())) { - qCDebug(dcRuleEngineDebug()).nospace().noquote() << "Rule " << rule.name() << " (" << rule.id().toString() << ") contains event " << event.eventId(); + qCDebug(dcRuleEngineDebug()).nospace().noquote() << "Rule " << rule.name() << " (" << rule.id().toString() << ") contains event"; if (rule.statesActive() && rule.timeActive()) { - qCDebug(dcRuleEngine).nospace().noquote() << "Rule " << rule.name() << " (" + rule.id().toString() << ") contains event" << event.eventId() << "and all states match."; + qCDebug(dcRuleEngine).nospace().noquote() << "Rule " << rule.name() << " (" + rule.id().toString() << ") contains event and all states match."; rules.append(rule); } else { - qCDebug(dcRuleEngine).nospace().noquote() << "Rule " << rule.name() << " (" + rule.id().toString() << ") contains event" << event.eventId() << "but state are not matching."; + qCDebug(dcRuleEngine).nospace().noquote() << "Rule " << rule.name() << " (" + rule.id().toString() << ") contains event but state are not matching."; rules.append(rule); } } diff --git a/libnymea/integrations/ioconnection.cpp b/libnymea/integrations/ioconnection.cpp new file mode 100644 index 00000000..e408df54 --- /dev/null +++ b/libnymea/integrations/ioconnection.cpp @@ -0,0 +1,51 @@ +#include "ioconnection.h" + +IOConnection::IOConnection() +{ + +} + +IOConnection::IOConnection(const IOConnectionId &id, const ThingId &inputThing, const StateTypeId &inputState, const ThingId &outputThing, const StateTypeId &outputState): + m_id(id), + m_inputThingId(inputThing), + m_inputStateTypeId(inputState), + m_outputThingId(outputThing), + m_outputStateTypeId(outputState) +{ + +} + +IOConnectionId IOConnection::id() const +{ + return m_id; +} + +ThingId IOConnection::inputThingId() const +{ + return m_inputThingId; +} + +StateTypeId IOConnection::inputStateTypeId() const +{ + return m_inputStateTypeId; +} + +ThingId IOConnection::outputThingId() const +{ + return m_outputThingId; +} + +StateTypeId IOConnection::outputStateTypeId() const +{ + return m_outputStateTypeId; +} + +QVariant IOConnections::get(int index) const +{ + return QVariant::fromValue(at(index)); +} + +void IOConnections::put(const QVariant &variant) +{ + append(variant.value()); +} diff --git a/libnymea/integrations/ioconnection.h b/libnymea/integrations/ioconnection.h new file mode 100644 index 00000000..144abc89 --- /dev/null +++ b/libnymea/integrations/ioconnection.h @@ -0,0 +1,55 @@ +#ifndef IOCONNECTION_H +#define IOCONNECTION_H + +#include +#include +#include + +#include "typeutils.h" + +class IOConnection +{ + Q_GADGET + Q_PROPERTY(QUuid id READ id) + Q_PROPERTY(QUuid inputThingId READ inputThingId) + Q_PROPERTY(QUuid inputStateTypeId READ inputStateTypeId) + Q_PROPERTY(QUuid outputThingId READ outputThingId) + Q_PROPERTY(QUuid outputStateTypeId READ outputStateTypeId) + +public: + IOConnection(); + IOConnection(const IOConnectionId &id, const ThingId &inputThingId, const StateTypeId &inputStateTypeId, const ThingId &outputThingId, const StateTypeId &outputStateTypeId); + + IOConnectionId id() const; + + ThingId inputThingId() const; + StateTypeId inputStateTypeId() const; + + ThingId outputThingId() const; + StateTypeId outputStateTypeId() const; + +private: + IOConnectionId m_id; + ThingId m_inputThingId; + StateTypeId m_inputStateTypeId; + ThingId m_outputThingId; + StateTypeId m_outputStateTypeId; +}; + +class IOConnections: public QList +{ + Q_GADGET + Q_PROPERTY(int count READ count) +public: + IOConnections() {} + inline IOConnections(std::initializer_list args): QList(args) {} + IOConnections(const QList &other): QList(other) {} + Q_INVOKABLE QVariant get(int index) const; + Q_INVOKABLE void put(const QVariant &variant); +}; + +Q_DECLARE_METATYPE(IOConnection) +Q_DECLARE_METATYPE(IOConnections) + + +#endif // IOCONNECTION_H diff --git a/libnymea/integrations/pluginmetadata.cpp b/libnymea/integrations/pluginmetadata.cpp index 65af11dd..35570c96 100644 --- a/libnymea/integrations/pluginmetadata.cpp +++ b/libnymea/integrations/pluginmetadata.cpp @@ -381,6 +381,68 @@ void PluginMetadata::parse(const QJsonObject &jsonObject) if (st.contains("cached")) { stateType.setCached(st.value("cached").toBool()); } + if (st.contains("writable")) { + stateType.setWritable(st.value("writable").toBool()); + } + + if (st.contains("ioType")) { + QString ioTypeString = st.value("ioType").toString(); + Types::IOType ioType = Types::IOTypeNone; + if (ioTypeString == "digitalInput") { + if (stateType.type() != QVariant::Bool) { + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" state type \"" + stateTypeName + "\" is marked as digital input but type is not \"bool\""); + hasError = true; + break; + } + ioType = Types::IOTypeDigitalInput; + } else if (ioTypeString == "digitalOutput") { + if (stateType.type() != QVariant::Bool) { + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" state type \"" + stateTypeName + "\" is marked as digital output but type is not \"bool\""); + hasError = true; + break; + } + if (!stateType.writable()) { + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" state type \"" + stateTypeName + "\" is marked as digital output but is not writable"); + hasError = true; + break; + } + ioType = Types::IOTypeDigitalOutput; + } else if (ioTypeString == "analogInput") { + if (stateType.type() != QVariant::Double) { + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" state type \"" + stateTypeName + "\" is marked as analog input but type is not \"double\""); + hasError = true; + break; + } + if (stateType.minValue().isNull() || stateType.maxValue().isNull()) { + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" state type \"" + stateTypeName + "\" is marked as analog input but it does not define \"minValue\" and \"maxValue\""); + hasError = true; + break; + } + ioType = Types::IOTypeAnalogInput; + } else if (ioTypeString == "analogOutput") { + if (stateType.type() != QVariant::Double) { + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" state type \"" + stateTypeName + "\" is marked as analog output but type is not \"double\""); + hasError = true; + break; + } + if (!stateType.writable()) { + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" state type \"" + stateTypeName + "\" is marked as analog output but is not writable"); + hasError = true; + break; + } + if (stateType.minValue().isNull() || stateType.maxValue().isNull()) { + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" state type \"" + stateTypeName + "\" is marked as analog output but it does not define \"minValue\" and \"maxValue\""); + hasError = true; + break; + } + ioType = Types::IOTypeAnalogOutput; + } else { + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" state type \"" + stateTypeName + "\" has invalid ioType value \"" + ioType + "\" which is not any of \"digitalInput\", \"digitalOutput\", \"analogInput\" or \"analogOutput\""); + hasError = true; + break; + } + stateType.setIOType(ioType); + } stateTypes.append(stateType); // Events for state changed (Not checking for duplicate UUID, this is expected to be the same as the state!) diff --git a/libnymea/integrations/thingmanager.cpp b/libnymea/integrations/thingmanager.cpp index d3d39c72..21751eb5 100644 --- a/libnymea/integrations/thingmanager.cpp +++ b/libnymea/integrations/thingmanager.cpp @@ -50,3 +50,9 @@ ThingManager::ThingManager(QObject *parent) : QObject(parent) qRegisterMetaType(); qRegisterMetaType(); } + +Thing::ThingError ThingManager::connectIO(const ThingId &inputThing, const StateTypeId &inputState, const ThingId &outputThing, const StateTypeId &outputState) +{ + IOConnection connection(IOConnectionId::createIOConnectionId(), inputThing, inputState, outputThing, outputState); + return connectIO(connection); +} diff --git a/libnymea/integrations/thingmanager.h b/libnymea/integrations/thingmanager.h index ed67a5ef..7c1dc417 100644 --- a/libnymea/integrations/thingmanager.h +++ b/libnymea/integrations/thingmanager.h @@ -35,6 +35,7 @@ #include "thing.h" #include "integrationplugin.h" +#include "ioconnection.h" #include "types/interface.h" #include "types/vendor.h" #include "types/browseritem.h" @@ -89,10 +90,18 @@ public: virtual BrowserActionInfo* executeBrowserItem(const BrowserAction &browserAction) = 0; virtual BrowserItemActionInfo* executeBrowserItemAction(const BrowserItemAction &browserItemAction) = 0; + virtual IOConnections ioConnections(const ThingId &thingId = ThingId()) const = 0; + Thing::ThingError connectIO(const ThingId &inputThing, const StateTypeId &inputState, const ThingId &outputThing, const StateTypeId &outputState); + virtual Thing::ThingError disconnectIO(const IOConnectionId &ioConnectionId) = 0; + virtual QString translate(const PluginId &pluginId, const QString &string, const QLocale &locale) = 0; virtual ParamType translateParamType(const PluginId &pluginId, const ParamType ¶mType, const QLocale &locale) = 0; virtual ThingClass translateThingClass(const ThingClass &thingClass, const QLocale &locale) = 0; virtual Vendor translateVendor(const Vendor &vendor, const QLocale &locale) = 0; + +protected: + virtual Thing::ThingError connectIO(const IOConnection &connection) = 0; + signals: void pluginConfigChanged(const PluginId &id, const ParamList &config); void eventTriggered(const Event &event); @@ -102,6 +111,8 @@ signals: void thingAdded(Thing *thing); void thingChanged(Thing *device); void thingSettingChanged(const ThingId &thingId, const ParamTypeId &settingParamTypeId, const QVariant &value); + void ioConnectionAdded(const IOConnection &ioConnection); + void ioConnectionRemoved(const IOConnectionId &ioConnectionId); }; #endif // THINGMANAGER_H diff --git a/libnymea/interfaces/temperaturesensor.json b/libnymea/interfaces/temperaturesensor.json index 5b480c3a..04fafa42 100644 --- a/libnymea/interfaces/temperaturesensor.json +++ b/libnymea/interfaces/temperaturesensor.json @@ -3,7 +3,8 @@ "states": [ { "name": "temperature", - "type": "double" + "type": "double", + "unit": "DegreeCelsius" } ] } diff --git a/libnymea/libnymea.pro b/libnymea/libnymea.pro index 6654c457..b8662168 100644 --- a/libnymea/libnymea.pro +++ b/libnymea/libnymea.pro @@ -14,6 +14,7 @@ HEADERS += \ integrations/browseritemactioninfo.h \ integrations/browseritemresult.h \ integrations/integrationplugin.h \ + integrations/ioconnection.h \ integrations/pluginmetadata.h \ integrations/browseresult.h \ integrations/thing.h \ @@ -97,6 +98,7 @@ SOURCES += \ integrations/browseritemactioninfo.cpp \ integrations/browseritemresult.cpp \ integrations/integrationplugin.cpp \ + integrations/ioconnection.cpp \ integrations/pluginmetadata.cpp \ integrations/browseresult.cpp \ integrations/thing.cpp \ diff --git a/libnymea/nymeasettings.cpp b/libnymea/nymeasettings.cpp index 8e607aa4..5b404857 100644 --- a/libnymea/nymeasettings.cpp +++ b/libnymea/nymeasettings.cpp @@ -115,6 +115,9 @@ NymeaSettings::NymeaSettings(const SettingsRole &role, QObject *parent): case SettingsRoleMqttPolicies: fileName = "mqttpolicies.conf"; break; + case SettingsRoleIOConnections: + fileName = "ioconnections.conf"; + break; } m_settings = new QSettings(basePath + settingsPrefix + fileName, QSettings::IniFormat, this); } diff --git a/libnymea/nymeasettings.h b/libnymea/nymeasettings.h index 61984e53..8c810e1d 100644 --- a/libnymea/nymeasettings.h +++ b/libnymea/nymeasettings.h @@ -51,6 +51,7 @@ public: SettingsRoleThingStates, SettingsRoleTags, SettingsRoleMqttPolicies, + SettingsRoleIOConnections, }; explicit NymeaSettings(const SettingsRole &role = SettingsRoleNone, QObject *parent = nullptr); diff --git a/libnymea/types/action.cpp b/libnymea/types/action.cpp index 5db75322..7df1246f 100644 --- a/libnymea/types/action.cpp +++ b/libnymea/types/action.cpp @@ -47,7 +47,6 @@ /*! Construct an Action with the given \a deviceId and \a actionTypeId. */ Action::Action(const ActionTypeId &actionTypeId, const ThingId &thingId) : - m_id(ActionId::createActionId()), m_actionTypeId(actionTypeId), m_thingId(thingId) { @@ -55,19 +54,12 @@ Action::Action(const ActionTypeId &actionTypeId, const ThingId &thingId) : /*! Construct a copy of an \a other Action. */ Action::Action(const Action &other): - m_id(other.id()), m_actionTypeId(other.actionTypeId()), m_thingId(other.thingId()), m_params(other.params()) { } -/*! Returns the actionId for this Action. */ -ActionId Action::id() const -{ - return m_id; -} - /*! An Action is valid if actionTypeId and deviceId are valid uuids. Returns true if valid, false if not. */ bool Action::isValid() const { @@ -123,7 +115,6 @@ Param Action::param(const ParamTypeId ¶mTypeId) const /*! Copy the data to an \l{Action} from an \a other action. */ void Action::operator =(const Action &other) { - m_id = other.id(); m_actionTypeId = other.actionTypeId(); m_params = other.params(); } diff --git a/libnymea/types/action.h b/libnymea/types/action.h index 8a1c249a..358d3c87 100644 --- a/libnymea/types/action.h +++ b/libnymea/types/action.h @@ -49,8 +49,6 @@ public: explicit Action(const ActionTypeId &actionTypeId = ActionTypeId(), const ThingId &thingId = ThingId()); Action(const Action &other); - ActionId id() const; - bool isValid() const; ActionTypeId actionTypeId() const; @@ -64,7 +62,6 @@ public: void operator=(const Action &other); private: - ActionId m_id; ActionTypeId m_actionTypeId; ThingId m_thingId; ParamList m_params; diff --git a/libnymea/types/browseraction.cpp b/libnymea/types/browseraction.cpp index 1c0075b0..21726544 100644 --- a/libnymea/types/browseraction.cpp +++ b/libnymea/types/browseraction.cpp @@ -31,7 +31,6 @@ #include "browseraction.h" BrowserAction::BrowserAction(const ThingId &thingId, const QString &itemId): - m_id(ActionId::createActionId()), m_thingId(thingId), m_itemId(itemId) { @@ -39,21 +38,15 @@ BrowserAction::BrowserAction(const ThingId &thingId, const QString &itemId): } BrowserAction::BrowserAction(const BrowserAction &other): - m_id(other.id()), m_thingId(other.thingId()), m_itemId(other.itemId()) { } -ActionId BrowserAction::id() const -{ - return m_id; -} - bool BrowserAction::isValid() const { - return !m_id.isNull() && !m_thingId.isNull() && !m_itemId.isNull(); + return !m_thingId.isNull() && !m_itemId.isNull(); } ThingId BrowserAction::thingId() const @@ -68,7 +61,6 @@ QString BrowserAction::itemId() const void BrowserAction::operator=(const BrowserAction &other) { - m_id = other.id(); m_thingId = other.thingId(); m_itemId = other.itemId(); } diff --git a/libnymea/types/browseraction.h b/libnymea/types/browseraction.h index 7dd5e0b6..2caf991d 100644 --- a/libnymea/types/browseraction.h +++ b/libnymea/types/browseraction.h @@ -39,8 +39,6 @@ public: explicit BrowserAction(const ThingId &thingId = ThingId(), const QString &itemId = QString()); BrowserAction(const BrowserAction &other); - ActionId id() const; - bool isValid() const; ThingId thingId() const; @@ -48,7 +46,6 @@ public: void operator=(const BrowserAction &other); private: - ActionId m_id; ThingId m_thingId; QString m_itemId; }; diff --git a/libnymea/types/browseritemaction.cpp b/libnymea/types/browseritemaction.cpp index ea625527..40d419e4 100644 --- a/libnymea/types/browseritemaction.cpp +++ b/libnymea/types/browseritemaction.cpp @@ -32,7 +32,6 @@ BrowserItemAction::BrowserItemAction(const ThingId &thingId, const QString &itemId, const ActionTypeId &actionTypeId, const ParamList ¶ms): - m_id(ActionId::createActionId()), m_thingId(thingId), m_itemId(itemId), m_actionTypeId(actionTypeId), @@ -42,7 +41,6 @@ BrowserItemAction::BrowserItemAction(const ThingId &thingId, const QString &item } BrowserItemAction::BrowserItemAction(const BrowserItemAction &other): - m_id(other.id()), m_thingId(other.thingId()), m_itemId(other.itemId()), m_actionTypeId(other.actionTypeId()), @@ -51,14 +49,9 @@ BrowserItemAction::BrowserItemAction(const BrowserItemAction &other): } -ActionId BrowserItemAction::id() const -{ - return m_id; -} - bool BrowserItemAction::isValid() const { - return !m_id.isNull() && !m_thingId.isNull() && !m_itemId.isNull(); + return !m_thingId.isNull() && !m_itemId.isNull(); } ThingId BrowserItemAction::thingId() const @@ -83,7 +76,6 @@ ParamList BrowserItemAction::params() const void BrowserItemAction::operator=(const BrowserItemAction &other) { - m_id = other.id(); m_thingId = other.thingId(); m_itemId = other.itemId(); m_actionTypeId = other.actionTypeId(); diff --git a/libnymea/types/browseritemaction.h b/libnymea/types/browseritemaction.h index 64ba5aae..534c9212 100644 --- a/libnymea/types/browseritemaction.h +++ b/libnymea/types/browseritemaction.h @@ -40,8 +40,6 @@ public: explicit BrowserItemAction(const ThingId &thingId = ThingId(), const QString &itemId = QString(), const ActionTypeId &actionTypeId = ActionTypeId(), const ParamList ¶ms = ParamList()); BrowserItemAction(const BrowserItemAction &other); - ActionId id() const; - bool isValid() const; ThingId thingId() const; @@ -54,7 +52,6 @@ public: void operator=(const BrowserItemAction &other); private: - ActionId m_id; ThingId m_thingId; QString m_itemId; ActionTypeId m_actionTypeId; diff --git a/libnymea/types/event.cpp b/libnymea/types/event.cpp index 71089d8d..c38655d5 100644 --- a/libnymea/types/event.cpp +++ b/libnymea/types/event.cpp @@ -46,8 +46,7 @@ #include "event.h" /*! Constructs an Event. */ -Event::Event(): - m_id(EventId::createEventId()) +Event::Event() { } @@ -56,7 +55,6 @@ Event::Event(): * specifies if the \l{Event} will be autogeneratet or not. The parameters must * match the description in the reflecting \l{Event}. */ Event::Event(const EventTypeId &eventTypeId, const ThingId &thingId, const ParamList ¶ms, bool isStateChangeEvent): - m_id(EventId::createEventId()), m_eventTypeId(eventTypeId), m_thingId(thingId), m_params(params), @@ -64,13 +62,6 @@ Event::Event(const EventTypeId &eventTypeId, const ThingId &thingId, const Param { } -/*! Returns the Id of this Event. Each newly created Event will have a new UUID generated. The id will be copied - * in the copy constructor. */ -EventId Event::eventId() const -{ - return m_id; -} - /*! Returns the id of the \l{EventType} which describes this Event. */ EventTypeId Event::eventTypeId() const { diff --git a/libnymea/types/event.h b/libnymea/types/event.h index 463a2a8e..cb323633 100644 --- a/libnymea/types/event.h +++ b/libnymea/types/event.h @@ -50,8 +50,6 @@ public: Event(); Event(const EventTypeId &eventTypeId, const ThingId &thingId, const ParamList ¶ms = ParamList(), bool isStateChangeEvent = false); - EventId eventId() const; - EventTypeId eventTypeId() const; void setEventTypeId(const EventTypeId &eventTypeId); @@ -67,7 +65,6 @@ public: bool isStateChangeEvent() const; private: - EventId m_id; EventTypeId m_eventTypeId; ThingId m_thingId; ParamList m_params; diff --git a/libnymea/types/state.cpp b/libnymea/types/state.cpp index f06f5be1..8366511f 100644 --- a/libnymea/types/state.cpp +++ b/libnymea/types/state.cpp @@ -51,18 +51,11 @@ State::State() /*! Constructs a State reflecting the \l{StateType} given by \a stateTypeId * and associated with the \l{Device} given by \a deviceId */ State::State(const StateTypeId &stateTypeId, const ThingId &deviceId): - m_id(StateId::createStateId()), m_stateTypeId(stateTypeId), m_thingId(deviceId) { } -/*! Returns the id of this State. */ -StateId State::id() const -{ - return m_id; -} - /*! Returns the id of the StateType describing this State. */ StateTypeId State::stateTypeId() const { diff --git a/libnymea/types/state.h b/libnymea/types/state.h index 55f7a3ac..165c2fb9 100644 --- a/libnymea/types/state.h +++ b/libnymea/types/state.h @@ -47,8 +47,6 @@ public: State(); State(const StateTypeId &stateTypeId, const ThingId &deviceId); - StateId id() const; - StateTypeId stateTypeId() const; ThingId thingId() const; @@ -56,7 +54,6 @@ public: void setValue(const QVariant &value); private: - StateId m_id; StateTypeId m_stateTypeId; ThingId m_thingId; QVariant m_value; diff --git a/libnymea/types/statetype.cpp b/libnymea/types/statetype.cpp index ef20c147..ab505a90 100644 --- a/libnymea/types/statetype.cpp +++ b/libnymea/types/statetype.cpp @@ -172,6 +172,30 @@ void StateType::setUnit(const Types::Unit &unit) m_unit = unit; } +/*! Returns the IO type of this StateType. */ +Types::IOType StateType::ioType() const +{ + return m_ioType; +} + +/*! Sets the IO type of this StateType. */ +void StateType::setIOType(Types::IOType ioType) +{ + m_ioType = ioType; +} + +/*! Returns whether the StateType is writable or not. A writable StateType will have an according ActionType defined.*/ +bool StateType::writable() const +{ + return m_writable; +} + +/*! Sets the writable property to true */ +void StateType::setWritable(bool writable) +{ + m_writable = writable; +} + /*! Returns true if this StateType is to be cached. This means, the last state value will be stored to disk upon shutdown and restored on reboot. If this is false, states will be initialized with the default value on each boot. By default all states are cached by the system. */ bool StateType::cached() const { @@ -189,7 +213,7 @@ QStringList StateType::typeProperties() { return QStringList() << "id" << "name" << "displayName" << "displayNameEvent" << "type" << "defaultValue" << "cached" << "unit" << "minValue" << "maxValue" << "possibleValues" << "writable" - << "displayNameAction"; + << "displayNameAction" << "ioType"; } /*! Returns a list of mandatory properties a DeviceClass definition must have. */ @@ -205,6 +229,16 @@ StateTypes::StateTypes(const QList &other) } } +bool StateTypes::contains(const StateTypeId &stateTypeId) +{ + foreach (const StateType &stateType, *this) { + if (stateType.id() == stateTypeId) { + return true; + } + } + return false; +} + QVariant StateTypes::get(int index) const { return QVariant::fromValue(at(index)); diff --git a/libnymea/types/statetype.h b/libnymea/types/statetype.h index edb6a6bd..0bfdd6ee 100644 --- a/libnymea/types/statetype.h +++ b/libnymea/types/statetype.h @@ -46,6 +46,7 @@ class LIBNYMEA_EXPORT StateType Q_PROPERTY(int index READ index WRITE setIndex) Q_PROPERTY(QVariant defaultValue READ defaultValue WRITE setDefaultValue) Q_PROPERTY(Types::Unit unit READ unit WRITE setUnit USER true) + Q_PROPERTY(Types::IOType ioType READ ioType WRITE setIOType USER true) Q_PROPERTY(QVariant minValue READ minValue WRITE setMinValue USER true) Q_PROPERTY(QVariant maxValue READ maxValue WRITE setMaxValue USER true) Q_PROPERTY(QVariantList possibleValues READ possibleValues WRITE setPossibleValues USER true) @@ -83,6 +84,12 @@ public: Types::Unit unit() const; void setUnit(const Types::Unit &unit); + Types::IOType ioType() const; + void setIOType(Types::IOType ioType); + + bool writable() const; + void setWritable(bool writable); + bool cached() const; void setCached(bool cached); @@ -100,6 +107,8 @@ private: QVariant m_maxValue; QVariantList m_possibleValues; Types::Unit m_unit = Types::UnitNone; + Types::IOType m_ioType = Types::IOTypeNone; + bool m_writable = false; bool m_cached = true; }; Q_DECLARE_METATYPE(StateType) @@ -111,6 +120,7 @@ class StateTypes: public QList public: StateTypes() = default; StateTypes(const QList &other); + bool contains(const StateTypeId &stateTypeId); Q_INVOKABLE QVariant get(int index) const; Q_INVOKABLE void put(const QVariant &variant); StateType findByName(const QString &name); diff --git a/libnymea/typeutils.h b/libnymea/typeutils.h index 619b5a54..b76a5c6b 100644 --- a/libnymea/typeutils.h +++ b/libnymea/typeutils.h @@ -56,14 +56,12 @@ DECLARE_TYPE_ID(ThingDescriptor) DECLARE_TYPE_ID(ParamType) DECLARE_TYPE_ID(Param) DECLARE_TYPE_ID(EventType) -DECLARE_TYPE_ID(Event) DECLARE_TYPE_ID(StateType) -DECLARE_TYPE_ID(State) DECLARE_TYPE_ID(ActionType) -DECLARE_TYPE_ID(Action) DECLARE_TYPE_ID(Plugin) DECLARE_TYPE_ID(Rule) DECLARE_TYPE_ID(Browser) +DECLARE_TYPE_ID(IOConnection) DECLARE_TYPE_ID(PairingTransaction) @@ -165,6 +163,15 @@ public: BrowserTypeGeneric, }; Q_ENUM(BrowserType) + + enum IOType { + IOTypeNone, + IOTypeDigitalInput, + IOTypeDigitalOutput, + IOTypeAnalogInput, + IOTypeAnalogOutput + }; + Q_ENUM(IOType) }; Q_DECLARE_METATYPE(Types::InputType) diff --git a/nymea.pro b/nymea.pro index 94a7cd35..1f3b20ef 100644 --- a/nymea.pro +++ b/nymea.pro @@ -5,7 +5,7 @@ NYMEA_VERSION_STRING=$$system('dpkg-parsechangelog | sed -n -e "s/^Version: //p" # define protocol versions JSON_PROTOCOL_VERSION_MAJOR=5 -JSON_PROTOCOL_VERSION_MINOR=0 +JSON_PROTOCOL_VERSION_MINOR=1 JSON_PROTOCOL_VERSION="$${JSON_PROTOCOL_VERSION_MAJOR}.$${JSON_PROTOCOL_VERSION_MINOR}" LIBNYMEA_API_VERSION_MAJOR=5 LIBNYMEA_API_VERSION_MINOR=0 diff --git a/plugins/mock/extern-plugininfo.h b/plugins/mock/extern-plugininfo.h index 79b79c29..aca86972 100644 --- a/plugins/mock/extern-plugininfo.h +++ b/plugins/mock/extern-plugininfo.h @@ -246,5 +246,54 @@ extern ParamTypeId inputTypeMockWritableTimestampUIntActionWritableTimestampUInt extern ThingClassId oAuthGoogleMockThingClassId; extern ThingClassId oAuthSonosMockThingClassId; extern ThingClassId userAndPassMockThingClassId; +extern ThingClassId genericIoMockThingClassId; +extern StateTypeId genericIoMockDigitalInput1StateTypeId; +extern StateTypeId genericIoMockDigitalInput2StateTypeId; +extern StateTypeId genericIoMockDigitalOutput1StateTypeId; +extern StateTypeId genericIoMockDigitalOutput2StateTypeId; +extern StateTypeId genericIoMockAnalogInput1StateTypeId; +extern StateTypeId genericIoMockAnalogInput2StateTypeId; +extern StateTypeId genericIoMockAnalogOutput1StateTypeId; +extern StateTypeId genericIoMockAnalogOutput2StateTypeId; +extern EventTypeId genericIoMockDigitalInput1EventTypeId; +extern ParamTypeId genericIoMockDigitalInput1EventDigitalInput1ParamTypeId; +extern EventTypeId genericIoMockDigitalInput2EventTypeId; +extern ParamTypeId genericIoMockDigitalInput2EventDigitalInput2ParamTypeId; +extern EventTypeId genericIoMockDigitalOutput1EventTypeId; +extern ParamTypeId genericIoMockDigitalOutput1EventDigitalOutput1ParamTypeId; +extern EventTypeId genericIoMockDigitalOutput2EventTypeId; +extern ParamTypeId genericIoMockDigitalOutput2EventDigitalOutput2ParamTypeId; +extern EventTypeId genericIoMockAnalogInput1EventTypeId; +extern ParamTypeId genericIoMockAnalogInput1EventAnalogInput1ParamTypeId; +extern EventTypeId genericIoMockAnalogInput2EventTypeId; +extern ParamTypeId genericIoMockAnalogInput2EventAnalogInput2ParamTypeId; +extern EventTypeId genericIoMockAnalogOutput1EventTypeId; +extern ParamTypeId genericIoMockAnalogOutput1EventAnalogOutput1ParamTypeId; +extern EventTypeId genericIoMockAnalogOutput2EventTypeId; +extern ParamTypeId genericIoMockAnalogOutput2EventAnalogOutput2ParamTypeId; +extern ActionTypeId genericIoMockDigitalOutput1ActionTypeId; +extern ParamTypeId genericIoMockDigitalOutput1ActionDigitalOutput1ParamTypeId; +extern ActionTypeId genericIoMockDigitalOutput2ActionTypeId; +extern ParamTypeId genericIoMockDigitalOutput2ActionDigitalOutput2ParamTypeId; +extern ActionTypeId genericIoMockAnalogInput1ActionTypeId; +extern ParamTypeId genericIoMockAnalogInput1ActionAnalogInput1ParamTypeId; +extern ActionTypeId genericIoMockAnalogOutput1ActionTypeId; +extern ParamTypeId genericIoMockAnalogOutput1ActionAnalogOutput1ParamTypeId; +extern ActionTypeId genericIoMockAnalogOutput2ActionTypeId; +extern ParamTypeId genericIoMockAnalogOutput2ActionAnalogOutput2ParamTypeId; +extern ThingClassId virtualIoLightMockThingClassId; +extern StateTypeId virtualIoLightMockPowerStateTypeId; +extern EventTypeId virtualIoLightMockPowerEventTypeId; +extern ParamTypeId virtualIoLightMockPowerEventPowerParamTypeId; +extern ActionTypeId virtualIoLightMockPowerActionTypeId; +extern ParamTypeId virtualIoLightMockPowerActionPowerParamTypeId; +extern ThingClassId virtualIoTemperatureSensorMockThingClassId; +extern ParamTypeId virtualIoTemperatureSensorMockSettingsMinTempParamTypeId; +extern ParamTypeId virtualIoTemperatureSensorMockSettingsMaxTempParamTypeId; +extern StateTypeId virtualIoTemperatureSensorMockTemperatureStateTypeId; +extern EventTypeId virtualIoTemperatureSensorMockTemperatureEventTypeId; +extern ParamTypeId virtualIoTemperatureSensorMockTemperatureEventTemperatureParamTypeId; +extern ActionTypeId virtualIoTemperatureSensorMockTemperatureActionTypeId; +extern ParamTypeId virtualIoTemperatureSensorMockTemperatureActionTemperatureParamTypeId; #endif // EXTERNPLUGININFO_H diff --git a/plugins/mock/integrationpluginmock.cpp b/plugins/mock/integrationpluginmock.cpp index a85d8c57..bbf0ff8b 100644 --- a/plugins/mock/integrationpluginmock.cpp +++ b/plugins/mock/integrationpluginmock.cpp @@ -233,6 +233,24 @@ void IntegrationPluginMock::setupThing(ThingSetupInfo *info) return; } + if (info->thing()->thingClassId() == genericIoMockThingClassId) { + qCDebug(dcMock()) << "Generic IO mock setup complete"; + info->finish(Thing::ThingErrorNoError); + return; + } + + if (info->thing()->thingClassId() == virtualIoLightMockThingClassId) { + qCDebug(dcMock()) << "Virtual IO mock light setup complete"; + info->finish(Thing::ThingErrorNoError); + return; + } + + if (info->thing()->thingClassId() == virtualIoTemperatureSensorMockThingClassId) { + qCDebug(dcMock()) << "Virtual IO mock temperature sensor setup complete"; + info->finish(Thing::ThingErrorNoError); + return; + } + qCWarning(dcMock()) << "Unhandled thing class" << info->thing()->thingClass(); info->finish(Thing::ThingErrorThingClassNotFound); } @@ -689,9 +707,59 @@ void IntegrationPluginMock::executeAction(ThingActionInfo *info) info->thing()->setStateValue(inputTypeMockWritableTimestampUIntStateTypeId, info->action().param(inputTypeMockWritableTimestampUIntActionWritableTimestampUIntParamTypeId).value().toULongLong()); } return; - } - info->finish(Thing::ThingErrorThingClassNotFound); + + if (info->thing()->thingClassId() == virtualIoLightMockThingClassId) { + if (info->action().actionTypeId() == virtualIoLightMockPowerActionTypeId) { + qCDebug(dcMock()) << "ExecuteAction for virtual light power action with param" << info->action().param(virtualIoLightMockPowerActionPowerParamTypeId).value(); + info->thing()->setStateValue(virtualIoLightMockPowerStateTypeId, info->action().param(virtualIoLightMockPowerActionPowerParamTypeId).value()); + info->finish(Thing::ThingErrorNoError); + return; + } + } + + if (info->thing()->thingClassId() == genericIoMockThingClassId) { + if (info->action().actionTypeId() == genericIoMockDigitalOutput1ActionTypeId) { + info->thing()->setStateValue(genericIoMockDigitalOutput1StateTypeId, info->action().param(genericIoMockDigitalOutput1ActionDigitalOutput1ParamTypeId).value()); + info->finish(Thing::ThingErrorNoError); + return; + } + if (info->action().actionTypeId() == genericIoMockDigitalOutput2ActionTypeId) { + info->thing()->setStateValue(genericIoMockDigitalOutput2StateTypeId, info->action().param(genericIoMockDigitalOutput2ActionDigitalOutput2ParamTypeId).value()); + info->finish(Thing::ThingErrorNoError); + return; + } + if (info->action().actionTypeId() == genericIoMockAnalogInput1ActionTypeId) { + qCDebug(dcMock()) << "ExecuteAction for virtual io analog in 1 action with param" << info->action().param(genericIoMockAnalogInput1ActionAnalogInput1ParamTypeId).value(); + info->thing()->setStateValue(genericIoMockAnalogInput1StateTypeId, info->action().param(genericIoMockAnalogInput1ActionAnalogInput1ParamTypeId).value()); + info->finish(Thing::ThingErrorNoError); + return; + } + if (info->action().actionTypeId() == genericIoMockAnalogOutput1ActionTypeId) { + info->thing()->setStateValue(genericIoMockAnalogOutput1StateTypeId, info->action().param(genericIoMockAnalogOutput1ActionAnalogOutput1ParamTypeId).value()); + info->finish(Thing::ThingErrorNoError); + return; + } + if (info->action().actionTypeId() == genericIoMockAnalogOutput2ActionTypeId) { + info->thing()->setStateValue(genericIoMockAnalogOutput2StateTypeId, info->action().param(genericIoMockAnalogOutput2ActionAnalogOutput2ParamTypeId).value()); + info->finish(Thing::ThingErrorNoError); + return; + } + } + + if (info->thing()->thingClassId() == virtualIoTemperatureSensorMockThingClassId) { + if (info->action().actionTypeId() == virtualIoTemperatureSensorMockTemperatureActionTypeId) { + double minTemp = info->thing()->setting(virtualIoTemperatureSensorMockSettingsMinTempParamTypeId).toDouble(); + double maxTemp = info->thing()->setting(virtualIoTemperatureSensorMockSettingsMaxTempParamTypeId).toDouble(); + double value = info->action().param(virtualIoTemperatureSensorMockTemperatureActionTemperatureParamTypeId).value().toDouble(); + double temp = minTemp + (maxTemp - minTemp) * value; + info->thing()->setStateValue(virtualIoTemperatureSensorMockTemperatureStateTypeId, temp); + info->finish(Thing::ThingErrorNoError); + return; + } + } + + qCWarning(dcMock()) << "Unhandled executeAction call in mock plugin!"; } void IntegrationPluginMock::executeBrowserItem(BrowserActionInfo *info) @@ -728,7 +796,6 @@ void IntegrationPluginMock::executeBrowserItem(BrowserActionInfo *info) void IntegrationPluginMock::executeBrowserItemAction(BrowserItemActionInfo *info) { - qCDebug(dcMock()) << "TODO" << info << info->browserItemAction().id(); if (info->browserItemAction().actionTypeId() == mockAddToFavoritesBrowserItemActionTypeId) { VirtualFsNode *node = m_virtualFs->findNode(info->browserItemAction().itemId()); diff --git a/plugins/mock/integrationpluginmock.json b/plugins/mock/integrationpluginmock.json index b590e091..0931a01e 100644 --- a/plugins/mock/integrationpluginmock.json +++ b/plugins/mock/integrationpluginmock.json @@ -868,6 +868,168 @@ "displayName": "Mocked Thing (User & Password)", "createMethods": ["discovery", "user"], "setupMethod": "userandpassword" + }, + { + "id": "7cbd729a-465b-4ccb-b59c-5733039dbbed", + "name": "genericIoMock", + "displayName": "Generic IO pins", + "createMethods": ["user"], + "setupMethod": "justAdd", + "stateTypes": [ + { + "id": "07165c12-4d53-45c0-8bf1-34618443b706", + "name": "digitalInput1", + "displayName": "Digital input 1", + "displayNameEvent": "Digital input 1 changed", + "type": "bool", + "defaultValue": false, + "ioType": "digitalInput" + }, + { + "id": "0a4362ba-a086-4540-84ba-107ef7b99ed8", + "name": "digitalInput2", + "displayName": "Digital input 2", + "displayNameEvent": "Digital input 2 changed", + "type": "bool", + "defaultValue": false, + "ioType": "digitalInput" + }, + { + "id": "d6fcdb52-f7c3-423b-b9f5-1e29f164c42e", + "name": "digitalOutput1", + "displayName": "Digital Output 1", + "displayNameEvent": "Digital Output 1 changed", + "displayNameAction": "Set Digital Output 1", + "type": "bool", + "defaultValue": false, + "ioType": "digitalOutput", + "writable": true + }, + { + "id": "35de8b68-0cf3-4850-a27d-cf9c4a26921f", + "name": "digitalOutput2", + "displayName": "Digital Output 2", + "displayNameEvent": "Digital Output 2 changed", + "displayNameAction": "Set Digital Output 2", + "type": "bool", + "defaultValue": false, + "ioType": "digitalOutput", + "writable": true + }, + { + "id": "ac56977c-cbba-47c6-a827-5735d8b0aed6", + "name": "analogInput1", + "displayName": "Analog Input 1", + "displayNameEvent": "Analog Input 1 changed", + "type": "double", + "defaultValue": 0, + "ioType": "analogInput", + "minValue": 0, + "maxValue": 3.3, + "writable": true, + "displayNameAction": "Set analog input 1" + }, + { + "id": "8e07e57e-ba4e-42df-81ee-5b72ed074532", + "name": "analogInput2", + "displayName": "Analog Input 2", + "displayNameEvent": "Analog Input 2 changed", + "type": "double", + "defaultValue": 0, + "ioType": "analogInput", + "minValue": 0, + "maxValue": 5 + }, + { + "id": "70cf053e-4abc-4d88-8e1e-2bd9a62256c7", + "name": "analogOutput1", + "displayName": "Analog Output 1", + "displayNameEvent": "Analog Output 1 changed", + "displayNameAction": "Set Output Input 1", + "type": "double", + "defaultValue": 0, + "ioType": "analogOutput", + "minValue": 0, + "maxValue": 3.3, + "writable": true + }, + { + "id": "e40bcf7d-47b8-41fa-b213-3652a905b376", + "name": "analogOutput2", + "displayName": "Analog Output 2", + "displayNameEvent": "Analog Output 2 changed", + "displayNameAction": "Set Output Input 2", + "type": "double", + "defaultValue": 0, + "ioType": "analogOutput", + "minValue": 0, + "maxValue": 5, + "writable": true + } + ] + }, + { + "id": "98ab137e-757e-43f8-9d9b-5d50d990242a", + "name": "virtualIoLightMock", + "displayName": "Generic Light (Mock)", + "createMethods": ["user"], + "setupMethod": "justAdd", + "interfaces": ["light"], + "stateTypes": [ + { + "id": "d1917b3d-1530-4cf9-90f7-263ee88e714b", + "name": "power", + "displayName": "Power", + "displayNameEvent": "Power changed", + "displayNameAction": "Set power", + "type": "bool", + "defaultValue": false, + "ioType": "digitalInput", + "writable": true + } + ] + }, + { + "id": "f8917e12-c9cb-4ea1-a06e-1ce6db2194f3", + "name": "virtualIoTemperatureSensorMock", + "displayName": "Generic Temperature Sensor (Mock)", + "createMethods": ["user"], + "setupMethod": "justAdd", + "interfaces": ["temperaturesensor"], + "settingsTypes": [ + { + "id": "803cddbf-94c7-4f35-bc7a-18698b03b942", + "name": "minTemp", + "displayName": "Minimum temperature", + "type": "double", + "defaultValue": -20, + "unit": "DegreeCelsius" + }, + { + "id": "7077c56f-c35b-4252-8c15-8fb549be04ce", + "name": "maxTemp", + "displayName": "Maximum temperature", + "type": "double", + "defaultValue": 50, + "unit": "DegreeCelsius" + } + ], + "stateTypes": [ + { + "id": "db9cc518-1012-47e2-8212-6e616fed07a6", + "name": "temperature", + "displayName": "Temperature", + "displayNameEvent": "Temperature changed", + "displayNameAction": "Set temperature", + "type": "double", + "unit": "DegreeCelsius", + "defaultValue": 0, + "ioType": "analogOutput", + "writable": true, + "minValue": 0, + "maxValue": 1 + } + ] } ] } diff --git a/plugins/mock/plugininfo.h b/plugins/mock/plugininfo.h index 5bf42599..e1b03571 100644 --- a/plugins/mock/plugininfo.h +++ b/plugins/mock/plugininfo.h @@ -250,11 +250,105 @@ ParamTypeId inputTypeMockWritableTimestampUIntActionWritableTimestampUIntParamTy ThingClassId oAuthGoogleMockThingClassId = ThingClassId("{805d1692-7bd0-449a-9d5c-43a332ff58f4}"); ThingClassId oAuthSonosMockThingClassId = ThingClassId("{783c615b-7bd6-49a4-98b0-8d1deb3c7156}"); ThingClassId userAndPassMockThingClassId = ThingClassId("{6fe07a77-9c07-4736-81e2-d504314bbcb9}"); +ThingClassId genericIoMockThingClassId = ThingClassId("{7cbd729a-465b-4ccb-b59c-5733039dbbed}"); +StateTypeId genericIoMockDigitalInput1StateTypeId = StateTypeId("{07165c12-4d53-45c0-8bf1-34618443b706}"); +StateTypeId genericIoMockDigitalInput2StateTypeId = StateTypeId("{0a4362ba-a086-4540-84ba-107ef7b99ed8}"); +StateTypeId genericIoMockDigitalOutput1StateTypeId = StateTypeId("{d6fcdb52-f7c3-423b-b9f5-1e29f164c42e}"); +StateTypeId genericIoMockDigitalOutput2StateTypeId = StateTypeId("{35de8b68-0cf3-4850-a27d-cf9c4a26921f}"); +StateTypeId genericIoMockAnalogInput1StateTypeId = StateTypeId("{ac56977c-cbba-47c6-a827-5735d8b0aed6}"); +StateTypeId genericIoMockAnalogInput2StateTypeId = StateTypeId("{8e07e57e-ba4e-42df-81ee-5b72ed074532}"); +StateTypeId genericIoMockAnalogOutput1StateTypeId = StateTypeId("{70cf053e-4abc-4d88-8e1e-2bd9a62256c7}"); +StateTypeId genericIoMockAnalogOutput2StateTypeId = StateTypeId("{e40bcf7d-47b8-41fa-b213-3652a905b376}"); +EventTypeId genericIoMockDigitalInput1EventTypeId = EventTypeId("{07165c12-4d53-45c0-8bf1-34618443b706}"); +ParamTypeId genericIoMockDigitalInput1EventDigitalInput1ParamTypeId = ParamTypeId("{07165c12-4d53-45c0-8bf1-34618443b706}"); +EventTypeId genericIoMockDigitalInput2EventTypeId = EventTypeId("{0a4362ba-a086-4540-84ba-107ef7b99ed8}"); +ParamTypeId genericIoMockDigitalInput2EventDigitalInput2ParamTypeId = ParamTypeId("{0a4362ba-a086-4540-84ba-107ef7b99ed8}"); +EventTypeId genericIoMockDigitalOutput1EventTypeId = EventTypeId("{d6fcdb52-f7c3-423b-b9f5-1e29f164c42e}"); +ParamTypeId genericIoMockDigitalOutput1EventDigitalOutput1ParamTypeId = ParamTypeId("{d6fcdb52-f7c3-423b-b9f5-1e29f164c42e}"); +EventTypeId genericIoMockDigitalOutput2EventTypeId = EventTypeId("{35de8b68-0cf3-4850-a27d-cf9c4a26921f}"); +ParamTypeId genericIoMockDigitalOutput2EventDigitalOutput2ParamTypeId = ParamTypeId("{35de8b68-0cf3-4850-a27d-cf9c4a26921f}"); +EventTypeId genericIoMockAnalogInput1EventTypeId = EventTypeId("{ac56977c-cbba-47c6-a827-5735d8b0aed6}"); +ParamTypeId genericIoMockAnalogInput1EventAnalogInput1ParamTypeId = ParamTypeId("{ac56977c-cbba-47c6-a827-5735d8b0aed6}"); +EventTypeId genericIoMockAnalogInput2EventTypeId = EventTypeId("{8e07e57e-ba4e-42df-81ee-5b72ed074532}"); +ParamTypeId genericIoMockAnalogInput2EventAnalogInput2ParamTypeId = ParamTypeId("{8e07e57e-ba4e-42df-81ee-5b72ed074532}"); +EventTypeId genericIoMockAnalogOutput1EventTypeId = EventTypeId("{70cf053e-4abc-4d88-8e1e-2bd9a62256c7}"); +ParamTypeId genericIoMockAnalogOutput1EventAnalogOutput1ParamTypeId = ParamTypeId("{70cf053e-4abc-4d88-8e1e-2bd9a62256c7}"); +EventTypeId genericIoMockAnalogOutput2EventTypeId = EventTypeId("{e40bcf7d-47b8-41fa-b213-3652a905b376}"); +ParamTypeId genericIoMockAnalogOutput2EventAnalogOutput2ParamTypeId = ParamTypeId("{e40bcf7d-47b8-41fa-b213-3652a905b376}"); +ActionTypeId genericIoMockDigitalOutput1ActionTypeId = ActionTypeId("{d6fcdb52-f7c3-423b-b9f5-1e29f164c42e}"); +ParamTypeId genericIoMockDigitalOutput1ActionDigitalOutput1ParamTypeId = ParamTypeId("{d6fcdb52-f7c3-423b-b9f5-1e29f164c42e}"); +ActionTypeId genericIoMockDigitalOutput2ActionTypeId = ActionTypeId("{35de8b68-0cf3-4850-a27d-cf9c4a26921f}"); +ParamTypeId genericIoMockDigitalOutput2ActionDigitalOutput2ParamTypeId = ParamTypeId("{35de8b68-0cf3-4850-a27d-cf9c4a26921f}"); +ActionTypeId genericIoMockAnalogInput1ActionTypeId = ActionTypeId("{ac56977c-cbba-47c6-a827-5735d8b0aed6}"); +ParamTypeId genericIoMockAnalogInput1ActionAnalogInput1ParamTypeId = ParamTypeId("{ac56977c-cbba-47c6-a827-5735d8b0aed6}"); +ActionTypeId genericIoMockAnalogOutput1ActionTypeId = ActionTypeId("{70cf053e-4abc-4d88-8e1e-2bd9a62256c7}"); +ParamTypeId genericIoMockAnalogOutput1ActionAnalogOutput1ParamTypeId = ParamTypeId("{70cf053e-4abc-4d88-8e1e-2bd9a62256c7}"); +ActionTypeId genericIoMockAnalogOutput2ActionTypeId = ActionTypeId("{e40bcf7d-47b8-41fa-b213-3652a905b376}"); +ParamTypeId genericIoMockAnalogOutput2ActionAnalogOutput2ParamTypeId = ParamTypeId("{e40bcf7d-47b8-41fa-b213-3652a905b376}"); +ThingClassId virtualIoLightMockThingClassId = ThingClassId("{98ab137e-757e-43f8-9d9b-5d50d990242a}"); +StateTypeId virtualIoLightMockPowerStateTypeId = StateTypeId("{d1917b3d-1530-4cf9-90f7-263ee88e714b}"); +EventTypeId virtualIoLightMockPowerEventTypeId = EventTypeId("{d1917b3d-1530-4cf9-90f7-263ee88e714b}"); +ParamTypeId virtualIoLightMockPowerEventPowerParamTypeId = ParamTypeId("{d1917b3d-1530-4cf9-90f7-263ee88e714b}"); +ActionTypeId virtualIoLightMockPowerActionTypeId = ActionTypeId("{d1917b3d-1530-4cf9-90f7-263ee88e714b}"); +ParamTypeId virtualIoLightMockPowerActionPowerParamTypeId = ParamTypeId("{d1917b3d-1530-4cf9-90f7-263ee88e714b}"); +ThingClassId virtualIoTemperatureSensorMockThingClassId = ThingClassId("{f8917e12-c9cb-4ea1-a06e-1ce6db2194f3}"); +ParamTypeId virtualIoTemperatureSensorMockSettingsMinTempParamTypeId = ParamTypeId("{803cddbf-94c7-4f35-bc7a-18698b03b942}"); +ParamTypeId virtualIoTemperatureSensorMockSettingsMaxTempParamTypeId = ParamTypeId("{7077c56f-c35b-4252-8c15-8fb549be04ce}"); +StateTypeId virtualIoTemperatureSensorMockTemperatureStateTypeId = StateTypeId("{db9cc518-1012-47e2-8212-6e616fed07a6}"); +EventTypeId virtualIoTemperatureSensorMockTemperatureEventTypeId = EventTypeId("{db9cc518-1012-47e2-8212-6e616fed07a6}"); +ParamTypeId virtualIoTemperatureSensorMockTemperatureEventTemperatureParamTypeId = ParamTypeId("{db9cc518-1012-47e2-8212-6e616fed07a6}"); +ActionTypeId virtualIoTemperatureSensorMockTemperatureActionTypeId = ActionTypeId("{db9cc518-1012-47e2-8212-6e616fed07a6}"); +ParamTypeId virtualIoTemperatureSensorMockTemperatureActionTemperatureParamTypeId = ParamTypeId("{db9cc518-1012-47e2-8212-6e616fed07a6}"); const QString translations[] { //: The name of the Browser Item ActionType ({00b8f0a8-99ca-4aa4-833d-59eb8d4d6de3}) of ThingClass mock QT_TRANSLATE_NOOP("mock", "Add to favorites"), + //: The name of the ParamType (ThingClass: genericIoMock, ActionType: analogInput1, ID: {ac56977c-cbba-47c6-a827-5735d8b0aed6}) + QT_TRANSLATE_NOOP("mock", "Analog Input 1"), + + //: The name of the ParamType (ThingClass: genericIoMock, EventType: analogInput1, ID: {ac56977c-cbba-47c6-a827-5735d8b0aed6}) + QT_TRANSLATE_NOOP("mock", "Analog Input 1"), + + //: The name of the StateType ({ac56977c-cbba-47c6-a827-5735d8b0aed6}) of ThingClass genericIoMock + QT_TRANSLATE_NOOP("mock", "Analog Input 1"), + + //: The name of the EventType ({ac56977c-cbba-47c6-a827-5735d8b0aed6}) of ThingClass genericIoMock + QT_TRANSLATE_NOOP("mock", "Analog Input 1 changed"), + + //: The name of the ParamType (ThingClass: genericIoMock, EventType: analogInput2, ID: {8e07e57e-ba4e-42df-81ee-5b72ed074532}) + QT_TRANSLATE_NOOP("mock", "Analog Input 2"), + + //: The name of the StateType ({8e07e57e-ba4e-42df-81ee-5b72ed074532}) of ThingClass genericIoMock + QT_TRANSLATE_NOOP("mock", "Analog Input 2"), + + //: The name of the EventType ({8e07e57e-ba4e-42df-81ee-5b72ed074532}) of ThingClass genericIoMock + QT_TRANSLATE_NOOP("mock", "Analog Input 2 changed"), + + //: The name of the ParamType (ThingClass: genericIoMock, ActionType: analogOutput1, ID: {70cf053e-4abc-4d88-8e1e-2bd9a62256c7}) + QT_TRANSLATE_NOOP("mock", "Analog Output 1"), + + //: The name of the ParamType (ThingClass: genericIoMock, EventType: analogOutput1, ID: {70cf053e-4abc-4d88-8e1e-2bd9a62256c7}) + QT_TRANSLATE_NOOP("mock", "Analog Output 1"), + + //: The name of the StateType ({70cf053e-4abc-4d88-8e1e-2bd9a62256c7}) of ThingClass genericIoMock + QT_TRANSLATE_NOOP("mock", "Analog Output 1"), + + //: The name of the EventType ({70cf053e-4abc-4d88-8e1e-2bd9a62256c7}) of ThingClass genericIoMock + QT_TRANSLATE_NOOP("mock", "Analog Output 1 changed"), + + //: The name of the ParamType (ThingClass: genericIoMock, ActionType: analogOutput2, ID: {e40bcf7d-47b8-41fa-b213-3652a905b376}) + QT_TRANSLATE_NOOP("mock", "Analog Output 2"), + + //: The name of the ParamType (ThingClass: genericIoMock, EventType: analogOutput2, ID: {e40bcf7d-47b8-41fa-b213-3652a905b376}) + QT_TRANSLATE_NOOP("mock", "Analog Output 2"), + + //: The name of the StateType ({e40bcf7d-47b8-41fa-b213-3652a905b376}) of ThingClass genericIoMock + QT_TRANSLATE_NOOP("mock", "Analog Output 2"), + + //: The name of the EventType ({e40bcf7d-47b8-41fa-b213-3652a905b376}) of ThingClass genericIoMock + QT_TRANSLATE_NOOP("mock", "Analog Output 2 changed"), + //: The name of the ParamType (ThingClass: inputTypeMock, EventType: bool, ID: {3bad3a09-5826-4ed7-a832-10e3e2ee2a7d}) QT_TRANSLATE_NOOP("mock", "Bool"), @@ -273,6 +367,48 @@ const QString translations[] { //: The name of the EventType ({4507d5c6-b692-4bd6-87f2-00364bc0cb4d}) of ThingClass inputTypeMock QT_TRANSLATE_NOOP("mock", "Color changed"), + //: The name of the ParamType (ThingClass: genericIoMock, ActionType: digitalOutput1, ID: {d6fcdb52-f7c3-423b-b9f5-1e29f164c42e}) + QT_TRANSLATE_NOOP("mock", "Digital Output 1"), + + //: The name of the ParamType (ThingClass: genericIoMock, EventType: digitalOutput1, ID: {d6fcdb52-f7c3-423b-b9f5-1e29f164c42e}) + QT_TRANSLATE_NOOP("mock", "Digital Output 1"), + + //: The name of the StateType ({d6fcdb52-f7c3-423b-b9f5-1e29f164c42e}) of ThingClass genericIoMock + QT_TRANSLATE_NOOP("mock", "Digital Output 1"), + + //: The name of the EventType ({d6fcdb52-f7c3-423b-b9f5-1e29f164c42e}) of ThingClass genericIoMock + QT_TRANSLATE_NOOP("mock", "Digital Output 1 changed"), + + //: The name of the ParamType (ThingClass: genericIoMock, ActionType: digitalOutput2, ID: {35de8b68-0cf3-4850-a27d-cf9c4a26921f}) + QT_TRANSLATE_NOOP("mock", "Digital Output 2"), + + //: The name of the ParamType (ThingClass: genericIoMock, EventType: digitalOutput2, ID: {35de8b68-0cf3-4850-a27d-cf9c4a26921f}) + QT_TRANSLATE_NOOP("mock", "Digital Output 2"), + + //: The name of the StateType ({35de8b68-0cf3-4850-a27d-cf9c4a26921f}) of ThingClass genericIoMock + QT_TRANSLATE_NOOP("mock", "Digital Output 2"), + + //: The name of the EventType ({35de8b68-0cf3-4850-a27d-cf9c4a26921f}) of ThingClass genericIoMock + QT_TRANSLATE_NOOP("mock", "Digital Output 2 changed"), + + //: The name of the ParamType (ThingClass: genericIoMock, EventType: digitalInput1, ID: {07165c12-4d53-45c0-8bf1-34618443b706}) + QT_TRANSLATE_NOOP("mock", "Digital input 1"), + + //: The name of the StateType ({07165c12-4d53-45c0-8bf1-34618443b706}) of ThingClass genericIoMock + QT_TRANSLATE_NOOP("mock", "Digital input 1"), + + //: The name of the EventType ({07165c12-4d53-45c0-8bf1-34618443b706}) of ThingClass genericIoMock + QT_TRANSLATE_NOOP("mock", "Digital input 1 changed"), + + //: The name of the ParamType (ThingClass: genericIoMock, EventType: digitalInput2, ID: {0a4362ba-a086-4540-84ba-107ef7b99ed8}) + QT_TRANSLATE_NOOP("mock", "Digital input 2"), + + //: The name of the StateType ({0a4362ba-a086-4540-84ba-107ef7b99ed8}) of ThingClass genericIoMock + QT_TRANSLATE_NOOP("mock", "Digital input 2"), + + //: The name of the EventType ({0a4362ba-a086-4540-84ba-107ef7b99ed8}) of ThingClass genericIoMock + QT_TRANSLATE_NOOP("mock", "Digital input 2 changed"), + //: The name of the ParamType (ThingClass: inputTypeMock, EventType: double, ID: {f7d2063d-959e-46ac-8568-8b99722d3b22}) QT_TRANSLATE_NOOP("mock", "Double"), @@ -327,6 +463,15 @@ const QString translations[] { //: The name of the EventType ({80baec19-54de-4948-ac46-31eabfaceb83}) of ThingClass mock QT_TRANSLATE_NOOP("mock", "Dummy int state changed"), + //: The name of the ThingClass ({7cbd729a-465b-4ccb-b59c-5733039dbbed}) + QT_TRANSLATE_NOOP("mock", "Generic IO pins"), + + //: The name of the ThingClass ({98ab137e-757e-43f8-9d9b-5d50d990242a}) + QT_TRANSLATE_NOOP("mock", "Generic Light (Mock)"), + + //: The name of the ThingClass ({f8917e12-c9cb-4ea1-a06e-1ce6db2194f3}) + QT_TRANSLATE_NOOP("mock", "Generic Temperature Sensor (Mock)"), + //: The name of the ParamType (ThingClass: inputTypeMock, Type: thing, ID: {9e5f86a0-4bb3-4892-bff8-3fc4032af6e2}) QT_TRANSLATE_NOOP("mock", "IPv4 address"), @@ -348,6 +493,12 @@ const QString translations[] { //: The name of the ParamType (ThingClass: inputTypeMock, Type: thing, ID: {a8494faf-3a0f-4cf3-84b7-4b39148a838d}) QT_TRANSLATE_NOOP("mock", "Mail address"), + //: The name of the ParamType (ThingClass: virtualIoTemperatureSensorMock, Type: settings, ID: {7077c56f-c35b-4252-8c15-8fb549be04ce}) + QT_TRANSLATE_NOOP("mock", "Maximum temperature"), + + //: The name of the ParamType (ThingClass: virtualIoTemperatureSensorMock, Type: settings, ID: {803cddbf-94c7-4f35-bc7a-18698b03b942}) + QT_TRANSLATE_NOOP("mock", "Minimum temperature"), + //: The name of the ActionType ({07cd8d5f-2f65-4955-b1f9-05d7f4da488a}) of ThingClass autoMock QT_TRANSLATE_NOOP("mock", "Mock Action 1 (with params)"), @@ -426,12 +577,36 @@ const QString translations[] { //: The name of the ParamType (ThingClass: inputTypeMock, Type: thing, ID: {e5c0d14b-c9f1-4aca-a56e-85bfa6977150}) QT_TRANSLATE_NOOP("mock", "Password text"), + //: The name of the ParamType (ThingClass: virtualIoLightMock, ActionType: power, ID: {d1917b3d-1530-4cf9-90f7-263ee88e714b}) + QT_TRANSLATE_NOOP("mock", "Power"), + + //: The name of the ParamType (ThingClass: virtualIoLightMock, EventType: power, ID: {d1917b3d-1530-4cf9-90f7-263ee88e714b}) + QT_TRANSLATE_NOOP("mock", "Power"), + + //: The name of the StateType ({d1917b3d-1530-4cf9-90f7-263ee88e714b}) of ThingClass virtualIoLightMock + QT_TRANSLATE_NOOP("mock", "Power"), + + //: The name of the EventType ({d1917b3d-1530-4cf9-90f7-263ee88e714b}) of ThingClass virtualIoLightMock + QT_TRANSLATE_NOOP("mock", "Power changed"), + //: The name of the Browser Item ActionType ({da6faef8-2816-430e-93bb-57e8f9582d29}) of ThingClass mock QT_TRANSLATE_NOOP("mock", "Remove from favorites"), //: The name of the ParamType (ThingClass: inputTypeMock, Type: thing, ID: {22add8c9-ee4f-43ad-8931-58e999313ac3}) QT_TRANSLATE_NOOP("mock", "Search text"), + //: The name of the ActionType ({d6fcdb52-f7c3-423b-b9f5-1e29f164c42e}) of ThingClass genericIoMock + QT_TRANSLATE_NOOP("mock", "Set Digital Output 1"), + + //: The name of the ActionType ({35de8b68-0cf3-4850-a27d-cf9c4a26921f}) of ThingClass genericIoMock + QT_TRANSLATE_NOOP("mock", "Set Digital Output 2"), + + //: The name of the ActionType ({70cf053e-4abc-4d88-8e1e-2bd9a62256c7}) of ThingClass genericIoMock + QT_TRANSLATE_NOOP("mock", "Set Output Input 1"), + + //: The name of the ActionType ({e40bcf7d-47b8-41fa-b213-3652a905b376}) of ThingClass genericIoMock + QT_TRANSLATE_NOOP("mock", "Set Output Input 2"), + //: The name of the ActionType ({a7c11774-f31f-4d64-99d1-e0ae5fb35a5c}) of ThingClass inputTypeMock QT_TRANSLATE_NOOP("mock", "Set Writable Bool"), @@ -477,6 +652,9 @@ const QString translations[] { //: The name of the ActionType ({05f63f9c-f61e-4dcf-ad55-3f13fde2765b}) of ThingClass pushButtonMock QT_TRANSLATE_NOOP("mock", "Set allowed values"), + //: The name of the ActionType ({ac56977c-cbba-47c6-a827-5735d8b0aed6}) of ThingClass genericIoMock + QT_TRANSLATE_NOOP("mock", "Set analog input 1"), + //: The name of the ActionType ({80ba1449-b485-47d4-a067-6bf306e2a568}) of ThingClass childMock QT_TRANSLATE_NOOP("mock", "Set bool value"), @@ -507,6 +685,12 @@ const QString translations[] { //: The name of the ActionType ({72981c04-267a-4ba0-a59e-9921d2f3af9c}) of ThingClass pushButtonMock QT_TRANSLATE_NOOP("mock", "Set percentage"), + //: The name of the ActionType ({d1917b3d-1530-4cf9-90f7-263ee88e714b}) of ThingClass virtualIoLightMock + QT_TRANSLATE_NOOP("mock", "Set power"), + + //: The name of the ActionType ({db9cc518-1012-47e2-8212-6e616fed07a6}) of ThingClass virtualIoTemperatureSensorMock + QT_TRANSLATE_NOOP("mock", "Set temperature"), + //: The name of the ParamType (ThingClass: mock, Type: settings, ID: {367f7ba4-5039-47be-abd8-59cc8eaf4b9a}) QT_TRANSLATE_NOOP("mock", "Setting 1"), @@ -519,6 +703,18 @@ const QString translations[] { //: The name of the EventType ({27f69ca9-a321-40ff-bfee-4b0272a671b4}) of ThingClass inputTypeMock QT_TRANSLATE_NOOP("mock", "String changed"), + //: The name of the ParamType (ThingClass: virtualIoTemperatureSensorMock, ActionType: temperature, ID: {db9cc518-1012-47e2-8212-6e616fed07a6}) + QT_TRANSLATE_NOOP("mock", "Temperature"), + + //: The name of the ParamType (ThingClass: virtualIoTemperatureSensorMock, EventType: temperature, ID: {db9cc518-1012-47e2-8212-6e616fed07a6}) + QT_TRANSLATE_NOOP("mock", "Temperature"), + + //: The name of the StateType ({db9cc518-1012-47e2-8212-6e616fed07a6}) of ThingClass virtualIoTemperatureSensorMock + QT_TRANSLATE_NOOP("mock", "Temperature"), + + //: The name of the EventType ({db9cc518-1012-47e2-8212-6e616fed07a6}) of ThingClass virtualIoTemperatureSensorMock + QT_TRANSLATE_NOOP("mock", "Temperature changed"), + //: The name of the ParamType (ThingClass: inputTypeMock, Type: thing, ID: {716f0994-bc01-42b0-b64d-59236f7320d2}) QT_TRANSLATE_NOOP("mock", "Text area"), diff --git a/tests/auto/api.json b/tests/auto/api.json index 92665af5..b3a4757c 100644 --- a/tests/auto/api.json +++ b/tests/auto/api.json @@ -1,4 +1,4 @@ -5.0 +5.1 { "enums": { "BasicType": [ @@ -82,6 +82,13 @@ "DeviceSetupStatusComplete", "DeviceSetupStatusFailed" ], + "IOType": [ + "IOTypeNone", + "IOTypeDigitalInput", + "IOTypeDigitalOutput", + "IOTypeAnalogInput", + "IOTypeAnalogOutput" + ], "InputType": [ "InputTypeNone", "InputTypeTextLine", @@ -937,6 +944,27 @@ "thingError": "$ref:ThingError" } }, + "Integrations.ConnectIO": { + "description": "Connect two generic IO states.", + "params": { + "inputStateTypeId": "Uuid", + "inputThingId": "Uuid", + "outputStateTypeId": "Uuid", + "outputThingId": "Uuid" + }, + "returns": { + "thingError": "$ref:ThingError" + } + }, + "Integrations.DisconnectIO": { + "description": "Disconnect an existing IO connection.", + "params": { + "ioConnectionId": "Uuid" + }, + "returns": { + "thingError": "$ref:ThingError" + } + }, "Integrations.DiscoverThings": { "description": "Performs a thing discovery for things of the given thingClassId and returns the results. This function may take a while to return. Note that this method will include all the found things, that is, including things that may already have been added. Those things will have thingId set to the id of the already added thing. Such results may be used to reconfigure existing things and might be filtered in cases where only unknown things are of interest.", "params": { @@ -1025,6 +1053,15 @@ "eventTypes": "$ref:EventTypes" } }, + "Integrations.GetIOConnections": { + "description": "Fetch IO connections. Optionally filtered by thingId and stateTypeId.", + "params": { + "o:thingId": "Uuid" + }, + "returns": { + "ioConnections": "$ref:IOConnections" + } + }, "Integrations.GetPluginConfiguration": { "description": "Get a plugin's params.", "params": { @@ -2015,6 +2052,18 @@ "event": "$ref:Event" } }, + "Integrations.IOConnectionAdded": { + "description": "Emitted whenever a IO connection is added.", + "params": { + "ioConnection": "$ref:IOConnection" + } + }, + "Integrations.IOConnectionRemoved": { + "description": "Emitted whenever a IO connection is removed.", + "params": { + "ioConnectionId": "Uuid" + } + }, "Integrations.PluginConfigurationChanged": { "description": "Emitted whenever a plugin's configuration is changed.", "params": { @@ -2402,6 +2451,16 @@ "name": "String", "version": "String" }, + "IOConnection": { + "r:id": "Uuid", + "r:inputStateTypeId": "Uuid", + "r:inputThingId": "Uuid", + "r:outputStateTypeId": "Uuid", + "r:outputThingId": "Uuid" + }, + "IOConnections": [ + "$ref:IOConnection" + ], "IntegrationPlugin": { "r:displayName": "String", "r:id": "Uuid", @@ -2586,6 +2645,7 @@ "displayName": "String", "index": "Int", "name": "String", + "o:ioType": "$ref:IOType", "o:maxValue": "Variant", "o:minValue": "Variant", "o:possibleValues": [ diff --git a/tests/auto/integrations/testintegrations.cpp b/tests/auto/integrations/testintegrations.cpp index 801b5a9b..0f82a748 100644 --- a/tests/auto/integrations/testintegrations.cpp +++ b/tests/auto/integrations/testintegrations.cpp @@ -287,8 +287,8 @@ void TestIntegrations::getThingClasses_data() QTest::addColumn("vendorId"); QTest::addColumn("resultCount"); - QTest::newRow("vendor nymea") << nymeaVendorId << 11; - QTest::newRow("no filter") << VendorId() << 11; + QTest::newRow("vendor nymea") << nymeaVendorId << 14; + QTest::newRow("no filter") << VendorId() << 14; QTest::newRow("invalid vendor") << VendorId("93e7d361-8025-4354-b17e-b68406c800bc") << 0; }