Add possiblity to add sensors to dashboard
This commit is contained in:
parent
e2ab54a2f9
commit
c6bb4a2ab8
@ -160,9 +160,10 @@ void InterfacesModel::syncInterfaces()
|
||||
}
|
||||
}
|
||||
|
||||
qWarning() << "syncing for interfaces:" << m_shownInterfaces;
|
||||
QStringList interfacesInSource;
|
||||
foreach (ThingClass *dc, thingClasses) {
|
||||
// qDebug() << "thing" <<dc->name() << "has interfaces" << dc->interfaces();
|
||||
// qWarning() << "thing" <<dc->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")) {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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<DashboardSensorItem*>(item);
|
||||
map.insert("thingId", sensorItem->thingId());
|
||||
map.insert("interfaces", sensorItem->interfaces());
|
||||
} else if (item->type() == "state") {
|
||||
DashboardStateItem *stateItem = dynamic_cast<DashboardStateItem*>(item);
|
||||
map.insert("thingId", stateItem->thingId());
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -183,6 +183,7 @@ int main(int argc, char *argv[])
|
||||
qmlRegisterUncreatableType<DashboardSceneItem>("Nymea", 1, 0, "DashboardSceneItem", "");
|
||||
qmlRegisterUncreatableType<DashboardWebViewItem>("Nymea", 1, 0, "DashboardWebViewItem", "");
|
||||
qmlRegisterUncreatableType<DashboardStateItem>("Nymea", 1, 0, "DashboardStateItem", "");
|
||||
qmlRegisterUncreatableType<DashboardSensorItem>("Nymea", 1, 0, "DashboardSensorItem", "");
|
||||
|
||||
qmlRegisterSingletonType<PrivacyPolicyHelper>("NymeaApp.Utils", 1, 0, "PrivacyPolicyHelper", PrivacyPolicyHelper::qmlProvider);
|
||||
qmlRegisterType<QHashQml>("NymeaApp.Utils", 1, 0, "QHash");
|
||||
|
||||
@ -315,5 +315,6 @@
|
||||
<file>ui/devicepages/ActionLogPage.qml</file>
|
||||
<file>ui/devicepages/EventLogPage.qml</file>
|
||||
<file>ui/mainviews/dashboard/DashboardStateDelegate.qml</file>
|
||||
<file>ui/mainviews/dashboard/DashboardSensorDelegate.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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") {
|
||||
|
||||
@ -64,7 +64,8 @@ MainViewBase {
|
||||
"graph": "DashboardGraphDelegate.qml",
|
||||
"scene": "DashboardSceneDelegate.qml",
|
||||
"webview": "DashboardWebViewDelegate.qml",
|
||||
"state": "DashboardStateDelegate.qml"
|
||||
"state": "DashboardStateDelegate.qml",
|
||||
"sensor": "DashboardSensorDelegate.qml"
|
||||
}
|
||||
|
||||
onEditModeChanged: {
|
||||
|
||||
@ -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 {
|
||||
|
||||
81
nymea-app/ui/mainviews/dashboard/DashboardSensorDelegate.qml
Normal file
81
nymea-app/ui/mainviews/dashboard/DashboardSensorDelegate.qml
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
5
nymea-app/ui/utils/SensorInterfaceStateMap.qml
Normal file
5
nymea-app/ui/utils/SensorInterfaceStateMap.qml
Normal file
@ -0,0 +1,5 @@
|
||||
import QtQuick 2.0
|
||||
|
||||
Item {
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user