diff --git a/libnymea-core/integrations/thingmanagerimplementation.cpp b/libnymea-core/integrations/thingmanagerimplementation.cpp index 8bba515a..3516c998 100644 --- a/libnymea-core/integrations/thingmanagerimplementation.cpp +++ b/libnymea-core/integrations/thingmanagerimplementation.cpp @@ -928,51 +928,61 @@ IOConnections ThingManagerImplementation::ioConnections(const ThingId &thingId) return ioConnections; } -Thing::ThingError ThingManagerImplementation::connectIO(const IOConnection &connection) +IOConnectionResult ThingManagerImplementation::connectIO(const IOConnection &connection) { + IOConnectionResult result; + // 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; + qCWarning(dcThingManager()) << "Could not find inputThing" << connection.inputThingId() << "in configured things. Not adding IO connection."; + result.error = Thing::ThingErrorThingNotFound; + return result; } 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; + result.error = Thing::ThingErrorStateTypeNotFound; + return result; } 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; + result.error = Thing::ThingErrorInvalidParameter; + return result; } 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; + qCWarning(dcThingManager()) << "Could not find outputThing" << connection.outputThingId() << "in configured things. Not adding IO connection."; + result.error = Thing::ThingErrorThingNotFound; + return result; } 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; + result.error = Thing::ThingErrorStateTypeNotFound; + return result; } 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; + result.error = Thing::ThingErrorInvalidParameter; + return result; } // 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; + result.error = Thing::ThingErrorInvalidParameter; + return result; } 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; + result.error = Thing::ThingErrorInvalidParameter; + return result; } // Check if either input or output is already connected @@ -994,7 +1004,10 @@ Thing::ThingError ThingManagerImplementation::connectIO(const IOConnection &conn storeIOConnections(); emit ioConnectionAdded(connection); - return Thing::ThingErrorNoError; + + result.error = Thing::ThingErrorNoError; + result.ioConnectionId = connection.id(); + return result; } Thing::ThingError ThingManagerImplementation::disconnectIO(const IOConnectionId &ioConnectionId) diff --git a/libnymea-core/integrations/thingmanagerimplementation.h b/libnymea-core/integrations/thingmanagerimplementation.h index 8cb343ef..8f579f8b 100644 --- a/libnymea-core/integrations/thingmanagerimplementation.h +++ b/libnymea-core/integrations/thingmanagerimplementation.h @@ -115,7 +115,7 @@ public: BrowserItemActionInfo *executeBrowserItemAction(const BrowserItemAction &browserItemAction) override; IOConnections ioConnections(const ThingId &thingId = ThingId()) const override; - Thing::ThingError connectIO(const IOConnection &connection) override; + IOConnectionResult connectIO(const IOConnection &connection) override; Thing::ThingError disconnectIO(const IOConnectionId &ioConnectionId) override; QString translate(const PluginId &pluginId, const QString &string, const QLocale &locale) override; diff --git a/libnymea-core/jsonrpc/integrationshandler.cpp b/libnymea-core/jsonrpc/integrationshandler.cpp index 0aa6b400..bab65155 100644 --- a/libnymea-core/jsonrpc/integrationshandler.cpp +++ b/libnymea-core/jsonrpc/integrationshandler.cpp @@ -355,12 +355,14 @@ IntegrationsHandler::IntegrationsHandler(ThingManager *thingManager, QObject *pa registerMethod("GetIOConnections", description, params, returns); params.clear(); returns.clear(); - description = "Connect two generic IO states."; + description = "Connect two generic IO states. Input and output need to be compatible, that is, either a digital input " + "and a digital output, or an analog input and an analog output. If successful, the connectionId will be returned."; params.insert("inputThingId", enumValueName(Uuid)); params.insert("inputStateTypeId", enumValueName(Uuid)); params.insert("outputThingId", enumValueName(Uuid)); params.insert("outputStateTypeId", enumValueName(Uuid)); returns.insert("thingError", enumRef()); + returns.insert("o:ioConnectionId", enumValueName(Uuid)); registerMethod("ConnectIO", description, params, returns); params.clear(); returns.clear(); @@ -990,8 +992,12 @@ JsonReply *IntegrationsHandler::ConnectIO(const QVariantMap ¶ms) 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)); + IOConnectionResult result = m_thingManager->connectIO(inputThingId, inputStateTypeId, outputThingId, outputStateTypeId); + QVariantMap reply = statusToReply(result.error); + if (result.error == Thing::ThingErrorNoError) { + reply.insert("ioConnectionId", result.ioConnectionId); + } + return createReply(reply); } JsonReply *IntegrationsHandler::DisconnectIO(const QVariantMap ¶ms) diff --git a/libnymea/integrations/ioconnection.h b/libnymea/integrations/ioconnection.h index 144abc89..207cbd71 100644 --- a/libnymea/integrations/ioconnection.h +++ b/libnymea/integrations/ioconnection.h @@ -6,6 +6,12 @@ #include #include "typeutils.h" +#include "thing.h" + +struct IOConnectionResult { + Thing::ThingError error = Thing::ThingErrorNoError; + IOConnectionId ioConnectionId; +}; class IOConnection { diff --git a/libnymea/integrations/thingmanager.cpp b/libnymea/integrations/thingmanager.cpp index 21751eb5..53d41a47 100644 --- a/libnymea/integrations/thingmanager.cpp +++ b/libnymea/integrations/thingmanager.cpp @@ -51,7 +51,7 @@ ThingManager::ThingManager(QObject *parent) : QObject(parent) qRegisterMetaType(); } -Thing::ThingError ThingManager::connectIO(const ThingId &inputThing, const StateTypeId &inputState, const ThingId &outputThing, const StateTypeId &outputState) +IOConnectionResult 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 7c1dc417..0b234591 100644 --- a/libnymea/integrations/thingmanager.h +++ b/libnymea/integrations/thingmanager.h @@ -91,7 +91,7 @@ public: 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); + IOConnectionResult 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; @@ -100,7 +100,7 @@ public: virtual Vendor translateVendor(const Vendor &vendor, const QLocale &locale) = 0; protected: - virtual Thing::ThingError connectIO(const IOConnection &connection) = 0; + virtual IOConnectionResult connectIO(const IOConnection &connection) = 0; signals: void pluginConfigChanged(const PluginId &id, const ParamList &config); diff --git a/plugins/mock/extern-plugininfo.h b/plugins/mock/extern-plugininfo.h index aca86972..3b92d668 100644 --- a/plugins/mock/extern-plugininfo.h +++ b/plugins/mock/extern-plugininfo.h @@ -290,10 +290,13 @@ extern ParamTypeId virtualIoLightMockPowerActionPowerParamTypeId; extern ThingClassId virtualIoTemperatureSensorMockThingClassId; extern ParamTypeId virtualIoTemperatureSensorMockSettingsMinTempParamTypeId; extern ParamTypeId virtualIoTemperatureSensorMockSettingsMaxTempParamTypeId; +extern StateTypeId virtualIoTemperatureSensorMockInputStateTypeId; extern StateTypeId virtualIoTemperatureSensorMockTemperatureStateTypeId; +extern EventTypeId virtualIoTemperatureSensorMockInputEventTypeId; +extern ParamTypeId virtualIoTemperatureSensorMockInputEventInputParamTypeId; extern EventTypeId virtualIoTemperatureSensorMockTemperatureEventTypeId; extern ParamTypeId virtualIoTemperatureSensorMockTemperatureEventTemperatureParamTypeId; -extern ActionTypeId virtualIoTemperatureSensorMockTemperatureActionTypeId; -extern ParamTypeId virtualIoTemperatureSensorMockTemperatureActionTemperatureParamTypeId; +extern ActionTypeId virtualIoTemperatureSensorMockInputActionTypeId; +extern ParamTypeId virtualIoTemperatureSensorMockInputActionInputParamTypeId; #endif // EXTERNPLUGININFO_H diff --git a/plugins/mock/integrationpluginmock.cpp b/plugins/mock/integrationpluginmock.cpp index bbf0ff8b..409cb268 100644 --- a/plugins/mock/integrationpluginmock.cpp +++ b/plugins/mock/integrationpluginmock.cpp @@ -748,11 +748,12 @@ void IntegrationPluginMock::executeAction(ThingActionInfo *info) } if (info->thing()->thingClassId() == virtualIoTemperatureSensorMockThingClassId) { - if (info->action().actionTypeId() == virtualIoTemperatureSensorMockTemperatureActionTypeId) { + if (info->action().actionTypeId() == virtualIoTemperatureSensorMockInputActionTypeId) { double minTemp = info->thing()->setting(virtualIoTemperatureSensorMockSettingsMinTempParamTypeId).toDouble(); double maxTemp = info->thing()->setting(virtualIoTemperatureSensorMockSettingsMaxTempParamTypeId).toDouble(); - double value = info->action().param(virtualIoTemperatureSensorMockTemperatureActionTemperatureParamTypeId).value().toDouble(); + double value = info->action().param(virtualIoTemperatureSensorMockInputActionInputParamTypeId).value().toDouble(); double temp = minTemp + (maxTemp - minTemp) * value; + qCDebug(dcMock()) << "Min:" << minTemp << "Max:" << maxTemp << "value:" << value << "temp:" << temp; info->thing()->setStateValue(virtualIoTemperatureSensorMockTemperatureStateTypeId, temp); info->finish(Thing::ThingErrorNoError); return; diff --git a/plugins/mock/integrationpluginmock.json b/plugins/mock/integrationpluginmock.json index 0931a01e..cd67c5e0 100644 --- a/plugins/mock/integrationpluginmock.json +++ b/plugins/mock/integrationpluginmock.json @@ -1016,18 +1016,26 @@ ], "stateTypes": [ { - "id": "db9cc518-1012-47e2-8212-6e616fed07a6", - "name": "temperature", - "displayName": "Temperature", - "displayNameEvent": "Temperature changed", - "displayNameAction": "Set temperature", + "id": "fd341f72-6d9a-4812-9f66-47197c48a935", + "name": "input", + "displayName": "Input", + "displayNameEvent": "Input changed", + "displayNameAction": "Set input", "type": "double", - "unit": "DegreeCelsius", "defaultValue": 0, "ioType": "analogOutput", "writable": true, "minValue": 0, "maxValue": 1 + }, + { + "id": "db9cc518-1012-47e2-8212-6e616fed07a6", + "name": "temperature", + "displayName": "Temperature", + "displayNameEvent": "Temperature changed", + "type": "double", + "unit": "DegreeCelsius", + "defaultValue": 0 } ] } diff --git a/plugins/mock/plugininfo.h b/plugins/mock/plugininfo.h index 9f4c7dad..ef097a61 100644 --- a/plugins/mock/plugininfo.h +++ b/plugins/mock/plugininfo.h @@ -294,11 +294,14 @@ ParamTypeId virtualIoLightMockPowerActionPowerParamTypeId = ParamTypeId("{d1917b 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 virtualIoTemperatureSensorMockInputStateTypeId = StateTypeId("{fd341f72-6d9a-4812-9f66-47197c48a935}"); StateTypeId virtualIoTemperatureSensorMockTemperatureStateTypeId = StateTypeId("{db9cc518-1012-47e2-8212-6e616fed07a6}"); +EventTypeId virtualIoTemperatureSensorMockInputEventTypeId = EventTypeId("{fd341f72-6d9a-4812-9f66-47197c48a935}"); +ParamTypeId virtualIoTemperatureSensorMockInputEventInputParamTypeId = ParamTypeId("{fd341f72-6d9a-4812-9f66-47197c48a935}"); 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}"); +ActionTypeId virtualIoTemperatureSensorMockInputActionTypeId = ActionTypeId("{fd341f72-6d9a-4812-9f66-47197c48a935}"); +ParamTypeId virtualIoTemperatureSensorMockInputActionInputParamTypeId = ParamTypeId("{fd341f72-6d9a-4812-9f66-47197c48a935}"); const QString translations[] { //: The name of the Browser Item ActionType ({00b8f0a8-99ca-4aa4-833d-59eb8d4d6de3}) of ThingClass mock @@ -478,6 +481,18 @@ const QString translations[] { //: The name of the ParamType (ThingClass: inputTypeMock, Type: thing, ID: {43bf3832-dd48-4090-a836-656e8b60216e}) QT_TRANSLATE_NOOP("mock", "IPv6 address"), + //: The name of the ParamType (ThingClass: virtualIoTemperatureSensorMock, ActionType: input, ID: {fd341f72-6d9a-4812-9f66-47197c48a935}) + QT_TRANSLATE_NOOP("mock", "Input"), + + //: The name of the ParamType (ThingClass: virtualIoTemperatureSensorMock, EventType: input, ID: {fd341f72-6d9a-4812-9f66-47197c48a935}) + QT_TRANSLATE_NOOP("mock", "Input"), + + //: The name of the StateType ({fd341f72-6d9a-4812-9f66-47197c48a935}) of ThingClass virtualIoTemperatureSensorMock + QT_TRANSLATE_NOOP("mock", "Input"), + + //: The name of the EventType ({fd341f72-6d9a-4812-9f66-47197c48a935}) of ThingClass virtualIoTemperatureSensorMock + QT_TRANSLATE_NOOP("mock", "Input changed"), + //: The name of the ParamType (ThingClass: inputTypeMock, EventType: int, ID: {d0fc56ae-5791-4e91-b76c-dadfbc7e7dbb}) QT_TRANSLATE_NOOP("mock", "Int"), @@ -679,6 +694,9 @@ const QString translations[] { //: The name of the ActionType ({53cd7c55-49b7-441b-b970-9048f20f0e2c}) of ThingClass pushButtonMock QT_TRANSLATE_NOOP("mock", "Set double value"), + //: The name of the ActionType ({fd341f72-6d9a-4812-9f66-47197c48a935}) of ThingClass virtualIoTemperatureSensorMock + QT_TRANSLATE_NOOP("mock", "Set input"), + //: The name of the ActionType ({527f0687-0b28-4c26-852c-25b8f83e4797}) of ThingClass displayPinMock QT_TRANSLATE_NOOP("mock", "Set percentage"), @@ -688,9 +706,6 @@ const QString translations[] { //: 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"), @@ -703,9 +718,6 @@ 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"), diff --git a/tests/auto/api.json b/tests/auto/api.json index b3a4757c..3785c079 100644 --- a/tests/auto/api.json +++ b/tests/auto/api.json @@ -945,7 +945,7 @@ } }, "Integrations.ConnectIO": { - "description": "Connect two generic IO states.", + "description": "Connect two generic IO states. Input and output need to be compatible, that is, either a digital input and a digital output, or an analog input and an analog output. If successful, the connectionId will be returned.", "params": { "inputStateTypeId": "Uuid", "inputThingId": "Uuid", @@ -953,6 +953,7 @@ "outputThingId": "Uuid" }, "returns": { + "o:ioConnectionId": "Uuid", "thingError": "$ref:ThingError" } }, diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro index 3c5e589b..c6431395 100644 --- a/tests/auto/auto.pro +++ b/tests/auto/auto.pro @@ -1,26 +1,27 @@ TEMPLATE = subdirs SUBDIRS = \ - versioning \ - devices \ - integrations \ - jsonrpc \ - events \ - states \ actions \ - rules \ - plugins \ - webserver \ - websocketserver \ + configurations \ + devices \ + events \ + integrations \ + ioconnections \ + jsonrpc \ logging \ loggingdirect \ loggingloading \ - #coap \ # temporary removed until fixed - configurations \ + mqttbroker \ + plugins \ + rules \ + scripts \ + states \ + tags \ timemanager \ userloading \ usermanager \ - mqttbroker \ - tags \ - scripts \ + versioning \ + webserver \ + websocketserver \ + #coap \ # temporary removed until fixed diff --git a/tests/auto/ioconnections/ioconnections.pro b/tests/auto/ioconnections/ioconnections.pro new file mode 100644 index 00000000..0ea01980 --- /dev/null +++ b/tests/auto/ioconnections/ioconnections.pro @@ -0,0 +1,5 @@ +include(../../../nymea.pri) +include(../autotests.pri) + +TARGET = testioconnections +SOURCES += testioconnections.cpp diff --git a/tests/auto/ioconnections/testioconnections.cpp b/tests/auto/ioconnections/testioconnections.cpp new file mode 100644 index 00000000..1bda1c73 --- /dev/null +++ b/tests/auto/ioconnections/testioconnections.cpp @@ -0,0 +1,299 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free Software +* Foundation, GNU version 3. This project is distributed in the hope that it +* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +* Public License for more details. +* +* You should have received a copy of the GNU General Public License along with +* this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "nymeatestbase.h" +#include "nymeacore.h" +#include "nymeasettings.h" + +#include "integrations/thingdiscoveryinfo.h" +#include "integrations/thingsetupinfo.h" + +#include "servers/mocktcpserver.h" +#include "jsonrpc/integrationshandler.h" + +using namespace nymeaserver; + +class TestIOConnections : public NymeaTestBase +{ + Q_OBJECT + +private: + ThingId m_ioThingId; + ThingId m_lightThingId; + ThingId m_tempSensorThingId; + + inline void verifyThingError(const QVariant &response, Thing::ThingError error = Thing::ThingErrorNoError) { + verifyError(response, "thingError", enumValueName(error)); + } + +private slots: + + void initTestCase(); + + void testConnectionCompatibility_data(); + void testConnectionCompatibility(); + + void testDigitalIO(); + + void testAnalogIO(); +}; + +void TestIOConnections::initTestCase() +{ + NymeaTestBase::initTestCase(); + QLoggingCategory::setFilterRules("*.debug=false\n" + "Tests.debug=true\n" + "Mock.debug=true\n" + ); + + // Adding generic IO mock + QVariantMap params; + params.insert("thingClassId", genericIoMockThingClassId); + params.insert("name", "Generic IO mock"); + QVariant response = injectAndWait("Integrations.AddThing", params); + m_ioThingId = ThingId(response.toMap().value("params").toMap().value("thingId").toString()); + QVERIFY2(!m_ioThingId.isNull(), "Creating generic IO mock failed"); + qCDebug(dcTests()) << "Created IO mock with ID" << m_ioThingId; + + // Adding virtual light (digital input) + params.clear(); + params.insert("thingClassId", virtualIoLightMockThingClassId); + params.insert("name", "light"); + response = injectAndWait("Integrations.AddThing", params); + m_lightThingId = ThingId(response.toMap().value("params").toMap().value("thingId").toUuid()); + QVERIFY2(!m_lightThingId.isNull(), "Creating virtual light failed"); + + // Adding virtual temp sensor (analog output) + params.clear(); + params.insert("thingClassId", virtualIoTemperatureSensorMockThingClassId); + params.insert("name", "temp sensor"); + response = injectAndWait("Integrations.AddThing", params); + m_tempSensorThingId = ThingId(response.toMap().value("params").toMap().value("thingId").toUuid()); + QVERIFY2(!m_tempSensorThingId.isNull(), "Creating virtual temp sensor failed"); +} + +void TestIOConnections::testConnectionCompatibility_data() +{ + QTest::addColumn("inputThingId"); + QTest::addColumn("inputStateTypeId"); + QTest::addColumn("outputThingId"); + QTest::addColumn("outputStateTypeId"); + QTest::addColumn("expectedError"); + + QTest::newRow("digital in, digital in") << m_ioThingId << genericIoMockDigitalInput1StateTypeId << m_ioThingId << genericIoMockDigitalInput1StateTypeId << Thing::ThingErrorInvalidParameter; + QTest::newRow("digital in, digital out") << m_ioThingId << genericIoMockDigitalInput1StateTypeId << m_ioThingId << genericIoMockDigitalOutput1StateTypeId << Thing::ThingErrorNoError; + QTest::newRow("digital in, analog in") << m_ioThingId << genericIoMockDigitalInput1StateTypeId << m_ioThingId << genericIoMockAnalogInput1StateTypeId << Thing::ThingErrorInvalidParameter; + QTest::newRow("digital in, analog out") << m_ioThingId << genericIoMockDigitalInput1StateTypeId << m_ioThingId << genericIoMockAnalogOutput1StateTypeId << Thing::ThingErrorInvalidParameter; + + QTest::newRow("digital out, digital in") << m_ioThingId << genericIoMockDigitalOutput1StateTypeId << m_ioThingId << genericIoMockDigitalInput1StateTypeId << Thing::ThingErrorInvalidParameter; + QTest::newRow("digital out, digital out") << m_ioThingId << genericIoMockDigitalOutput1StateTypeId << m_ioThingId << genericIoMockDigitalOutput1StateTypeId << Thing::ThingErrorInvalidParameter; + QTest::newRow("digital out, analog in") << m_ioThingId << genericIoMockDigitalOutput1StateTypeId << m_ioThingId << genericIoMockAnalogInput1StateTypeId << Thing::ThingErrorInvalidParameter; + QTest::newRow("digital out, analot out") << m_ioThingId << genericIoMockDigitalOutput1StateTypeId << m_ioThingId << genericIoMockAnalogOutput1StateTypeId << Thing::ThingErrorInvalidParameter; + + QTest::newRow("analog in, digital in") << m_ioThingId << genericIoMockAnalogInput1StateTypeId << m_ioThingId << genericIoMockDigitalInput1StateTypeId << Thing::ThingErrorInvalidParameter; + QTest::newRow("analog in, digital out") << m_ioThingId << genericIoMockAnalogInput1StateTypeId << m_ioThingId << genericIoMockDigitalOutput1StateTypeId << Thing::ThingErrorInvalidParameter; + QTest::newRow("analog in, analog in") << m_ioThingId << genericIoMockAnalogInput1StateTypeId << m_ioThingId << genericIoMockAnalogInput1StateTypeId << Thing::ThingErrorInvalidParameter; + QTest::newRow("analog in, analog out") << m_ioThingId << genericIoMockAnalogInput1StateTypeId << m_ioThingId << genericIoMockAnalogOutput1StateTypeId << Thing::ThingErrorNoError; + + QTest::newRow("analog out, digital in") << m_ioThingId << genericIoMockAnalogOutput1StateTypeId << m_ioThingId << genericIoMockDigitalInput1StateTypeId << Thing::ThingErrorInvalidParameter; + QTest::newRow("analog out, digital out") << m_ioThingId << genericIoMockAnalogOutput1StateTypeId << m_ioThingId << genericIoMockDigitalOutput1StateTypeId << Thing::ThingErrorInvalidParameter; + QTest::newRow("analog out, analog in") << m_ioThingId << genericIoMockAnalogOutput1StateTypeId << m_ioThingId << genericIoMockAnalogInput1StateTypeId << Thing::ThingErrorInvalidParameter; + QTest::newRow("analog out, analog out") << m_ioThingId << genericIoMockAnalogOutput1StateTypeId << m_ioThingId << genericIoMockAnalogOutput1StateTypeId << Thing::ThingErrorInvalidParameter; + + QTest::newRow("valid input, invalid output thing") << m_ioThingId << genericIoMockDigitalInput1StateTypeId << ThingId("707d5093-4915-499e-8e69-10c11972bb34") << genericIoMockDigitalOutput1StateTypeId << Thing::ThingErrorThingNotFound; + QTest::newRow("valid input, invalid output stateType") << m_ioThingId << genericIoMockDigitalInput1StateTypeId << m_ioThingId << StateTypeId("51534cd7-8adf-4bdc-a4c1-042d0a9d4faa") << Thing::ThingErrorStateTypeNotFound; + QTest::newRow("invalid input thing, valid output") << ThingId("99843693-8615-416a-8e59-a47b050f5c1a") << genericIoMockDigitalInput1StateTypeId << m_ioThingId << genericIoMockDigitalOutput1StateTypeId << Thing::ThingErrorThingNotFound; + QTest::newRow("invalid input stateType, valid output") << m_ioThingId << StateTypeId("04657948-e349-4f43-bf20-13f9986ad1b4") << m_ioThingId << genericIoMockDigitalOutput1StateTypeId << Thing::ThingErrorStateTypeNotFound; +} + +void TestIOConnections::testConnectionCompatibility() +{ + QFETCH(ThingId, inputThingId); + QFETCH(StateTypeId, inputStateTypeId); + QFETCH(ThingId, outputThingId); + QFETCH(StateTypeId, outputStateTypeId); + QFETCH(Thing::ThingError, expectedError); + + QVariantMap params; + params.insert("inputThingId", inputThingId); + params.insert("inputStateTypeId", inputStateTypeId); + params.insert("outputThingId", outputThingId); + params.insert("outputStateTypeId", outputStateTypeId); + QVariant response = injectAndWait("Integrations.ConnectIO", params); + verifyThingError(response, expectedError); + +} + +void TestIOConnections::testDigitalIO() +{ + QVariantMap params; + params.insert("inputThingId", m_lightThingId); + params.insert("inputStateTypeId", virtualIoLightMockPowerStateTypeId); + params.insert("outputThingId", m_ioThingId); + params.insert("outputStateTypeId", genericIoMockDigitalOutput1StateTypeId); + QVariant response = injectAndWait("Integrations.ConnectIO", params); + verifyThingError(response); + IOConnectionId ioConnectionId = response.toMap().value("params").toMap().value("ioConnectionId").toUuid(); + + // verify both, input and out are off + params.clear(); + params.insert("thingId", m_lightThingId); + params.insert("stateTypeId", virtualIoLightMockPowerStateTypeId); + response = injectAndWait("Integrations.GetStateValue", params); + verifyThingError(response); + QVERIFY2(response.toMap().value("params").toMap().value("value").toBool() == false, "Light isn't turned off"); + + params.clear(); + params.insert("thingId", m_ioThingId); + params.insert("stateTypeId", genericIoMockDigitalOutput1StateTypeId); + response = injectAndWait("Integrations.GetStateValue", params); + verifyThingError(response); + QVERIFY2(response.toMap().value("params").toMap().value("value").toBool() == false, "Digital output isn't turned off"); + + // Turn on light and verify digital output went on + params.clear(); + params.insert("thingId", m_lightThingId); + params.insert("actionTypeId", virtualIoLightMockPowerActionTypeId); + QVariantMap actionParam; + actionParam.insert("paramTypeId", virtualIoLightMockPowerActionPowerParamTypeId); + actionParam.insert("value", true); + params.insert("params", QVariantList() << actionParam); + response = injectAndWait("Integrations.ExecuteAction", params); + verifyThingError(response); + + params.clear(); + params.insert("thingId", m_ioThingId); + params.insert("stateTypeId", genericIoMockDigitalOutput1StateTypeId); + response = injectAndWait("Integrations.GetStateValue", params); + verifyThingError(response); + QVERIFY2(response.toMap().value("params").toMap().value("value").toBool() == true, "Digital output isn't turned on"); + + // Disconnect IO again + params.clear(); + params.insert("ioConnectionId", ioConnectionId); + response = injectAndWait("Integrations.DisconnectIO", params); + verifyThingError(response); + + // Turn off the light and verify digital output is still on + params.clear(); + params.insert("thingId", m_lightThingId); + params.insert("actionTypeId", virtualIoLightMockPowerActionTypeId); + actionParam.clear(); + actionParam.insert("paramTypeId", virtualIoLightMockPowerActionPowerParamTypeId); + actionParam.insert("value", false); + params.insert("params", QVariantList() << actionParam); + response = injectAndWait("Integrations.ExecuteAction", params); + verifyThingError(response); + + params.clear(); + params.insert("thingId", m_ioThingId); + params.insert("stateTypeId", genericIoMockDigitalOutput1StateTypeId); + response = injectAndWait("Integrations.GetStateValue", params); + verifyThingError(response); + QVERIFY2(response.toMap().value("params").toMap().value("value").toBool() == true, "Digital output turned off while it should not"); +} + +void TestIOConnections::testAnalogIO() +{ + QVariantMap params; + params.insert("inputThingId", m_ioThingId); + params.insert("inputStateTypeId", genericIoMockAnalogInput1StateTypeId); + params.insert("outputThingId", m_tempSensorThingId); + params.insert("outputStateTypeId", virtualIoTemperatureSensorMockInputStateTypeId); + QVariant response = injectAndWait("Integrations.ConnectIO", params); + verifyThingError(response); + IOConnectionId ioConnectionId = response.toMap().value("params").toMap().value("ioConnectionId").toUuid(); + + // verify input is at 0, temp at 0 too + params.clear(); + params.insert("thingId", m_ioThingId); + params.insert("stateTypeId", genericIoMockAnalogInput1StateTypeId); + response = injectAndWait("Integrations.GetStateValue", params); + verifyThingError(response); + QVERIFY2(qFuzzyCompare(response.toMap().value("params").toMap().value("value").toDouble(), 0), "Input isn't at 0"); + + params.clear(); + params.insert("thingId", m_tempSensorThingId); + params.insert("stateTypeId", virtualIoTemperatureSensorMockTemperatureStateTypeId); + response = injectAndWait("Integrations.GetStateValue", params); + verifyThingError(response); + QVERIFY2(qFuzzyCompare(response.toMap().value("params").toMap().value("value").toDouble(), 0), QString("Temp sensor is not at 0 but at %1").arg(response.toMap().value("params").toMap().value("value").toDouble()).toUtf8()); + + + // set analog input to 0.5 and verify temp aligned + params.clear(); + params.insert("thingId", m_ioThingId); + params.insert("actionTypeId", genericIoMockAnalogInput1StateTypeId); + QVariantMap actionParam; + actionParam.insert("paramTypeId", genericIoMockAnalogInput1ActionAnalogInput1ParamTypeId); + actionParam.insert("value", 1.65); // goes from 0 to 3.3 + params.insert("params", QVariantList() << actionParam); + response = injectAndWait("Integrations.ExecuteAction", params); + verifyThingError(response); + + params.clear(); + params.insert("thingId", m_tempSensorThingId); + params.insert("stateTypeId", virtualIoTemperatureSensorMockTemperatureStateTypeId); + response = injectAndWait("Integrations.GetStateValue", params); + verifyThingError(response); + // generic IO output goes from 0 to 3.3. We're setting 1.65V which 50% + // temp goes from -20 to 50. A input of 1.65 should output a temperature of 15°C + double expectedTemp = 70.0 / 2 - 20; + QVERIFY2(qFuzzyCompare(response.toMap().value("params").toMap().value("value").toDouble(), expectedTemp), QString("Temp sensor is not at %1 but at %2").arg(expectedTemp).arg(response.toMap().value("params").toMap().value("value").toDouble()).toUtf8()); + + // Disconnect IO again + params.clear(); + params.insert("ioConnectionId", ioConnectionId); + response = injectAndWait("Integrations.DisconnectIO", params); + verifyThingError(response); + + // set analog input to 3 and verify temp is still at the old expectedTemp + params.clear(); + params.insert("thingId", m_ioThingId); + params.insert("actionTypeId", genericIoMockAnalogInput1StateTypeId); + actionParam.clear(); + actionParam.insert("paramTypeId", genericIoMockAnalogInput1ActionAnalogInput1ParamTypeId); + actionParam.insert("value", 3); // goes from 0 to 3.3 + params.insert("params", QVariantList() << actionParam); + response = injectAndWait("Integrations.ExecuteAction", params); + verifyThingError(response); + + params.clear(); + params.insert("thingId", m_tempSensorThingId); + params.insert("stateTypeId", virtualIoTemperatureSensorMockTemperatureStateTypeId); + response = injectAndWait("Integrations.GetStateValue", params); + verifyThingError(response); + QVERIFY2(qFuzzyCompare(response.toMap().value("params").toMap().value("value").toDouble(), expectedTemp), QString("Temp sensor is not at %1 but at %2").arg(expectedTemp).arg(response.toMap().value("params").toMap().value("value").toDouble()).toUtf8()); + +} + +#include "testioconnections.moc" +QTEST_MAIN(TestIOConnections) +