diff --git a/libnymea-core/debugserverhandler.cpp b/libnymea-core/debugserverhandler.cpp index 48222228..a402ec2d 100644 --- a/libnymea-core/debugserverhandler.cpp +++ b/libnymea-core/debugserverhandler.cpp @@ -343,6 +343,35 @@ HttpReply *DebugServerHandler::processDebugRequest(const QString &requestPath, c return reply; } + if (requestPath.startsWith("/debug/settings/ioconnections")) { + QString settingsFileName = NymeaSettings(NymeaSettings::SettingsRoleIOConnections).fileName(); + qCDebug(dcDebugServer()) << "Loading" << settingsFileName; + QFile settingsFile(settingsFileName); + if (!settingsFile.exists()) { + qCWarning(dcDebugServer()) << "Could not read file for debug download" << settingsFileName << "file does not exist."; + HttpReply *reply = HttpReply::createErrorReply(HttpReply::NotFound); + reply->setHeader(HttpReply::ContentTypeHeader, "text/html"); + reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, tr("Could not find file \"%1\".").arg(settingsFileName))); + return reply; + } + + if (!settingsFile.open(QFile::ReadOnly)) { + qCWarning(dcDebugServer()) << "Could not read file for debug download" << settingsFileName; + HttpReply *reply = HttpReply::createErrorReply(HttpReply::Forbidden); + reply->setHeader(HttpReply::ContentTypeHeader, "text/html"); + reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, tr("Could not open file \"%1\".").arg(settingsFileName))); + return reply; + } + + QByteArray settingsFileData = settingsFile.readAll(); + settingsFile.close(); + + HttpReply *reply = HttpReply::createSuccessReply(); + reply->setHeader(HttpReply::ContentTypeHeader, "text/plain"); + reply->setPayload(settingsFileData); + return reply; + } + if (requestPath.startsWith("/debug/ping")) { // Only one ping process should run if (m_pingProcess || m_pingReply) @@ -1608,6 +1637,56 @@ QByteArray DebugServerHandler::createDebugXmlDocument() writer.writeEndElement(); // div download-row + + // Download row IO connections + writer.writeStartElement("div"); + writer.writeAttribute("class", "download-row"); + + writer.writeStartElement("div"); + writer.writeAttribute("class", "download-name-column"); + //: The MQTT policies download description of the debug interface + writer.writeTextElement("p", tr("IO Connections")); + writer.writeEndElement(); // div download-name-column + + writer.writeStartElement("div"); + writer.writeAttribute("class", "download-path-column"); + writer.writeTextElement("p", NymeaSettings(NymeaSettings::SettingsRoleIOConnections).fileName()); + writer.writeEndElement(); // div download-path-column + + writer.writeStartElement("div"); + writer.writeAttribute("class", "download-button-column"); + writer.writeStartElement("form"); + writer.writeAttribute("class", "download-button"); + writer.writeStartElement("button"); + writer.writeAttribute("class", "button"); + writer.writeAttribute("type", "button"); + if (!QFile::exists(NymeaSettings(NymeaSettings::SettingsRoleMqttPolicies).fileName())) { + writer.writeAttribute("disabled", "true"); + } + writer.writeAttribute("onClick", "downloadFile('/debug/settings/ioconnections', 'ioconnections.conf')"); + writer.writeCharacters(tr("Download")); + writer.writeEndElement(); // button + writer.writeEndElement(); // form + writer.writeEndElement(); // div download-button-column + + writer.writeStartElement("div"); + writer.writeAttribute("class", "show-button-column"); + writer.writeStartElement("form"); + writer.writeAttribute("class", "show-button"); + writer.writeStartElement("button"); + writer.writeAttribute("class", "button"); + writer.writeAttribute("type", "button"); + if (!QFile::exists(NymeaSettings(NymeaSettings::SettingsRoleMqttPolicies).fileName())) { + writer.writeAttribute("disabled", "true"); + } + writer.writeAttribute("onClick", "showFile('/debug/settings/ioconnections')"); + writer.writeCharacters(tr("Show")); + writer.writeEndElement(); // button + writer.writeEndElement(); // form + writer.writeEndElement(); // div show-button-column + + writer.writeEndElement(); // div download-row + writer.writeEndElement(); // downloads-section diff --git a/libnymea-core/integrations/thingmanagerimplementation.cpp b/libnymea-core/integrations/thingmanagerimplementation.cpp index 568c8cd9..7ec37118 100644 --- a/libnymea-core/integrations/thingmanagerimplementation.cpp +++ b/libnymea-core/integrations/thingmanagerimplementation.cpp @@ -2039,6 +2039,12 @@ void ThingManagerImplementation::loadIOConnections() IOConnection ioConnection(id, inputThingId, inputStateTypeId, outputThingId, outputStateTypeId, inverted); m_ioConnections.insert(id, ioConnection); connectionSettings.endGroup(); + + Thing *inputThing = m_configuredThings.value(inputThingId); + if (!inputThing) { + continue; + } + syncIOConnection(inputThing, inputStateTypeId); } connectionSettings.endGroup(); } diff --git a/libnymea-core/jsonrpc/integrationshandler.cpp b/libnymea-core/jsonrpc/integrationshandler.cpp index 158c4f8c..51ac9b1f 100644 --- a/libnymea-core/jsonrpc/integrationshandler.cpp +++ b/libnymea-core/jsonrpc/integrationshandler.cpp @@ -420,7 +420,7 @@ IntegrationsHandler::IntegrationsHandler(ThingManager *thingManager, QObject *pa }); params.clear(); returns.clear(); - description = "Emitted whenever a IO connection is added."; + description = "Emitted whenever an IO connection has been added."; params.insert("ioConnection", objectRef()); registerNotification("IOConnectionAdded", description, params); connect(m_thingManager, &ThingManager::ioConnectionAdded, this, [this](const IOConnection &connection) { @@ -430,7 +430,7 @@ IntegrationsHandler::IntegrationsHandler(ThingManager *thingManager, QObject *pa }); params.clear(); returns.clear(); - description = "Emitted whenever a IO connection is removed."; + description = "Emitted whenever an IO connection has been removed."; params.insert("ioConnectionId", enumValueName(Uuid)); registerNotification("IOConnectionRemoved", description, params); connect(m_thingManager, &ThingManager::ioConnectionRemoved, this, [this](const IOConnectionId &ioConnectionId) { diff --git a/libnymea/integrations/ioconnection.cpp b/libnymea/integrations/ioconnection.cpp index b56aa53b..67219449 100644 --- a/libnymea/integrations/ioconnection.cpp +++ b/libnymea/integrations/ioconnection.cpp @@ -1,3 +1,47 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; 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 +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser 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 +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/*! + \class IOConnection + \brief The IOConnection class stores information about generic IO connections. + + \ingroup things + \inmodule libnymea + + Generic IO connections allow a user to connect states of two devices so they'll be synced whenever + those states change. + + \sa ThingManager::connectIO + +*/ + #include "ioconnection.h" IOConnection::IOConnection() @@ -5,6 +49,7 @@ IOConnection::IOConnection() } +/*! Constructs a new IOConnection object. */ IOConnection::IOConnection(const IOConnectionId &id, const ThingId &inputThing, const StateTypeId &inputState, const ThingId &outputThing, const StateTypeId &outputState, bool inverted): m_id(id), m_inputThingId(inputThing), @@ -16,31 +61,37 @@ IOConnection::IOConnection(const IOConnectionId &id, const ThingId &inputThing, } +/*! Returns the ID of this connection object. */ IOConnectionId IOConnection::id() const { return m_id; } +/*! Returns the ID of the input thing for this connection. */ ThingId IOConnection::inputThingId() const { return m_inputThingId; } +/*! Returns the input state type ID for this connection. */ StateTypeId IOConnection::inputStateTypeId() const { return m_inputStateTypeId; } +/*! Returns the ID of the output thing for this connection. */ ThingId IOConnection::outputThingId() const { return m_outputThingId; } +/*! Returns the output state type ID for this connection. */ StateTypeId IOConnection::outputStateTypeId() const { return m_outputStateTypeId; } +/*! Returns whether the connection is inverted or not. */ bool IOConnection::inverted() const { return m_inverted; diff --git a/libnymea/integrations/ioconnection.h b/libnymea/integrations/ioconnection.h index 7cb124de..92022a78 100644 --- a/libnymea/integrations/ioconnection.h +++ b/libnymea/integrations/ioconnection.h @@ -1,3 +1,33 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; 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 +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser 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 +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + #ifndef IOCONNECTION_H #define IOCONNECTION_H diff --git a/libnymea/integrations/thingmanager.cpp b/libnymea/integrations/thingmanager.cpp index c427cda3..ae02ed1a 100644 --- a/libnymea/integrations/thingmanager.cpp +++ b/libnymea/integrations/thingmanager.cpp @@ -51,6 +51,18 @@ ThingManager::ThingManager(QObject *parent) : QObject(parent) qRegisterMetaType(); } +/*! Connect two states. + When two states are connected, any state changes will be synced between those. A connection + is made from an \a inputThing and its \a inputState to an \a outputThing and its \a outputState. + Whenever the input state changes, the output state is set accordingly. If the input state is + writable, the connection will be bidirectional, that is, a change of the output state will also + reflect on the input state. + Connections can be logically inverted. + Connections need to be compatible. This means, only states which have a defined ioState of type "input" + can be connected to states which habe a defined ioState of type "output". Additionally, the digital/analog + type needs to match. In other words, states with ioType "digitalInput" can be connected to states with ioType + "digitaOutput" and states with ioType "analogInput" can be connected to states with ioType "analogOutput". + */ IOConnectionResult ThingManager::connectIO(const ThingId &inputThing, const StateTypeId &inputState, const ThingId &outputThing, const StateTypeId &outputState, bool inverted) { IOConnection connection(IOConnectionId::createIOConnectionId(), inputThing, inputState, outputThing, outputState, inverted); diff --git a/libnymea/nymeasettings.cpp b/libnymea/nymeasettings.cpp index 5b404857..f7f2324e 100644 --- a/libnymea/nymeasettings.cpp +++ b/libnymea/nymeasettings.cpp @@ -60,6 +60,10 @@ This role will create the \b{device-states.conf} file and is used to store the configured \l{Device} \l{State}{States}. \value SettingsRoleTags This role will create the \b{tags.conf} file and is used to store the \l{Tag}{Tags}. + \value SettingsRoleMqttPolicies + This role will create the \b{mqttpolicies.conf} file and is used to store the \l{MqttPolicy}{MqttPolicies}. + \value SettingsRoleIOConnections + This role will create the \b{ioconnections.conf} file and is used to store the \l{IOConnection}{IOConnections}. */ diff --git a/plugins/mock/integrationpluginmock.cpp b/plugins/mock/integrationpluginmock.cpp index 42c5cc6b..731b4135 100644 --- a/plugins/mock/integrationpluginmock.cpp +++ b/plugins/mock/integrationpluginmock.cpp @@ -720,12 +720,13 @@ void IntegrationPluginMock::executeAction(ThingActionInfo *info) if (info->thing()->thingClassId() == genericIoMockThingClassId) { if (info->action().actionTypeId() == genericIoMockDigitalOutput1ActionTypeId) { - info->thing()->setStateValue(genericIoMockDigitalOutput1StateTypeId, info->action().param(genericIoMockDigitalOutput1ActionDigitalOutput1ParamTypeId).value()); + qCDebug(dcMock()) << "Setting digital output 1 to" << info->action().param(genericIoMockDigitalOutput1ActionDigitalOutput1ParamTypeId).value().toBool(); + info->thing()->setStateValue(genericIoMockDigitalOutput1StateTypeId, info->action().param(genericIoMockDigitalOutput1ActionDigitalOutput1ParamTypeId).value().toBool()); info->finish(Thing::ThingErrorNoError); return; } if (info->action().actionTypeId() == genericIoMockDigitalOutput2ActionTypeId) { - info->thing()->setStateValue(genericIoMockDigitalOutput2StateTypeId, info->action().param(genericIoMockDigitalOutput2ActionDigitalOutput2ParamTypeId).value()); + info->thing()->setStateValue(genericIoMockDigitalOutput2StateTypeId, info->action().param(genericIoMockDigitalOutput2ActionDigitalOutput2ParamTypeId).value().toBool()); info->finish(Thing::ThingErrorNoError); return; } diff --git a/tests/auto/api.json b/tests/auto/api.json index 3b5ffff8..4645bbe9 100644 --- a/tests/auto/api.json +++ b/tests/auto/api.json @@ -2055,13 +2055,13 @@ } }, "Integrations.IOConnectionAdded": { - "description": "Emitted whenever a IO connection is added.", + "description": "Emitted whenever an IO connection has been added.", "params": { "ioConnection": "$ref:IOConnection" } }, "Integrations.IOConnectionRemoved": { - "description": "Emitted whenever a IO connection is removed.", + "description": "Emitted whenever an IO connection has been removed.", "params": { "ioConnectionId": "Uuid" }