diff --git a/libnymea-app/devicemanager.cpp b/libnymea-app/devicemanager.cpp index 44bcbbc6..ad8bd6eb 100644 --- a/libnymea-app/devicemanager.cpp +++ b/libnymea-app/devicemanager.cpp @@ -35,6 +35,7 @@ #include "types/browseritem.h" #include "thinggroup.h" #include "types/interface.h" +#include "types/ioconnections.h" #include DeviceManager::DeviceManager(JsonRpcClient* jsonclient, QObject *parent) : @@ -43,6 +44,7 @@ DeviceManager::DeviceManager(JsonRpcClient* jsonclient, QObject *parent) : m_plugins(new Plugins(this)), m_devices(new Devices(this)), m_deviceClasses(new DeviceClasses(this)), + m_ioConnections(new IOConnections(this)), m_jsonClient(jsonclient) { m_jsonClient->registerNotificationHandler(this, "notificationReceived"); @@ -54,6 +56,7 @@ void DeviceManager::clear() m_deviceClasses->clearModel(); m_vendors->clearModel(); m_plugins->clearModel(); + m_ioConnections->clearModel(); } void DeviceManager::init() @@ -86,10 +89,21 @@ void DeviceManager::init() } } + // Register a custom notification handler for the Integrations namespace for now. + if (m_jsonClient->ensureServerVersion("5.1")) { + if (!m_integrationsHandler) { + m_integrationsHandler = new IntegrationsHandler(this); + m_jsonClient->registerNotificationHandler(m_integrationsHandler, "notificationReceived"); + connect(m_integrationsHandler, &IntegrationsHandler::onNotificationReceived, this, [this](const QVariantMap ¶ms){ + notificationReceived(params); + }); + } + } m_fetchingData = true; emit fetchingDataChanged(); - m_jsonClient->sendCommand("Devices.GetPlugins", this, "getPluginsResponse"); + + m_jsonClient->sendCommand("Devices.GetSupportedDevices", this, "getSupportedDevicesResponse"); } QString DeviceManager::nameSpace() const @@ -117,6 +131,11 @@ DeviceClasses *DeviceManager::deviceClasses() const return m_deviceClasses; } +IOConnections *DeviceManager::ioConnections() const +{ + return m_ioConnections; +} + bool DeviceManager::fetchingData() const { return m_fetchingData; @@ -207,6 +226,22 @@ void DeviceManager::notificationReceived(const QVariantMap &data) } // qDebug() << "Event received" << deviceId.toString() << eventTypeId.toString(); dev->eventTriggered(eventTypeId.toString(), event.value("params").toMap()); + } else if (notification == "Integrations.IOConnectionAdded") { + QVariantMap connectionMap = data.value("params").toMap().value("ioConnection").toMap(); + QUuid id = connectionMap.value("id").toUuid(); + QUuid inputThingId = connectionMap.value("inputThingId").toUuid(); + QUuid inputStateTypeId = connectionMap.value("inputStateTypeId").toUuid(); + QUuid outputThingId = connectionMap.value("outputThingId").toUuid(); + QUuid outputStateTypeId = connectionMap.value("outputStateTypeId").toUuid(); + IOConnection *ioConnection = new IOConnection(id, inputThingId, inputStateTypeId, outputThingId, outputStateTypeId); + m_ioConnections->addIOConnection(ioConnection); + } else if (notification == "Integrations.IOConnectionRemoved") { + QUuid connectionId = data.value("params").toMap().value("ioConnectionId").toUuid(); + if (!m_ioConnections->getIOConnection(connectionId)) { + qWarning() << "Received an IO connection removed event for an IO connection we don't know."; + return; + } + m_ioConnections->removeIOConnection(connectionId); } else { qWarning() << "DeviceManager unhandled device notification received" << notification; } @@ -223,8 +258,6 @@ void DeviceManager::getVendorsResponse(const QVariantMap ¶ms) // qDebug() << "Added Vendor:" << vendor->name(); } } - - m_jsonClient->sendCommand("Devices.GetSupportedDevices", this, "getSupportedDevicesResponse"); } void DeviceManager::getSupportedDevicesResponse(const QVariantMap ¶ms) @@ -321,6 +354,10 @@ void DeviceManager::getConfiguredDevicesResponse(const QVariantMap ¶ms) } m_fetchingData = false; emit fetchingDataChanged(); + + m_jsonClient->sendCommand("Integrations.GetIOConnections", this, "getIOConnectionsResponse"); + + m_jsonClient->sendCommand("Devices.GetPlugins", this, "getPluginsResponse"); } void DeviceManager::addDeviceResponse(const QVariantMap ¶ms) @@ -675,9 +712,52 @@ int DeviceManager::executeBrowserItemAction(const QUuid &deviceId, const QString return m_jsonClient->sendCommand("Actions.ExecuteBrowserItemAction", data, this, "executeBrowserItemActionResponse"); } +int DeviceManager::connectIO(const QUuid &inputThingId, const QUuid &inputStateTypeId, const QUuid &outputThingId, const QUuid &outputStateTypeId) +{ + QVariantMap data; + data.insert("inputThingId", inputThingId); + data.insert("inputStateTypeId", inputStateTypeId); + data.insert("outputThingId", outputThingId); + data.insert("outputStateTypeId", outputStateTypeId); + return m_jsonClient->sendCommand("Integrations.ConnectIO", data, this, "connectIOResponse"); +} + +int DeviceManager::disconnectIO(const QUuid &ioConnectionId) +{ + QVariantMap data; + data.insert("ioConnectionId", ioConnectionId); + return m_jsonClient->sendCommand("Integrations.DisconnectIO", data, this, "disconnectIOResponse"); +} + void DeviceManager::executeBrowserItemActionResponse(const QVariantMap ¶ms) { qDebug() << "Execute Browser Item Action finished" << params; emit executeBrowserItemActionReply(params); } +void DeviceManager::getIOConnectionsResponse(const QVariantMap ¶ms) +{ + qDebug() << "Get IO connections response" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson()); + + foreach (const QVariant &connectionVariant, params.value("params").toMap().value("ioConnections").toList()) { + QVariantMap connectionMap = connectionVariant.toMap(); + QUuid id = connectionMap.value("id").toUuid(); + QUuid inputThingId = connectionMap.value("inputThingId").toUuid(); + QUuid inputStateTypeId = connectionMap.value("inputStateTypeId").toUuid(); + QUuid outputThingId = connectionMap.value("outputThingId").toUuid(); + QUuid outputStateTypeId = connectionMap.value("outputStateTypeId").toUuid(); + IOConnection *ioConnection = new IOConnection(id, inputThingId, inputStateTypeId, outputThingId, outputStateTypeId); + m_ioConnections->addIOConnection(ioConnection); + } +} + +void DeviceManager::connectIOResponse(const QVariantMap ¶ms) +{ + qDebug() << "ConnectIO response" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson()); +} + +void DeviceManager::disconnectIOResponse(const QVariantMap ¶ms) +{ + qDebug() << "DisconnectIO response" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson()); +} + diff --git a/libnymea-app/devicemanager.h b/libnymea-app/devicemanager.h index 638b096e..4e3bd100 100644 --- a/libnymea-app/devicemanager.h +++ b/libnymea-app/devicemanager.h @@ -43,17 +43,20 @@ class BrowserItem; class BrowserItems; -class EventHandler; class ThingGroup; class Interface; +class IOConnections; +class EventHandler; +class IntegrationsHandler; class DeviceManager : public JsonHandler { Q_OBJECT - Q_PROPERTY(Vendors *vendors READ vendors CONSTANT) - Q_PROPERTY(Plugins *plugins READ plugins CONSTANT) - Q_PROPERTY(Devices *devices READ devices CONSTANT) - Q_PROPERTY(DeviceClasses *deviceClasses READ deviceClasses CONSTANT) + Q_PROPERTY(Vendors* vendors READ vendors CONSTANT) + Q_PROPERTY(Plugins* plugins READ plugins CONSTANT) + Q_PROPERTY(Devices* devices READ devices CONSTANT) + Q_PROPERTY(DeviceClasses* deviceClasses READ deviceClasses CONSTANT) + Q_PROPERTY(IOConnections* ioConnections READ ioConnections CONSTANT) Q_PROPERTY(bool fetchingData READ fetchingData NOTIFY fetchingDataChanged) @@ -72,10 +75,11 @@ public: QString nameSpace() const override; - Vendors *vendors() const; - Plugins *plugins() const; - Devices *devices() const; - DeviceClasses *deviceClasses() const; + Vendors* vendors() const; + Plugins* plugins() const; + Devices* devices() const; + DeviceClasses* deviceClasses() const; + IOConnections* ioConnections() const; bool fetchingData() const; @@ -99,6 +103,9 @@ public: Q_INVOKABLE int executeBrowserItem(const QUuid &deviceId, const QString &itemId); Q_INVOKABLE int executeBrowserItemAction(const QUuid &deviceId, const QString &itemId, const QUuid &actionTypeId, const QVariantList ¶ms = QVariantList()); + Q_INVOKABLE int connectIO(const QUuid &inputThingId, const QUuid &inputStateTypeId, const QUuid &outputThingId, const QUuid &outputStateTypeId); + Q_INVOKABLE int disconnectIO(const QUuid &ioConnectionId); + private: Q_INVOKABLE void notificationReceived(const QVariantMap &data); Q_INVOKABLE void getVendorsResponse(const QVariantMap ¶ms); @@ -118,6 +125,9 @@ private: Q_INVOKABLE void browserItemResponse(const QVariantMap ¶ms); Q_INVOKABLE void executeBrowserItemResponse(const QVariantMap ¶ms); Q_INVOKABLE void executeBrowserItemActionResponse(const QVariantMap ¶ms); + Q_INVOKABLE void getIOConnectionsResponse(const QVariantMap ¶ms); + Q_INVOKABLE void connectIOResponse(const QVariantMap ¶ms); + Q_INVOKABLE void disconnectIOResponse(const QVariantMap ¶ms); public slots: void savePluginConfig(const QUuid &pluginId); @@ -145,6 +155,7 @@ private: Plugins *m_plugins; Devices *m_devices; DeviceClasses *m_deviceClasses; + IOConnections *m_ioConnections; bool m_fetchingData = false; @@ -158,6 +169,8 @@ private: // Deprecated stuff for nymea < 0.17 (JSONRPC < 4.0) EventHandler *m_eventHandler = nullptr; + // Register notifications for new stuff that's only available in the Integrations namespace for now + IntegrationsHandler *m_integrationsHandler = nullptr; }; @@ -181,4 +194,22 @@ private: }; +class IntegrationsHandler: public JsonHandler { + Q_OBJECT + +public: + IntegrationsHandler(QObject *parent = nullptr): JsonHandler(parent) {} + QString nameSpace() const override { + return "Integrations"; + } + +signals: + void onNotificationReceived(const QVariantMap &data); + +private: + Q_INVOKABLE void notificationReceived(const QVariantMap &data) { + emit onNotificationReceived(data); + } +}; + #endif // DEVICEMANAGER_H diff --git a/libnymea-app/devicesproxy.cpp b/libnymea-app/devicesproxy.cpp index e7deb91c..bc77975a 100644 --- a/libnymea-app/devicesproxy.cpp +++ b/libnymea-app/devicesproxy.cpp @@ -203,6 +203,66 @@ void DevicesProxy::setNameFilter(const QString &nameFilter) } } +bool DevicesProxy::showDigitalInputs() const +{ + return m_showDigitalInputs; +} + +void DevicesProxy::setShowDigitalInputs(bool showDigitalInputs) +{ + if (m_showDigitalInputs != showDigitalInputs) { + m_showDigitalInputs = showDigitalInputs; + emit showDigitalInputsChanged(); + invalidateFilter(); + emit countChanged(); + } +} + +bool DevicesProxy::showDigitalOutputs() const +{ + return m_showDigitalOutputs; +} + +void DevicesProxy::setShowDigitalOutputs(bool showDigitalOutputs) +{ + if (m_showDigitalOutputs != showDigitalOutputs) { + m_showDigitalOutputs = showDigitalOutputs; + emit showDigitalOutputsChanged(); + invalidateFilter(); + emit countChanged(); + } +} + +bool DevicesProxy::showAnalogInputs() const +{ + return m_showAnalogInputs; +} + +void DevicesProxy::setShowAnalogInputs(bool showAnalogInputs) +{ + if (m_showAnalogInputs != showAnalogInputs) { + m_showAnalogInputs = showAnalogInputs; + emit showAnalogInputsChanged(); + invalidateFilter(); + emit countChanged(); + } +} + +bool DevicesProxy::showAnalogOutputs() const +{ + return m_showDigitalOutputs; +} + +void DevicesProxy::setShowAnalogOutputs(bool showAnalogOutputs) +{ + if (m_showAnalogOutputs != showAnalogOutputs) { + m_showAnalogOutputs = showAnalogOutputs; + emit showAnalogOutputsChanged(); + invalidateFilter(); + emit countChanged(); + } +} + bool DevicesProxy::filterBatteryCritical() const { return m_filterBatteryCritical; @@ -339,6 +399,21 @@ bool DevicesProxy::filterAcceptsRow(int source_row, const QModelIndex &source_pa } } + if (m_showDigitalInputs || m_showDigitalOutputs || m_showAnalogInputs || m_showAnalogOutputs) { + if (m_showDigitalInputs && deviceClass->stateTypes()->ioStateTypes(Types::IOTypeDigitalInput).isEmpty()) { + return false; + } + if (m_showDigitalOutputs && deviceClass->stateTypes()->ioStateTypes(Types::IOTypeDigitalOutput).isEmpty()) { + return false; + } + if (m_showAnalogInputs && deviceClass->stateTypes()->ioStateTypes(Types::IOTypeAnalogInput).isEmpty()) { + return false; + } + if (m_showAnalogOutputs && deviceClass->stateTypes()->ioStateTypes(Types::IOTypeAnalogOutput).isEmpty()) { + return false; + } + } + if (m_filterBatteryCritical) { if (!deviceClass->interfaces().contains("battery") || device->stateValue(deviceClass->stateTypes()->findByName("batteryCritical")->id()).toBool() == false) { return false; diff --git a/libnymea-app/devicesproxy.h b/libnymea-app/devicesproxy.h index 9788d082..c5f11c65 100644 --- a/libnymea-app/devicesproxy.h +++ b/libnymea-app/devicesproxy.h @@ -53,6 +53,12 @@ class DevicesProxy : public QSortFilterProxyModel Q_PROPERTY(QStringList hiddenInterfaces READ hiddenInterfaces WRITE setHiddenInterfaces NOTIFY hiddenInterfacesChanged) Q_PROPERTY(QString nameFilter READ nameFilter WRITE setNameFilter NOTIFY nameFilterChanged) + // Setting one of those to true will hide those set to false. If all of those are false no IO filtering will be done + Q_PROPERTY(bool showDigitalInputs READ showDigitalInputs WRITE setShowDigitalInputs NOTIFY showDigitalInputsChanged) + Q_PROPERTY(bool showDigitalOutputs READ showDigitalOutputs WRITE setShowDigitalOutputs NOTIFY showDigitalOutputsChanged) + Q_PROPERTY(bool showAnalogInputs READ showAnalogInputs WRITE setShowAnalogInputs NOTIFY showAnalogInputsChanged) + Q_PROPERTY(bool showAnalogOutputs READ showAnalogOutputs WRITE setShowAnalogOutputs NOTIFY showAnalogOutputsChanged) + // Setting this to true will imply filtering for "battery" interface Q_PROPERTY(bool filterBatteryCritical READ filterBatteryCritical WRITE setFilterBatteryCritical NOTIFY filterBatteryCriticalChanged) @@ -91,6 +97,18 @@ public: QString nameFilter() const; void setNameFilter(const QString &nameFilter); + bool showDigitalInputs() const; + void setShowDigitalInputs(bool showDigitalInputs); + + bool showDigitalOutputs() const; + void setShowDigitalOutputs(bool showDigitalOutputs); + + bool showAnalogInputs() const; + void setShowAnalogInputs(bool showAnalogInputs); + + bool showAnalogOutputs() const; + void setShowAnalogOutputs(bool showAnalogOutputs); + bool filterBatteryCritical() const; void setFilterBatteryCritical(bool filterBatteryCritical); @@ -113,6 +131,10 @@ signals: void shownInterfacesChanged(); void hiddenInterfacesChanged(); void nameFilterChanged(); + void showDigitalInputsChanged(); + void showDigitalOutputsChanged(); + void showAnalogInputsChanged(); + void showAnalogOutputsChanged(); void filterBatteryCriticalChanged(); void filterDisconnectedChanged(); void groupByInterfaceChanged(); @@ -131,6 +153,11 @@ private: QStringList m_hiddenInterfaces; QString m_nameFilter; + bool m_showDigitalInputs = false; + bool m_showDigitalOutputs = false; + bool m_showAnalogInputs = false; + bool m_showAnalogOutputs = false; + bool m_filterBatteryCritical = false; bool m_filterDisconnected = false; diff --git a/libnymea-app/jsonrpc/jsontypes.cpp b/libnymea-app/jsonrpc/jsontypes.cpp index b7021070..61d60de6 100644 --- a/libnymea-app/jsonrpc/jsontypes.cpp +++ b/libnymea-app/jsonrpc/jsontypes.cpp @@ -189,6 +189,11 @@ StateType *JsonTypes::unpackStateType(const QVariantMap &stateTypeMap, QObject * QPair unit = stringToUnit(stateTypeMap.value("unit").toString()); stateType->setUnit(unit.first); stateType->setUnitString(unit.second); + + QMetaEnum metaEnum = QMetaEnum::fromType(); + Types::IOType ioType = static_cast(metaEnum.keyToValue(stateTypeMap.value("ioType").toByteArray())); + stateType->setIOType(ioType); + return stateType; } diff --git a/libnymea-app/libnymea-app-core.h b/libnymea-app/libnymea-app-core.h index 00d0a4fe..7890f84e 100644 --- a/libnymea-app/libnymea-app-core.h +++ b/libnymea-app/libnymea-app-core.h @@ -110,6 +110,10 @@ #include "types/tokeninfo.h" #include "types/userinfo.h" #include "thinggroup.h" +#include "types/statetypesproxy.h" +#include "types/ioconnection.h" +#include "types/ioconnections.h" +#include "types/ioconnectionwatcher.h" #include @@ -157,6 +161,7 @@ void registerQmlTypes() { qmlRegisterUncreatableType(uri, 1, 0, "StateTypes", "Can't create this in QML. Get it from the DeviceClass."); qmlRegisterUncreatableType(uri, 1, 0, "ActionType", "Can't create this in QML. Get it from the ActionTypes."); qmlRegisterUncreatableType(uri, 1, 0, "ActionTypes", "Can't create this in QML. Get it from the DeviceClass."); + qmlRegisterType(uri, 1, 0, "StateTypesProxy"); qmlRegisterUncreatableType(uri, 1, 0, "State", "Can't create this in QML. Get it from the States."); qmlRegisterUncreatableType(uri, 1, 0, "States", "Can't create this in QML. Get it from the Device."); @@ -293,6 +298,11 @@ void registerQmlTypes() { qmlRegisterUncreatableType(uri, 1, 0, "UserInfo", "Get it from UserManager"); qmlRegisterUncreatableType(uri, 1, 0, "TokenInfo", "Get it from TokenInfos"); qmlRegisterUncreatableType(uri, 1, 0, "TokenInfos", "Get it from UserManager"); + + qmlRegisterUncreatableType(uri, 1, 0, "IOConnections", "Get it from DeviceManager"); + qmlRegisterUncreatableType(uri, 1, 0, "IOConnection", "Get it from IOConnections"); + qmlRegisterType(uri, 1, 0, "IOInputConnectionWatcher"); + qmlRegisterType(uri, 1, 0, "IOOutputConnectionWatcher"); } #endif // LIBNYMEAAPPCORE_H diff --git a/libnymea-app/libnymea-app.pro b/libnymea-app/libnymea-app.pro index ee155727..c8217466 100644 --- a/libnymea-app/libnymea-app.pro +++ b/libnymea-app/libnymea-app.pro @@ -48,6 +48,7 @@ SOURCES += \ types/paramtypes.cpp \ types/statetype.cpp \ types/statetypes.cpp \ + types/statetypesproxy.cpp \ types/eventtype.cpp \ types/eventtypes.cpp \ types/actiontype.cpp \ @@ -86,6 +87,9 @@ SOURCES += \ types/tokeninfo.cpp \ types/tokeninfos.cpp \ types/userinfo.cpp \ + types/ioconnection.cpp \ + types/ioconnections.cpp \ + types/ioconnectionwatcher.cpp \ connection/nymeahost.cpp \ connection/nymeahosts.cpp \ connection/nymeaconnection.cpp \ @@ -176,6 +180,7 @@ HEADERS += \ types/paramtypes.h \ types/statetype.h \ types/statetypes.h \ + types/statetypesproxy.h \ types/eventtype.h \ types/eventtypes.h \ types/actiontype.h \ @@ -214,6 +219,9 @@ HEADERS += \ types/tokeninfo.h \ types/tokeninfos.h \ types/userinfo.h \ + types/ioconnection.h \ + types/ioconnections.h \ + types/ioconnectionwatcher.h \ connection/nymeahost.h \ connection/nymeahosts.h \ connection/nymeaconnection.h \ diff --git a/libnymea-app/types/ioconnection.cpp b/libnymea-app/types/ioconnection.cpp new file mode 100644 index 00000000..5ae39d99 --- /dev/null +++ b/libnymea-app/types/ioconnection.cpp @@ -0,0 +1,67 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 "ioconnection.h" + +IOConnection::IOConnection(const QUuid &id, const QUuid &inputThingId, const QUuid &inputStateTypeId, const QUuid &outputThingId, const QUuid &outputStateTypeId, QObject *parent): + QObject(parent), + m_id(id), + m_inputThingId(inputThingId), + m_inputStateTypeId(inputStateTypeId), + m_outputThingId(outputThingId), + m_outputStateTypeId(outputStateTypeId) +{ + +} + +QUuid IOConnection::id() const +{ + return m_id; +} + +QUuid IOConnection::inputThingId() const +{ + return m_inputThingId; +} + +QUuid IOConnection::inputStateTypeId() const +{ + return m_inputStateTypeId; +} + +QUuid IOConnection::outputThingId() const +{ + return m_outputThingId; +} + +QUuid IOConnection::outputStateTypeId() const +{ + return m_outputStateTypeId; +} diff --git a/libnymea-app/types/ioconnection.h b/libnymea-app/types/ioconnection.h new file mode 100644 index 00000000..a005828b --- /dev/null +++ b/libnymea-app/types/ioconnection.h @@ -0,0 +1,63 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef IOCONNECTION_H +#define IOCONNECTION_H + +#include +#include + +class IOConnection : public QObject +{ + Q_OBJECT + Q_PROPERTY(QUuid id READ id CONSTANT) + Q_PROPERTY(QUuid inputThingId READ inputThingId CONSTANT) + Q_PROPERTY(QUuid inputStateTypeId READ inputStateTypeId CONSTANT) + Q_PROPERTY(QUuid outputThingId READ outputThingId CONSTANT) + Q_PROPERTY(QUuid outputStateTypeId READ outputStateTypeId CONSTANT) + +public: + explicit IOConnection(const QUuid &id, const QUuid &inputThingId, const QUuid &inputStateTypeId, const QUuid &outputThingId, const QUuid &outputStateTypeId, QObject *parent = nullptr); + + QUuid id() const; + QUuid inputThingId() const; + QUuid inputStateTypeId() const; + QUuid outputThingId() const; + QUuid outputStateTypeId() const; + +private: + QUuid m_id; + QUuid m_inputThingId; + QUuid m_inputStateTypeId; + QUuid m_outputThingId; + QUuid m_outputStateTypeId; +}; + +#endif // IOCONNECTION_H diff --git a/libnymea-app/types/ioconnections.cpp b/libnymea-app/types/ioconnections.cpp new file mode 100644 index 00000000..854dd4ae --- /dev/null +++ b/libnymea-app/types/ioconnections.cpp @@ -0,0 +1,129 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 "ioconnections.h" + +IOConnections::IOConnections(QObject *parent) : QAbstractListModel(parent) +{ + +} + +int IOConnections::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return m_list.count(); +} + +QVariant IOConnections::data(const QModelIndex &index, int role) const +{ + switch (role) { + case RoleInputThingId: + return m_list.at(index.row())->inputThingId(); + case RoleInputStateTypeId: + return m_list.at(index.row())->inputStateTypeId(); + case RoleOutputThingId: + return m_list.at(index.row())->outputThingId(); + case RoleOutputStateTypeId: + return m_list.at(index.row())->outputStateTypeId(); + } + return QVariant(); +} + +QHash IOConnections::roleNames() const +{ + QHash roles; + roles.insert(RoleInputThingId, "inputThingId"); + roles.insert(RoleInputStateTypeId, "inputStateTypeId"); + roles.insert(RoleOutputThingId, "outputThingId"); + roles.insert(RoleOutputStateTypeId, "outputStateTypeId"); + return roles; +} + +void IOConnections::addIOConnection(IOConnection *ioConnection) +{ + ioConnection->setParent(this); + beginInsertRows(QModelIndex(), m_list.count(), m_list.count()); + m_list.append(ioConnection); + endInsertRows(); + emit countChanged(); +} + +void IOConnections::removeIOConnection(const QUuid &ioConnectionId) +{ + int idx = -1; + for (int i = 0; i < m_list.count(); i++) { + if (m_list.at(i)->id() == ioConnectionId) { + idx = i; + break; + } + } + beginRemoveRows(QModelIndex(), idx, idx); + m_list.takeAt(idx)->deleteLater(); + endRemoveRows(); + emit countChanged(); +} + +void IOConnections::clearModel() +{ + beginResetModel(); + qDeleteAll(m_list); + m_list.clear(); + endResetModel(); +} + +IOConnection *IOConnections::getIOConnection(const QUuid &ioConnectionId) const +{ + foreach (IOConnection* ioConnection, m_list) { + if (ioConnection->id() == ioConnectionId) { + return ioConnection; + } + } + return nullptr; +} + +IOConnection *IOConnections::findIOConnectionByInput(const QUuid &inputThingId, const QUuid &inputStateTypeId) const +{ + foreach (IOConnection* ioConnection, m_list) { + if (ioConnection->inputThingId() == inputThingId && ioConnection->inputStateTypeId() == inputStateTypeId) { + return ioConnection; + } + } + return nullptr; +} + +IOConnection *IOConnections::findIOConnectionByOutput(const QUuid &outputThingId, const QUuid &outputStateTypeId) const +{ + foreach (IOConnection* ioConnection, m_list) { + if (ioConnection->outputThingId() == outputThingId && ioConnection->outputStateTypeId() == outputStateTypeId) { + return ioConnection; + } + } + return nullptr; +} diff --git a/libnymea-app/types/ioconnections.h b/libnymea-app/types/ioconnections.h new file mode 100644 index 00000000..ef5ef4d7 --- /dev/null +++ b/libnymea-app/types/ioconnections.h @@ -0,0 +1,73 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef IOCONNECTIONS_H +#define IOCONNECTIONS_H + +#include "ioconnection.h" + +#include + +class IOConnections : public QAbstractListModel +{ + Q_OBJECT + Q_PROPERTY(int count READ rowCount NOTIFY countChanged) +public: + enum Roles { + RoleInputThingId, + RoleInputStateTypeId, + RoleOutputThingId, + RoleOutputStateTypeId + }; + Q_ENUM(Roles) + + explicit IOConnections(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role) const override; + QHash roleNames() const; + + void addIOConnection(IOConnection *ioConnection); + void removeIOConnection(const QUuid &ioConnectionId); + void clearModel(); + + Q_INVOKABLE IOConnection* getIOConnection(const QUuid &ioConnectionId) const; + + Q_INVOKABLE IOConnection* findIOConnectionByInput(const QUuid &inputThingId, const QUuid &inputStateTypeId) const; + Q_INVOKABLE IOConnection* findIOConnectionByOutput(const QUuid &outputThingId, const QUuid &outputStateTypeId) const; + +signals: + void countChanged(); + +private: + QList m_list; +}; + +#endif // IOCONNECTIONS_H diff --git a/libnymea-app/types/ioconnectionwatcher.cpp b/libnymea-app/types/ioconnectionwatcher.cpp new file mode 100644 index 00000000..9aea5dd5 --- /dev/null +++ b/libnymea-app/types/ioconnectionwatcher.cpp @@ -0,0 +1,157 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 "ioconnectionwatcher.h" +#include "ioconnections.h" +#include "ioconnection.h" + +IOInputConnectionWatcher::IOInputConnectionWatcher(QObject *parent) : QObject(parent) +{ + +} + +IOConnections *IOInputConnectionWatcher::ioConnections() const +{ + return m_ioConnections; +} + +void IOInputConnectionWatcher::setIOConnections(IOConnections *ioConnections) +{ + if (m_ioConnections != ioConnections) { + + if (m_ioConnections) { + disconnect(ioConnections, &IOConnections::countChanged, this, &IOInputConnectionWatcher::ioConnectionChanged); + } + + m_ioConnections = ioConnections; + emit ioConnectionsChanged(); + emit ioConnectionChanged(); + + connect(ioConnections, &IOConnections::countChanged, this, &IOInputConnectionWatcher::ioConnectionChanged); + } +} + +QUuid IOInputConnectionWatcher::inputThingId() const +{ + return m_inputThingId; +} + +void IOInputConnectionWatcher::setInputThingId(const QUuid &inputThingId) +{ + if (m_inputThingId != inputThingId) { + m_inputThingId = inputThingId; + emit inputThingIdChanged(); + emit ioConnectionChanged(); + } +} + +QUuid IOInputConnectionWatcher::inputStateTypeId() const +{ + return m_inputStateTypeId; +} + +void IOInputConnectionWatcher::setInputStateTypeId(const QUuid &inputStateTypeId) +{ + if (m_inputStateTypeId != inputStateTypeId) { + m_inputStateTypeId = inputStateTypeId; + emit inputStateTypeIdChanged(); + emit ioConnectionChanged(); + } +} + +IOConnection* IOInputConnectionWatcher::ioConnection() const +{ + if (!m_ioConnections) { + return nullptr; + } + return m_ioConnections->findIOConnectionByInput(m_inputThingId, m_inputStateTypeId); +} + +IOOutputConnectionWatcher::IOOutputConnectionWatcher(QObject *parent): QObject(parent) +{ + +} + +IOConnections *IOOutputConnectionWatcher::ioConnections() const +{ + return m_ioConnections; +} + +void IOOutputConnectionWatcher::setIOConnections(IOConnections *ioConnections) +{ + if (m_ioConnections != ioConnections) { + + if (m_ioConnections) { + disconnect(ioConnections, &IOConnections::countChanged, this, &IOOutputConnectionWatcher::ioConnectionChanged); + } + + m_ioConnections = ioConnections; + emit ioConnectionsChanged(); + emit ioConnectionChanged(); + + connect(ioConnections, &IOConnections::countChanged, this, &IOOutputConnectionWatcher::ioConnectionChanged); + } +} + +QUuid IOOutputConnectionWatcher::outputThingId() const +{ + return m_outputThingId; +} + +void IOOutputConnectionWatcher::setOutputThingId(const QUuid &outputThingId) +{ + if (m_outputThingId != outputThingId) { + m_outputThingId = outputThingId; + emit outputThingIdChanged(); + emit ioConnectionChanged(); + } +} + +QUuid IOOutputConnectionWatcher::outputStateTypeId() const +{ + return m_outputStateTypeId; +} + +void IOOutputConnectionWatcher::setOutputStateTypeId(const QUuid &outputStateTypeId) +{ + if (m_outputStateTypeId != outputStateTypeId) { + m_outputStateTypeId = outputStateTypeId; + emit outputStateTypeIdChanged(); + emit ioConnectionChanged(); + } +} + +IOConnection *IOOutputConnectionWatcher::ioConnection() const +{ + if (!m_ioConnections) { + return nullptr; + } + return m_ioConnections->findIOConnectionByOutput(m_outputThingId, m_outputStateTypeId); +} diff --git a/libnymea-app/types/ioconnectionwatcher.h b/libnymea-app/types/ioconnectionwatcher.h new file mode 100644 index 00000000..e979c618 --- /dev/null +++ b/libnymea-app/types/ioconnectionwatcher.h @@ -0,0 +1,107 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef IOCONNECTIONWATCHER_H +#define IOCONNECTIONWATCHER_H + +#include +#include + +class IOConnection; +class IOConnections; + +class IOInputConnectionWatcher : public QObject +{ + Q_OBJECT + Q_PROPERTY(IOConnections* ioConnections READ ioConnections WRITE setIOConnections NOTIFY ioConnectionsChanged) + Q_PROPERTY(QUuid inputThingId READ inputThingId WRITE setInputThingId NOTIFY inputThingIdChanged) + Q_PROPERTY(QUuid inputStateTypeId READ inputStateTypeId WRITE setInputStateTypeId NOTIFY inputStateTypeIdChanged) + Q_PROPERTY(IOConnection* ioConnection READ ioConnection NOTIFY ioConnectionChanged) +public: + explicit IOInputConnectionWatcher(QObject *parent = nullptr); + + IOConnections* ioConnections() const; + void setIOConnections(IOConnections *ioConnections); + + QUuid inputThingId() const; + void setInputThingId(const QUuid &inputThingId); + + QUuid inputStateTypeId() const; + void setInputStateTypeId(const QUuid &inputStateTypeId); + + IOConnection* ioConnection() const; + +signals: + void ioConnectionsChanged(); + void inputThingIdChanged(); + void inputStateTypeIdChanged(); + void ioConnectionChanged(); + +private: + IOConnections *m_ioConnections = nullptr; + QUuid m_inputThingId; + QUuid m_inputStateTypeId; +}; + + +class IOOutputConnectionWatcher : public QObject +{ + Q_OBJECT + Q_PROPERTY(IOConnections* ioConnections READ ioConnections WRITE setIOConnections NOTIFY ioConnectionsChanged) + Q_PROPERTY(QUuid outputThingId READ outputThingId WRITE setOutputThingId NOTIFY outputThingIdChanged) + Q_PROPERTY(QUuid outputStateTypeId READ outputStateTypeId WRITE setOutputStateTypeId NOTIFY outputStateTypeIdChanged) + Q_PROPERTY(IOConnection* ioConnection READ ioConnection NOTIFY ioConnectionChanged) +public: + explicit IOOutputConnectionWatcher(QObject *parent = nullptr); + + IOConnections* ioConnections() const; + void setIOConnections(IOConnections *ioConnections); + + QUuid outputThingId() const; + void setOutputThingId(const QUuid &outputThingId); + + QUuid outputStateTypeId() const; + void setOutputStateTypeId(const QUuid &outputStateTypeId); + + IOConnection* ioConnection() const; + +signals: + void ioConnectionsChanged(); + void outputThingIdChanged(); + void outputStateTypeIdChanged(); + void ioConnectionChanged(); + +private: + IOConnections *m_ioConnections = nullptr; + QUuid m_outputThingId; + QUuid m_outputStateTypeId; +}; + +#endif // IOCONNECTIONWATCHER_H diff --git a/libnymea-app/types/statetype.cpp b/libnymea-app/types/statetype.cpp index 16dafea4..66c0372d 100644 --- a/libnymea-app/types/statetype.cpp +++ b/libnymea-app/types/statetype.cpp @@ -149,3 +149,13 @@ void StateType::setMaxValue(const QVariant &maxValue) { m_maxValue = maxValue; } + +Types::IOType StateType::ioType() const +{ + return m_ioType; +} + +void StateType::setIOType(Types::IOType ioType) +{ + m_ioType = ioType; +} diff --git a/libnymea-app/types/statetype.h b/libnymea-app/types/statetype.h index 3d8e9b3e..3fb4ccc9 100644 --- a/libnymea-app/types/statetype.h +++ b/libnymea-app/types/statetype.h @@ -48,6 +48,7 @@ class StateType : public QObject Q_PROPERTY(QVariant defaultValue READ defaultValue CONSTANT) Q_PROPERTY(QVariantList allowedValues READ allowedValues CONSTANT) Q_PROPERTY(Types::Unit unit READ unit CONSTANT) + Q_PROPERTY(Types::IOType ioType READ ioType CONSTANT) Q_PROPERTY(QString unitString READ unitString CONSTANT) Q_PROPERTY(QVariant minValue READ minValue CONSTANT) Q_PROPERTY(QVariant maxValue READ maxValue CONSTANT) @@ -80,6 +81,9 @@ public: Types::Unit unit() const; void setUnit(const Types::Unit &unit); + Types::IOType ioType() const; + void setIOType(Types::IOType ioType); + QString unitString() const; void setUnitString(const QString &unitString); @@ -97,7 +101,8 @@ private: int m_index; QVariant m_defaultValue; QVariantList m_allowedValues; - Types::Unit m_unit; + Types::Unit m_unit = Types::UnitNone; + Types::IOType m_ioType = Types::IOTypeNone; QString m_unitString; QVariant m_minValue; QVariant m_maxValue; diff --git a/libnymea-app/types/statetypes.cpp b/libnymea-app/types/statetypes.cpp index 79d3472d..e4a6b679 100644 --- a/libnymea-app/types/statetypes.cpp +++ b/libnymea-app/types/statetypes.cpp @@ -87,6 +87,8 @@ QVariant StateTypes::data(const QModelIndex &index, int role) const return stateType->unitString(); case RoleUnit: return stateType->unit(); + case RoleIOType: + return stateType->ioType(); } return QVariant(); } @@ -110,6 +112,17 @@ StateType *StateTypes::findByName(const QString &name) const return nullptr; } +QList StateTypes::ioStateTypes(Types::IOType ioType) const +{ + QList ret; + foreach (StateType* stateType, m_stateTypes) { + if (stateType->ioType() == ioType) { + ret.append(stateType); + } + } + return ret; +} + void StateTypes::clearModel() { beginResetModel(); @@ -129,6 +142,7 @@ QHash StateTypes::roleNames() const roles[RoleDefaultValue] = "defaultValue"; roles[RoleUnitString] = "unitString"; roles[RoleUnit] = "unit"; + roles[RoleIOType] = "ioType"; return roles; } diff --git a/libnymea-app/types/statetypes.h b/libnymea-app/types/statetypes.h index 7ba01643..4c4cfd94 100644 --- a/libnymea-app/types/statetypes.h +++ b/libnymea-app/types/statetypes.h @@ -49,10 +49,11 @@ public: RoleType, RoleDefaultValue, RoleUnit, - RoleUnitString + RoleUnitString, + RoleIOType, }; - StateTypes(QObject *parent = 0); + StateTypes(QObject *parent = nullptr); QList stateTypes(); @@ -66,6 +67,8 @@ public: Q_INVOKABLE StateType *findByName(const QString &name) const; + QList ioStateTypes(Types::IOType ioType) const; + void clearModel(); protected: diff --git a/libnymea-app/types/statetypesproxy.cpp b/libnymea-app/types/statetypesproxy.cpp new file mode 100644 index 00000000..49285143 --- /dev/null +++ b/libnymea-app/types/statetypesproxy.cpp @@ -0,0 +1,140 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 "statetypesproxy.h" + +StateTypesProxy::StateTypesProxy(QObject *parent) : QSortFilterProxyModel(parent) +{ + +} + +StateTypes *StateTypesProxy::stateTypes() const +{ + return m_stateTypes; +} + +void StateTypesProxy::setStateTypes(StateTypes *stateTypes) +{ + if (m_stateTypes != stateTypes) { + m_stateTypes = stateTypes; + setSourceModel(stateTypes); + emit countChanged(); + } +} + +bool StateTypesProxy::digitalInputs() const +{ + return m_digitalInputs; +} + +void StateTypesProxy::setDigitalInputs(bool digitalInputs) +{ + if (m_digitalInputs != digitalInputs) { + m_digitalInputs = digitalInputs; + emit digitalInputsChanged(); + invalidateFilter(); + emit countChanged(); + } +} + +bool StateTypesProxy::digitalOutputs() const +{ + return m_digitalOutputs; +} + +void StateTypesProxy::setDigitalOutputs(bool digitalOutputs) +{ + if (m_digitalOutputs != digitalOutputs) { + m_digitalOutputs = digitalOutputs; + emit digitalOutputsChanged(); + invalidateFilter(); + emit countChanged(); + } +} + +bool StateTypesProxy::analogInputs() const +{ + return m_analogInputs; +} + +void StateTypesProxy::setAnalogInputs(bool analogInputs) +{ + if (m_analogInputs != analogInputs) { + m_analogInputs = analogInputs; + emit analogInputsChanged(); + invalidateFilter(); + emit countChanged(); + } +} + +bool StateTypesProxy::analogOutputs() const +{ + return m_analogOutputs; +} + +void StateTypesProxy::setAnalogOutputs(bool analogOutputs) +{ + if (m_analogOutputs != analogOutputs) { + m_analogOutputs = analogOutputs; + emit analogOutputsChanged(); + invalidateFilter(); + emit countChanged(); + } +} + +StateType *StateTypesProxy::get(int index) const +{ + return m_stateTypes->get(mapToSource(this->index(index, 0)).row()); +} + +bool StateTypesProxy::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const +{ + Q_UNUSED(source_parent) + + if (!m_digitalInputs && !m_digitalOutputs && !m_analogInputs && !m_analogOutputs) { + // filtering disabled + return true; + } + + StateType* stateType = m_stateTypes->get(source_row); + switch (stateType->ioType()) { + case Types::IOTypeNone: + return false; + case Types::IOTypeDigitalInput: + return m_digitalInputs; + case Types::IOTypeDigitalOutput: + return m_digitalOutputs; + case Types::IOTypeAnalogInput: + return m_analogInputs; + case Types::IOTypeAnalogOutput: + return m_analogOutputs; + } + return false; +} diff --git a/libnymea-app/types/statetypesproxy.h b/libnymea-app/types/statetypesproxy.h new file mode 100644 index 00000000..bbb31bd4 --- /dev/null +++ b/libnymea-app/types/statetypesproxy.h @@ -0,0 +1,89 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef STATETYPESPROXY_H +#define STATETYPESPROXY_H + +#include + +#include "statetypes.h" + +class StateTypesProxy : public QSortFilterProxyModel +{ + Q_OBJECT + Q_PROPERTY(int count READ rowCount NOTIFY countChanged) + + Q_PROPERTY(StateTypes* stateTypes READ stateTypes WRITE setStateTypes NOTIFY stateTypesChanged) + Q_PROPERTY(bool digitalInputs READ digitalInputs WRITE setDigitalInputs NOTIFY digitalInputsChanged) + Q_PROPERTY(bool digitalOutputs READ digitalOutputs WRITE setDigitalOutputs NOTIFY digitalOutputsChanged) + Q_PROPERTY(bool analogInputs READ analogInputs WRITE setAnalogInputs NOTIFY analogInputsChanged) + Q_PROPERTY(bool analogOutputs READ analogOutputs WRITE setAnalogOutputs NOTIFY analogOutputsChanged) + +public: + explicit StateTypesProxy(QObject *parent = nullptr); + + StateTypes* stateTypes() const; + void setStateTypes(StateTypes *stateTypes); + + bool digitalInputs() const; + void setDigitalInputs(bool digitalInputs); + + bool digitalOutputs() const; + void setDigitalOutputs(bool digitalOutputs); + + bool analogInputs() const; + void setAnalogInputs(bool analogInputs); + + bool analogOutputs() const; + void setAnalogOutputs(bool analogOutputs); + + Q_INVOKABLE StateType* get(int index) const; + +signals: + void countChanged(); + void stateTypesChanged(); + void digitalInputsChanged(); + void digitalOutputsChanged(); + void analogInputsChanged(); + void analogOutputsChanged(); + +protected: + bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; + +private: + StateTypes *m_stateTypes = nullptr; + + bool m_digitalInputs = false; + bool m_digitalOutputs = false; + bool m_analogInputs = false; + bool m_analogOutputs = false; +}; + +#endif // STATETYPESPROXY_H diff --git a/libnymea-app/types/types.h b/libnymea-app/types/types.h index 3421c13e..e5cc76a3 100644 --- a/libnymea-app/types/types.h +++ b/libnymea-app/types/types.h @@ -120,6 +120,15 @@ public: }; Q_ENUM(Unit) + enum IOType { + IOTypeNone, + IOTypeDigitalInput, + IOTypeDigitalOutput, + IOTypeAnalogInput, + IOTypeAnalogOutput + }; + Q_ENUM(IOType) + enum UnitSystem { UnitSystemMetric, UnitSystemImperial diff --git a/nymea-app/images.qrc b/nymea-app/images.qrc index 839cac42..b51066ed 100644 --- a/nymea-app/images.qrc +++ b/nymea-app/images.qrc @@ -218,5 +218,6 @@ ui/images/smartlock.svg ui/images/key.svg ui/images/browser/MediaBrowserIconRadioParadise.svg + ui/images/io-connections.svg diff --git a/nymea-app/ui/images/io-connections.svg b/nymea-app/ui/images/io-connections.svg new file mode 100644 index 00000000..89760b34 --- /dev/null +++ b/nymea-app/ui/images/io-connections.svg @@ -0,0 +1,20 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/nymea-app/ui/thingconfiguration/ConfigureThingPage.qml b/nymea-app/ui/thingconfiguration/ConfigureThingPage.qml index 4809537f..70daa165 100644 --- a/nymea-app/ui/thingconfiguration/ConfigureThingPage.qml +++ b/nymea-app/ui/thingconfiguration/ConfigureThingPage.qml @@ -35,7 +35,7 @@ import Nymea 1.0 import "../components" import "../delegates" -Page { +SettingsPageBase { id: root property Device device: null readonly property DeviceClass deviceClass: device ? device.deviceClass : null @@ -110,120 +110,174 @@ Page { } } - Flickable { - anchors.fill: parent - contentHeight: contentColumn.implicitHeight - ColumnLayout { - id: contentColumn - width: parent.width - - Label { - Layout.fillWidth: true - Layout.margins: app.margins - text: qsTr("Information") - color: app.accentColor - } - RowLayout { - Layout.leftMargin: app.margins; Layout.rightMargin: app.margins - Label { - text: qsTr("Vendor:") - Layout.fillWidth: true - } - Label { - text: engine.deviceManager.vendors.getVendor(root.deviceClass.vendorId).displayName - } - } - RowLayout { - Layout.leftMargin: app.margins; Layout.rightMargin: app.margins - Label { - text: qsTr("Type") - Layout.fillWidth: true - } - Label { - text: root.deviceClass.displayName - } - } - - Label { - Layout.fillWidth: true - Layout.leftMargin: app.margins - Layout.rightMargin: app.margins - Layout.topMargin: app.margins - text: qsTr("Parameters") - color: app.accentColor - visible: root.deviceClass.paramTypes.count > 0 - } - - Repeater { - model: root.device.params - delegate: ParamDelegate { - Layout.fillWidth: true - paramType: root.deviceClass.paramTypes.getParamType(model.id) - param: root.device.params.get(index) - writable: false - } - } - - Label { - Layout.fillWidth: true - Layout.leftMargin: app.margins - Layout.rightMargin: app.margins - Layout.topMargin: app.margins - text: qsTr("Settings") - color: app.accentColor - visible: root.deviceClass.settingsTypes.count > 0 - } - - Repeater { - id: settingsRepeater - model: root.device.settings - delegate: ParamDelegate { - Layout.fillWidth: true - paramType: root.deviceClass.settingsTypes.getParamType(model.id) - value: root.device.settings.get(index).value - writable: true - property bool dirty: root.device.settings.get(index).value !== value - onDirtyChanged: settingsRepeater.checkDirty() - } - function checkDirty() { - for (var i = 0; i < settingsRepeater.count; i++) { - if (settingsRepeater.itemAt(i).dirty) { - dirty = true; - return; - } - } - dirty = false; - } - property bool dirty: false - } - Button { - Layout.fillWidth: true - Layout.leftMargin: app.margins - Layout.rightMargin: app.margins - text: qsTr("Apply") - enabled: settingsRepeater.dirty - visible: settingsRepeater.count > 0 - - onClicked: { - var params = [] - for (var i = 0; i < settingsRepeater.count; i++) { - if (!settingsRepeater.itemAt(i).dirty) { - continue; - } - var setting = {} - setting["paramTypeId"] = settingsRepeater.itemAt(i).param.paramTypeId - setting["value"] = settingsRepeater.itemAt(i).param.value - params.push(setting) - } - - engine.deviceManager.setDeviceSettings(root.device.id, params); - } - } - -// ThinDivider {} + Label { + Layout.fillWidth: true + Layout.margins: app.margins + text: qsTr("Information") + color: app.accentColor + } + RowLayout { + Layout.leftMargin: app.margins; Layout.rightMargin: app.margins + Label { + text: qsTr("Vendor:") + Layout.fillWidth: true } + Label { + text: engine.deviceManager.vendors.getVendor(root.deviceClass.vendorId).displayName + } + } + RowLayout { + Layout.leftMargin: app.margins; Layout.rightMargin: app.margins + Label { + text: qsTr("Type") + Layout.fillWidth: true + } + Label { + text: root.deviceClass.displayName + } + } + Label { + Layout.fillWidth: true + Layout.leftMargin: app.margins + Layout.rightMargin: app.margins + Layout.topMargin: app.margins + text: qsTr("Parameters") + color: app.accentColor + visible: root.deviceClass.paramTypes.count > 0 + } + + Repeater { + model: root.device.params + delegate: ParamDelegate { + Layout.fillWidth: true + paramType: root.deviceClass.paramTypes.getParamType(model.id) + param: root.device.params.get(index) + writable: false + } + } + + Label { + Layout.fillWidth: true + Layout.leftMargin: app.margins + Layout.rightMargin: app.margins + Layout.topMargin: app.margins + text: qsTr("Input/Output Connections") + color: app.accentColor + visible: ioModel.count > 0 + } + + StateTypesProxy { + id: ioModel + stateTypes: root.deviceClass.stateTypes + digitalInputs: true + digitalOutputs: true + analogInputs: true + analogOutputs: true + } + Repeater { + model: ioModel + delegate: NymeaListItemDelegate { + Layout.fillWidth: true + + iconName: "../images/io-connections.svg" + text: model.displayName + subText: { + if (ioStateType.ioType == Types.IOTypeDigitalInput || ioStateType.ioType == Types.IOTypeAnalogInput) { + if (inputConnectionWatcher.ioConnection) { + return "%1: %2".arg(inputConnectionWatcher.outputThing.name).arg(inputConnectionWatcher.outputStateType.displayName) + } + } else { + if (outputConnectionWatcher.ioConnection) { + return "%1: %2".arg(outputConnectionWatcher.inputThing.name).arg(outputConnectionWatcher.inputStateType.displayName) + } + } + return qsTr("Not connected") + } + + + property StateType ioStateType: ioModel.get(index) + + IOInputConnectionWatcher { + id: inputConnectionWatcher + ioConnections: engine.deviceManager.ioConnections + inputThingId: root.device.id + inputStateTypeId: ioStateType.id + property Device outputThing: ioConnection ? engine.deviceManager.devices.getDevice(ioConnection.outputThingId) : null + property StateType outputStateType: ioConnection ? outputThing.deviceClass.stateTypes.getStateType(ioConnection.outputStateTypeId) : null + } + IOOutputConnectionWatcher { + id: outputConnectionWatcher + ioConnections: engine.deviceManager.ioConnections + outputThingId: root.device.id + outputStateTypeId: ioStateType.id + property Device inputThing: ioConnection ? engine.deviceManager.devices.getDevice(ioConnection.inputThingId) : null + property StateType inputStateType: ioConnection ? inputThing.deviceClass.stateTypes.getStateType(ioConnection.inputStateTypeId) : null + } + + onClicked: { + var popup = ioConnectionsDialogComponent.createObject(app, {ioStateType: ioStateType, inputWatcher: inputConnectionWatcher, outputWatcher: outputConnectionWatcher}) + popup.open() + } + } + } + + + Label { + Layout.fillWidth: true + Layout.leftMargin: app.margins + Layout.rightMargin: app.margins + Layout.topMargin: app.margins + text: qsTr("Settings") + color: app.accentColor + visible: root.deviceClass.settingsTypes.count > 0 + } + + Repeater { + id: settingsRepeater + model: root.device.settings + delegate: ParamDelegate { + Layout.fillWidth: true + paramType: root.deviceClass.settingsTypes.getParamType(model.id) + value: root.device.settings.get(index).value + writable: true + property bool dirty: root.device.settings.get(index).value !== value + onDirtyChanged: settingsRepeater.checkDirty() + } + function checkDirty() { + for (var i = 0; i < settingsRepeater.count; i++) { + if (settingsRepeater.itemAt(i).dirty) { + dirty = true; + return; + } + } + dirty = false; + } + property bool dirty: false + } + Button { + Layout.fillWidth: true + Layout.leftMargin: app.margins + Layout.rightMargin: app.margins + text: qsTr("Apply") + enabled: settingsRepeater.dirty + visible: settingsRepeater.count > 0 + + onClicked: { + var params = [] + for (var i = 0; i < settingsRepeater.count; i++) { + if (!settingsRepeater.itemAt(i).dirty) { + continue; + } + var setting = {} + setting["paramTypeId"] = settingsRepeater.itemAt(i).param.paramTypeId + setting["value"] = settingsRepeater.itemAt(i).param.value + params.push(setting) + } + + engine.deviceManager.setDeviceSettings(root.device.id, params); + } } Component { @@ -264,4 +318,124 @@ Page { } } + Component { + id: ioConnectionsDialogComponent + MeaDialog { + id: ioConnectionDialog + standardButtons: Dialog.Ok | Dialog.Cancel | Dialog.Reset + + title: qsTr("Connect Inputs/Outputs") + + property StateType ioStateType: null + property IOInputConnectionWatcher inputWatcher: null + property IOOutputConnectionWatcher outputWatcher: null + + Label { + Layout.fillWidth: true + text: qsTr("Connect \"%1\" to:").arg(ioConnectionDialog.ioStateType.displayName) + wrapMode: Text.WordWrap + } + Label { text: "\n" } // Fake in some spacing + + GridLayout { + columns: (ioConnectionDialog.width / 400) * 2 + + Label { + Layout.fillWidth: true + text: qsTr("Thing") + } + + ComboBox { + id: ioThingComboBox + model: DevicesProxy { + id: connectableIODevices + engine: _engine + showDigitalInputs: ioConnectionDialog.ioStateType.ioType == Types.IOTypeDigitalOutput + showDigitalOutputs: ioConnectionDialog.ioStateType.ioType == Types.IOTypeDigitalInput + showAnalogInputs: ioConnectionDialog.ioStateType.ioType == Types.IOTypeAnalogOutput + showAnalogOutputs: ioConnectionDialog.ioStateType.ioType == Types.IOTypeAnalogInput + } + textRole: "name" + Layout.fillWidth: true + Component.onCompleted: { + for (var i = 0; i < connectableIODevices.count; i++) { + if (ioConnectionDialog.ioStateType.ioType == Types.IOTypeDigitalInput || ioConnectionDialog.ioStateType.ioType == Types.IOTypeAnalogInput) { + if (connectableIODevices.get(i).id === ioConnectionDialog.inputWatcher.ioConnection.outputThingId) { + ioThingComboBox.currentIndex = i; + break; + } + } else { + if (connectableIODevices.get(i).id === ioConnectionDialog.outputWatcher.ioConnection.inputThingId) { + ioThingComboBox.currentIndex = i; + break; + } + } + } + } + } + + Label { + Layout.fillWidth: true + text: (ioConnectionDialog.ioStateType.ioType == Types.IOTypeDigitalInput || ioConnectionDialog.ioStateType.ioType == Types.IOTypeAnalogInput) ? qsTr("Output") : qsTr("Input") + } + + ComboBox { + id: ioStateComboBox + model: StateTypesProxy { + id: connectableStateTypes + stateTypes: connectableIODevices.get(ioThingComboBox.currentIndex).deviceClass.stateTypes + digitalInputs: ioConnectionDialog.ioStateType.ioType == Types.IOTypeDigitalOutput + digitalOutputs: ioConnectionDialog.ioStateType.ioType == Types.IOTypeDigitalInput + analogInputs: ioConnectionDialog.ioStateType.ioType == Types.IOTypeAnalogOutput + analogOutputs: ioConnectionDialog.ioStateType.ioType == Types.IOTypeAnalogInput + } + textRole: "displayName" + Layout.fillWidth: true + onCountChanged: { +// print("loading for:", ioConnectionDialog.inputWatcher.ioConnection.outputStateTypeId) + for (var i = 0; i < connectableStateTypes.count; i++) { + print("checking:", connectableStateTypes.get(i).id) + if (ioConnectionDialog.ioStateType.ioType == Types.IOTypeDigitalInput || ioConnectionDialog.ioStateType.ioType == Types.IOTypeAnalogInput) { + if (connectableStateTypes.get(i).id === ioConnectionDialog.inputWatcher.ioConnection.outputStateTypeId) { + ioStateComboBox.currentIndex = i; + break; + } + } else { + if (connectableStateTypes.get(i).id === ioConnectionDialog.outputWatcher.ioConnection.inputStateTypeId) { + ioStateComboBox.currentIndex = i; + break; + } + } + } + } + } + } + + onAccepted: { + var inputThingId; + var inputStateTypeId; + var outputThingId; + var outputStateTypeId; + if (ioConnectionDialog.ioStateType.ioType == Types.IOTypeDigitalInput + || ioConnectionDialog.ioStateType.ioType == Types.IOTypeAnalogInput) { + inputThingId = root.device.id; + inputStateTypeId = ioConnectionDialog.ioStateType.id; + outputThingId = connectableIODevices.get(ioThingComboBox.currentIndex).id; + outputStateTypeId = connectableStateTypes.get(ioStateComboBox.currentIndex).id; + } else { + inputThingId = connectableIODevices.get(ioThingComboBox.currentIndex).id; + inputStateTypeId = connectableStateTypes.get(ioStateComboBox.currentIndex).id; + outputThingId = root.device.id; + outputStateTypeId = ioConnectionDialog.ioStateType.id; + } + + print("connecting", inputThingId, inputStateTypeId, outputThingId, outputStateTypeId) + engine.deviceManager.connectIO(inputThingId, inputStateTypeId, outputThingId, outputStateTypeId); + } + onReset: { + engine.deviceManager.disconnectIO(ioConnectionDialog.watcher.ioConnection.id); + ioConnectionDialog.reject(); + } + } + } }