From c6bb4a2ab855a52f3d292a0ca4ed30b5edb30ac2 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Wed, 11 Oct 2023 23:20:14 +0200 Subject: [PATCH] Add possiblity to add sensors to dashboard --- libnymea-app/interfacesmodel.cpp | 4 +- nymea-app/dashboard/dashboarditem.cpp | 20 ++ nymea-app/dashboard/dashboarditem.h | 14 ++ nymea-app/dashboard/dashboardmodel.cpp | 12 ++ nymea-app/dashboard/dashboardmodel.h | 1 + nymea-app/main.cpp | 1 + nymea-app/resources.qrc | 1 + nymea-app/ui/customviews/SensorView.qml | 35 +--- nymea-app/ui/customviews/StateChart.qml | 10 +- .../ui/mainviews/dashboard/Dashboard.qml | 3 +- .../dashboard/DashboardAddWizard.qml | 171 ++++++++++++++++++ .../dashboard/DashboardSensorDelegate.qml | 81 +++++++++ nymea-app/ui/utils/NymeaUtils.qml | 29 +++ .../ui/utils/SensorInterfaceStateMap.qml | 5 + 14 files changed, 349 insertions(+), 38 deletions(-) create mode 100644 nymea-app/ui/mainviews/dashboard/DashboardSensorDelegate.qml create mode 100644 nymea-app/ui/utils/SensorInterfaceStateMap.qml diff --git a/libnymea-app/interfacesmodel.cpp b/libnymea-app/interfacesmodel.cpp index 87bc49b1..dd24c2cc 100644 --- a/libnymea-app/interfacesmodel.cpp +++ b/libnymea-app/interfacesmodel.cpp @@ -160,9 +160,10 @@ void InterfacesModel::syncInterfaces() } } + qWarning() << "syncing for interfaces:" << m_shownInterfaces; QStringList interfacesInSource; foreach (ThingClass *dc, thingClasses) { -// qDebug() << "thing" <name() << "has interfaces" << dc->interfaces(); +// qWarning() << "thing" <name() << "has interfaces" << dc->interfaces(); bool isInShownIfaces = false; foreach (const QString &interface, dc->interfaces()) { @@ -173,6 +174,7 @@ void InterfacesModel::syncInterfaces() if (!interfacesInSource.contains(interface)) { interfacesInSource.append(interface); } +// qWarning() << "yes" << interface; isInShownIfaces = true; } if (m_showUncategorized && !isInShownIfaces && !interfacesInSource.contains("uncategorized")) { diff --git a/nymea-app/dashboard/dashboarditem.cpp b/nymea-app/dashboard/dashboarditem.cpp index bc9777df..d2030adc 100644 --- a/nymea-app/dashboard/dashboarditem.cpp +++ b/nymea-app/dashboard/dashboarditem.cpp @@ -180,3 +180,23 @@ QUuid DashboardStateItem::stateTypeId() const { return m_stateTypeId; } + +DashboardSensorItem::DashboardSensorItem(const QUuid &thingId, const QStringList &interfaces, QObject *parent): + DashboardItem("sensor", parent), + m_thingId(thingId), + m_interfaces(interfaces) +{ + if (interfaces.count() > 1) { + setColumnSpan(2); + } +} + +QUuid DashboardSensorItem::thingId() const +{ + return m_thingId; +} + +QStringList DashboardSensorItem::interfaces() const +{ + return m_interfaces; +} diff --git a/nymea-app/dashboard/dashboarditem.h b/nymea-app/dashboard/dashboarditem.h index 4f16036b..61db76ee 100644 --- a/nymea-app/dashboard/dashboarditem.h +++ b/nymea-app/dashboard/dashboarditem.h @@ -124,4 +124,18 @@ private: QUuid m_stateTypeId; }; +class DashboardSensorItem: public DashboardItem +{ + Q_OBJECT + Q_PROPERTY(QUuid thingId READ thingId CONSTANT) + Q_PROPERTY(QStringList interfaces READ interfaces CONSTANT) +public: + explicit DashboardSensorItem(const QUuid &thingId, const QStringList &interfaces, QObject *parent = nullptr); + QUuid thingId() const; + QStringList interfaces() const; +private: + QUuid m_thingId; + QStringList m_interfaces; +}; + #endif // DASHBOARDITEM_H diff --git a/nymea-app/dashboard/dashboardmodel.cpp b/nymea-app/dashboard/dashboardmodel.cpp index f72e2bf8..4e6f8e78 100644 --- a/nymea-app/dashboard/dashboardmodel.cpp +++ b/nymea-app/dashboard/dashboardmodel.cpp @@ -91,6 +91,12 @@ void DashboardModel::addStateItem(const QUuid &thingId, const QUuid &stateTypeId addItem(item, index); } +void DashboardModel::addSensorItem(const QUuid &thingId, const QStringList &interfaces, int index) +{ + DashboardSensorItem *item = new DashboardSensorItem(thingId, interfaces, this); + addItem(item, index); +} + void DashboardModel::removeItem(int index) { qWarning() << "removing" << index; @@ -145,6 +151,8 @@ void DashboardModel::loadFromJson(const QByteArray &json) item = new DashboardWebViewItem(itemMap.value("url").toUrl(), itemMap.value("interactive", false).toBool(), this); } else if (type == "state") { item = new DashboardStateItem(itemMap.value("thingId").toUuid(), itemMap.value("stateTypeId").toUuid(), this); + } else if (type == "sensor") { + item = new DashboardSensorItem(itemMap.value("thingId").toUuid(), itemMap.value("interfaces").toStringList(), this); } else { qWarning() << "Dashboard item type" << type << "is not implemented. Skipping..."; continue; @@ -188,6 +196,10 @@ QByteArray DashboardModel::toJson() const if (webViewItem->interactive()) { map.insert("interactive", true); } + } else if (item->type() == "sensor") { + DashboardSensorItem *sensorItem = dynamic_cast(item); + map.insert("thingId", sensorItem->thingId()); + map.insert("interfaces", sensorItem->interfaces()); } else if (item->type() == "state") { DashboardStateItem *stateItem = dynamic_cast(item); map.insert("thingId", stateItem->thingId()); diff --git a/nymea-app/dashboard/dashboardmodel.h b/nymea-app/dashboard/dashboardmodel.h index e89b0115..ed00aa11 100644 --- a/nymea-app/dashboard/dashboardmodel.h +++ b/nymea-app/dashboard/dashboardmodel.h @@ -31,6 +31,7 @@ public: Q_INVOKABLE void addSceneItem(const QUuid &ruleId, int index = -1); Q_INVOKABLE void addWebViewItem(const QUrl &url, int columnSpan, int rowSpan, bool interactive, int index = -1); Q_INVOKABLE void addStateItem(const QUuid &thingId, const QUuid &stateTypeId, int index = -1); + Q_INVOKABLE void addSensorItem(const QUuid &thingId, const QStringList &interfaces, int index = -1); Q_INVOKABLE void removeItem(int index); Q_INVOKABLE void move(int from, int to); diff --git a/nymea-app/main.cpp b/nymea-app/main.cpp index f8a15f14..590ef362 100644 --- a/nymea-app/main.cpp +++ b/nymea-app/main.cpp @@ -183,6 +183,7 @@ int main(int argc, char *argv[]) qmlRegisterUncreatableType("Nymea", 1, 0, "DashboardSceneItem", ""); qmlRegisterUncreatableType("Nymea", 1, 0, "DashboardWebViewItem", ""); qmlRegisterUncreatableType("Nymea", 1, 0, "DashboardStateItem", ""); + qmlRegisterUncreatableType("Nymea", 1, 0, "DashboardSensorItem", ""); qmlRegisterSingletonType("NymeaApp.Utils", 1, 0, "PrivacyPolicyHelper", PrivacyPolicyHelper::qmlProvider); qmlRegisterType("NymeaApp.Utils", 1, 0, "QHash"); diff --git a/nymea-app/resources.qrc b/nymea-app/resources.qrc index f228d7fa..c4ae21e3 100644 --- a/nymea-app/resources.qrc +++ b/nymea-app/resources.qrc @@ -315,5 +315,6 @@ ui/devicepages/ActionLogPage.qml ui/devicepages/EventLogPage.qml ui/mainviews/dashboard/DashboardStateDelegate.qml + ui/mainviews/dashboard/DashboardSensorDelegate.qml diff --git a/nymea-app/ui/customviews/SensorView.qml b/nymea-app/ui/customviews/SensorView.qml index c031b3f3..17d7d68a 100644 --- a/nymea-app/ui/customviews/SensorView.qml +++ b/nymea-app/ui/customviews/SensorView.qml @@ -12,33 +12,6 @@ Item { property Thing thing: null property string interfaceName: "" - property var interfaceStateMap: { - "temperaturesensor": "temperature", - "humiditysensor": "humidity", - "pressuresensor": "pressure", - "moisturesensor": "moisture", - "lightsensor": "lightIntensity", - "conductivitysensor": "conductivity", - "noisesensor": "noise", - "cosensor": "co", - "co2sensor": "co2", - "gassensor": "gasLevel", - "presencesensor": "isPresent", - "daylightsensor": "daylight", - "closablesensor": "closed", - "watersensor": "waterDetected", - "firesensor": "fireDetected", - "waterlevelsensor": "waterLevel", - "phsensor": "ph", - "o2sensor": "o2saturation", - "o3sensor": "o3", - "orpsensor": "orp", - "vocsensor": "voc", - "cosensor": "co", - "pm10sensor": "pm10", - "pm25sensor": "pm25", - "no2sensor": "no2" - } CircleBackground { id: background @@ -46,8 +19,8 @@ Item { width: Math.min(parent.width, parent.height) - Style.margins height: width - readonly property StateType sensorStateType: root.thing ? root.thing.thingClass.stateTypes.findByName(interfaceStateMap[root.interfaceName]) : null - readonly property State sensorState: root.thing ? root.thing.stateByName(interfaceStateMap[interfaceName]) : null + readonly property StateType sensorStateType: root.thing ? root.thing.thingClass.stateTypes.findByName(NymeaUtils.sensorInterfaceStateMap[root.interfaceName]) : null + readonly property State sensorState: root.thing ? root.thing.stateByName(NymeaUtils.sensorInterfaceStateMap[interfaceName]) : null onColor: { if (root.interfaceName == "closablesensor") { @@ -78,8 +51,8 @@ Item { anchors.centerIn: parent width: background.contentItem.width height: background.contentItem.height - property StateType stateType: root.thing.thingClass.stateTypes.findByName(interfaceStateMap[root.interfaceName]) - property State state: root.thing.stateByName(interfaceStateMap[root.interfaceName]) + property StateType stateType: root.thing.thingClass.stateTypes.findByName(NymeaUtils.sensorInterfaceStateMap[root.interfaceName]) + property State state: root.thing.stateByName(NymeaUtils.sensorInterfaceStateMap[root.interfaceName]) property string interfaceName: root.interfaceName property var minValue: { if (["temperaturesensor"].indexOf(root.interfaceName) >= 0) { diff --git a/nymea-app/ui/customviews/StateChart.qml b/nymea-app/ui/customviews/StateChart.qml index b7b0447c..6af4cc36 100644 --- a/nymea-app/ui/customviews/StateChart.qml +++ b/nymea-app/ui/customviews/StateChart.qml @@ -62,18 +62,18 @@ Item { sortOrder: Qt.DescendingOrder Component.onCompleted: { - print("****** completed") +// print("****** completed") ready = true update() } property bool ready: false onSourceChanged: { - print("***** source changed") +// print("***** source changed") update() } function update() { - print("*********+ source", source, "start", startTime, "end", endTime, ready) +// print("*********+ source", source, "start", startTime, "end", endTime, ready) if (ready && source != "") { fetchLogs() } @@ -83,10 +83,10 @@ Item { property double maxValue onEntriesAddedIdx: { - print("**** entries added", index, count, "entries in series:", valueSeries.count, "in model", logsModel.count) +// print("**** entries added", index, count, "entries in series:", valueSeries.count, "in model", logsModel.count) for (var i = 0; i < count; i++) { var entry = logsModel.get(i) - print("entry", entry.timestamp, entry.source, JSON.stringify(entry.values)) +// print("entry", entry.timestamp, entry.source, JSON.stringify(entry.values)) zeroSeries.ensureValue(entry.timestamp) if (root.stateType.type.toLowerCase() == "bool") { diff --git a/nymea-app/ui/mainviews/dashboard/Dashboard.qml b/nymea-app/ui/mainviews/dashboard/Dashboard.qml index c3326762..c3ef0586 100644 --- a/nymea-app/ui/mainviews/dashboard/Dashboard.qml +++ b/nymea-app/ui/mainviews/dashboard/Dashboard.qml @@ -64,7 +64,8 @@ MainViewBase { "graph": "DashboardGraphDelegate.qml", "scene": "DashboardSceneDelegate.qml", "webview": "DashboardWebViewDelegate.qml", - "state": "DashboardStateDelegate.qml" + "state": "DashboardStateDelegate.qml", + "sensor": "DashboardSensorDelegate.qml" } onEditModeChanged: { diff --git a/nymea-app/ui/mainviews/dashboard/DashboardAddWizard.qml b/nymea-app/ui/mainviews/dashboard/DashboardAddWizard.qml index 1fb37165..73d7199b 100644 --- a/nymea-app/ui/mainviews/dashboard/DashboardAddWizard.qml +++ b/nymea-app/ui/mainviews/dashboard/DashboardAddWizard.qml @@ -37,6 +37,7 @@ import Nymea 1.0 import NymeaApp.Utils 1.0 import "../../components" import "../../delegates" +import "../../customviews" NymeaDialog { id: root @@ -67,6 +68,14 @@ NymeaDialog { internalPageStack.push(addThingSelectionComponent) } } + NymeaItemDelegate { + Layout.fillWidth: true + iconName: "sensors" + text: qsTr("Sensor") + onClicked: { + internalPageStack.push(addSensorComponent) + } + } NymeaItemDelegate { Layout.fillWidth: true iconName: "folder" @@ -154,6 +163,168 @@ NymeaDialog { } } + Component { + id: addSensorComponent + ColumnLayout { + RowLayout { + Layout.leftMargin: Style.margins + Layout.rightMargin: Style.margins + ColorIcon { + name: "/ui/images/find.svg" + } + TextField { + id: filterTextField + Layout.fillWidth: true + + } + } + + ListView { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.preferredHeight: Style.delegateHeight * 6 + clip: true + model: ThingsProxy { + id: thingsProxy + engine: _engine + nameFilter: filterTextField.displayText + shownInterfaces: { + var ret = [] + for (var key in NymeaUtils.sensorInterfaceStateMap) { + ret.push(key) + } + return ret + } + } + + ScrollBar.vertical: ScrollBar {} + + delegate: NymeaItemDelegate { + width: parent.width + text: model.name + iconName: app.interfacesToIcon(thingsProxy.get(index).thingClass.interfaces) + progressive: false + onClicked: { + var thing = thingsProxy.get(index) + var availableInterfaces = [] + print("checking ifaces", thingsProxy.shownInterfaces, "----", thing.thingClass.interfaces) + for (var i = 0; i < thing.thingClass.interfaces.length; i++) { + var iface = thing.thingClass.interfaces[i] + print("checking", iface) + if (thingsProxy.shownInterfaces.indexOf(iface) >= 0) { + availableInterfaces.push(iface); + } + } + + print("matching:", availableInterfaces) + if (availableInterfaces.length == 1) { + root.dashboardModel.addSensorItem(model.id, availableInterfaces[0], root.index) + root.close(); + } else { + print("opening with interfaces:", availableInterfaces) + internalPageStack.push(addSensorSelectInterfaceComponent, {thing: thing, interfaces: availableInterfaces}) + } + + } + } + } + } + } + + Component { + id: addSensorSelectInterfaceComponent + ColumnLayout { + id: addSensorSelectInterface + property Thing thing: null + property var interfaces: ([]) + + Label { + Layout.fillWidth: true + wrapMode: Text.WordWrap + text: qsTr("Select depiction") + } + + SensorListDelegate { + Layout.fillWidth: true + thing: addSensorSelectInterface.thing + onClicked: { + root.dashboardModel.addSensorItem(addSensorSelectInterface.thing.id, addSensorSelectInterface.interfaces, root.index) + root.close(); + } + } + + + GridLayout { + Layout.fillWidth: true + Layout.fillHeight: true + columns: 2 + + Repeater { + model: addSensorSelectInterface.interfaces + + delegate: SensorView { + thing: addSensorSelectInterface.thing + interfaceName: modelData + Layout.fillWidth: true + Layout.preferredHeight: width + MouseArea { + anchors.fill: parent + onClicked: { + root.dashboardModel.addSensorItem(addSensorSelectInterface.thing.id, [modelData], root.index) + root.close(); + } + } + } + } + } + +// ListView { +// Layout.fillWidth: true +// Layout.fillHeight: true +// Layout.preferredHeight: Style.delegateHeight * 6 +// clip: true +// model: InterfacesModel { +// engine: _engine +// things: ThingsProxy { +// engine: _engine +// shownThingIds: [addSensorSelectInterface.thing.id] +// } +//// shownInterfaces: app.supportedInterfaces + +// shownInterfaces: addSensorSelectInterface.interfaces +// } + +// ScrollBar.vertical: ScrollBar {} + +// delegate: NymeaItemDelegate { +// readonly property StateType stateType: addSensorSelectInterface.thing.thingClass.stateTypes.findByName(NymeaUtils.sensorInterfaceStateMap[model.name]) +// width: parent.width +// text: stateType.displayName +// iconName: app.interfaceToIcon(model.name) +// progressive: false +// onClicked: { +//// var thing = thingsProxy.get(index) +//// var availableInterfaces = [] +//// for (var i = 0; i < thing.thingClass.interfaces.count; i++) { +//// var iface = thing.thingClass.interfaces[i] +//// if (thingsProxy.shownInterfaces.indexOf(iface) >= 0) { +//// availableInterfaces.push(iface); +//// } +//// } + +//// if (availableInterfaces.length == 1) { +//// root.dashboardModel.addSensorItem(model.id, availableInterfaces[0], root.index) +//// } else { +//// internalPageStack.push(addSensorSelectInterfaceComponent, thing, availableInterfaces) +//// } + +//// root.close(); +// } +// } +// } + } + } + Component { id: addFolderComponent ColumnLayout { diff --git a/nymea-app/ui/mainviews/dashboard/DashboardSensorDelegate.qml b/nymea-app/ui/mainviews/dashboard/DashboardSensorDelegate.qml new file mode 100644 index 00000000..9b5eebb3 --- /dev/null +++ b/nymea-app/ui/mainviews/dashboard/DashboardSensorDelegate.qml @@ -0,0 +1,81 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2023, 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 +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +import QtQuick 2.8 +import QtQuick.Controls 2.1 +import QtQuick.Controls.Material 2.1 +import QtQuick.Layouts 1.2 +import QtCharts 2.2 +import Nymea 1.0 +import NymeaApp.Utils 1.0 +import "../../components" +import "../../delegates" +import "../../customviews" + +DashboardDelegateBase { + id: root + property DashboardSensorItem item: null + + readonly property Thing thing: engine.thingManager.fetchingData ? null : engine.thingManager.things.getThing(root.item.thingId) + readonly property var interfaces: root.item.interfaces + + contentItem: Loader { + height: root.height + width: root.width + sourceComponent: root.item.interfaces.length === 1 ? singleSensorViewComponent : multiSensorViewComponent + } + + Component { + id: singleSensorViewComponent + ColumnLayout { + id: delegateRoot + SensorView { + Layout.fillWidth: true + Layout.fillHeight: true + thing: root.thing + interfaceName: root.item.interfaces[0] + // onPressAndHold: root.longPressed(); + } + Label { + text: root.thing.name + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + } + } + } + + Component { + id: multiSensorViewComponent + SensorListDelegate { + id: delegateRoot + thing: root.thing + } + } +} diff --git a/nymea-app/ui/utils/NymeaUtils.qml b/nymea-app/ui/utils/NymeaUtils.qml index 77d8383c..75df37c2 100644 --- a/nymea-app/ui/utils/NymeaUtils.qml +++ b/nymea-app/ui/utils/NymeaUtils.qml @@ -180,4 +180,33 @@ Item { var h= c && ((v==r) ? (g-b)/c : ((v==g) ? 2+(b-r)/c : 4+(r-g)/c)); return [60*(h<0?h+6:h), v&&c/v, v]; } + + readonly property var sensorInterfaceStateMap: { + "temperaturesensor": "temperature", + "humiditysensor": "humidity", + "pressuresensor": "pressure", + "moisturesensor": "moisture", + "lightsensor": "lightIntensity", + "conductivitysensor": "conductivity", + "noisesensor": "noise", + "cosensor": "co", + "co2sensor": "co2", + "gassensor": "gasLevel", + "presencesensor": "isPresent", + "daylightsensor": "daylight", + "closablesensor": "closed", + "watersensor": "waterDetected", + "firesensor": "fireDetected", + "waterlevelsensor": "waterLevel", + "phsensor": "ph", + "o2sensor": "o2saturation", + "o3sensor": "o3", + "orpsensor": "orp", + "vocsensor": "voc", + "cosensor": "co", + "pm10sensor": "pm10", + "pm25sensor": "pm25", + "no2sensor": "no2" + } + } diff --git a/nymea-app/ui/utils/SensorInterfaceStateMap.qml b/nymea-app/ui/utils/SensorInterfaceStateMap.qml new file mode 100644 index 00000000..9c36e13c --- /dev/null +++ b/nymea-app/ui/utils/SensorInterfaceStateMap.qml @@ -0,0 +1,5 @@ +import QtQuick 2.0 + +Item { + +}