diff --git a/libnymea-app/models/newlogsmodel.cpp b/libnymea-app/models/newlogsmodel.cpp index d04398ff..d6d960cf 100644 --- a/libnymea-app/models/newlogsmodel.cpp +++ b/libnymea-app/models/newlogsmodel.cpp @@ -355,12 +355,18 @@ void NewLogsModel::fetchLogs() qCDebug(dcLogEngine()) << "Fetching logs:" << QJsonDocument::fromVariant(params).toJson(); m_engine->jsonRpcClient()->sendCommand("Logging.GetLogEntries", params, this, "logsReply"); + + m_busy = true; + emit busyChanged(); } void NewLogsModel::logsReply(int commandId, const QVariantMap &data) { Q_UNUSED(commandId) + m_busy = false; + emit busyChanged(); + QList entries; foreach (const QVariant &entryVariant, data.value("logEntries").toList()) { QVariantMap map = entryVariant.toMap(); @@ -375,10 +381,6 @@ void NewLogsModel::logsReply(int commandId, const QVariantMap &data) m_canFetchMore = entries.count() >= m_blockSize; qCDebug(dcLogEngine()) << "Logs received:" << entries.count(); - if (entries.empty()) { - return; - } - if (!m_startTime.isNull() && !m_endTime.isNull()) { beginResetModel(); QList oldEntries = m_list; @@ -387,22 +389,27 @@ void NewLogsModel::logsReply(int commandId, const QVariantMap &data) emit entriesRemoved(0, oldEntries.count()); qDeleteAll(oldEntries); - beginInsertRows(QModelIndex(), 0, entries.count() - 1); - m_list = entries; - endInsertRows(); - emit entriesAdded(0, entries); + if (!entries.isEmpty()) { + beginInsertRows(QModelIndex(), 0, entries.count() - 1); + m_list = entries; + endInsertRows(); + emit entriesAdded(0, entries); + } + emit countChanged(); } else { - beginInsertRows(QModelIndex(), m_list.count(), m_list.count() + entries.count() - 1); - qSort(entries.begin(), entries.end(), [](NewLogEntry *left, NewLogEntry *right){ - return left->timestamp() > right->timestamp(); - }); - m_list.append(entries); - endInsertRows(); - emit entriesAdded(m_list.count() - entries.count(), entries); + if (!entries.isEmpty()) { + beginInsertRows(QModelIndex(), m_list.count(), m_list.count() + entries.count() - 1); + qSort(entries.begin(), entries.end(), [](NewLogEntry *left, NewLogEntry *right){ + return left->timestamp() > right->timestamp(); + }); + m_list.append(entries); + endInsertRows(); + emit entriesAdded(m_list.count() - entries.count(), entries); + emit countChanged(); + } } - emit countChanged(); } void NewLogsModel::newLogEntryReceived(const QVariantMap &map) diff --git a/nymea-app/ui/customviews/SensorView.qml b/nymea-app/ui/customviews/SensorView.qml index 168e6cf8..c031b3f3 100644 --- a/nymea-app/ui/customviews/SensorView.qml +++ b/nymea-app/ui/customviews/SensorView.qml @@ -119,7 +119,8 @@ Item { "phsensor", "pressuresensor", "waterlevelsensor", - "windspeedsensor" + "windspeedsensor", + "noisesensor" ] if (progressInterfaces.indexOf(root.interfaceName) >= 0) { return progressComponent diff --git a/nymea-app/ui/customviews/StateChart.qml b/nymea-app/ui/customviews/StateChart.qml index d8a87e65..b821ae22 100644 --- a/nymea-app/ui/customviews/StateChart.qml +++ b/nymea-app/ui/customviews/StateChart.qml @@ -53,12 +53,19 @@ Item { NewLogsModel { id: logsModel - engine: root.thing && root.stateType ? _engine : null + engine: _engine source: root.thing ? "state-" + thing.id + "-" + root.stateType.name : "" startTime: new Date(d.startTime.getTime() - d.range * 1.1 * 60000) endTime: new Date(d.endTime.getTime() + d.range * 1.1 * 60000) sampleRate: d.sampleRate + Component.onCompleted: { + if (source != "") { + fetchLogs() + } + } + onSourceChanged: fetchLogs() + property double minValue property double maxValue @@ -127,10 +134,6 @@ Item { zeroSeries.shrink() } - - onEngineChanged: fetchLogs() - Component.onCompleted: fetchLogs() - } ColumnLayout { @@ -183,6 +186,7 @@ Item { onTabSelected: { d.now = new Date() logsModel.clear() + print("*** tab selected") logsModel.fetchLogs() } } @@ -191,6 +195,7 @@ Item { Layout.fillWidth: true Layout.fillHeight: true + ChartView { id: chartView anchors.fill: parent @@ -208,6 +213,41 @@ Item { legend.font: Style.extraSmallFont legend.visible: false + ActivityIndicator { + anchors.centerIn: parent + visible: logsModel.busy + opacity: .5 + } + + Label { + anchors.centerIn: parent + visible: !logsModel.busy && logsModel.count == 0 + text: qsTr("No data") + font: Style.smallFont + opacity: .5 + } + + Label { + x: chartView.x + chartView.plotArea.x + (chartView.plotArea.width - width) / 2 + y: chartView.y + chartView.plotArea.y + Style.smallMargins + text: { + switch (d.sampleRate) { + case NewLogsModel.SampleRate1Min: + return d.startTime.toLocaleDateString(Qt.locale(), Locale.LongFormat) + case NewLogsModel.SampleRate15Mins: + case NewLogsModel.SampleRate1Hour: + case NewLogsModel.SampleRate3Hours: + case NewLogsModel.SampleRate1Day: + case NewLogsModel.SampleRate1Week: + case NewLogsModel.SampleRate1Month: + case NewLogsModel.SampleRate1Year: + return d.startTime.toLocaleDateString(Qt.locale(), Locale.ShortFormat) + " - " + d.endTime.toLocaleDateString(Qt.locale(), Locale.ShortFormat) + } + } + font: Style.smallFont + opacity: ((new Date().getTime() - d.now.getTime()) / d.sampleRate / 60000) > d.visibleValues ? .5 : 0 + Behavior on opacity { NumberAnimation {} } + } ValueAxis { id: valueAxis min: logsModel.minValue == undefined || logsModel.minValue == 0 @@ -217,28 +257,31 @@ Item { ? 0 : logsModel.maxValue + 5 - labelFormat: "" + labelFormat: "%0.0f " + Types.toUiUnit(root.stateType.unit) gridLineColor: Style.tileOverlayColor labelsVisible: false lineVisible: false titleVisible: false shadesVisible: false + labelsFont: Style.extraSmallFont + labelsColor: Style.foregroundColor } + // Overriding the labels with our own as printf struggles with special chars Item { id: labelsLayout x: Style.smallMargins y: chartView.plotArea.y height: chartView.plotArea.height width: chartView.plotArea.x - x - visible: root.stateType.type.toLowerCase() != "bool" + visible: root.stateType.type.toLowerCase() != "bool" && logsModel.minValue != logsModel.maxValue Repeater { model: valueAxis.tickCount delegate: Label { y: parent.height / (valueAxis.tickCount - 1) * index - font.pixelSize / 2 width: parent.width - Style.smallMargins horizontalAlignment: Text.AlignRight - text: root.stateType ? Types.toUiValue(((valueAxis.max - (index * valueAxis.max / (valueAxis.tickCount - 1)))), root.stateType.unit).toFixed(0) + Types.toUiUnit(root.stateType.unit) : "" + text: root.stateType ? Types.toUiValue(((valueAxis.max - (index * valueAxis.max / (valueAxis.tickCount - 1)))), root.stateType.unit).toFixed(0) + " " + Types.toUiUnit(root.stateType.unit) : "" verticalAlignment: Text.AlignTop font: Style.extraSmallFont } @@ -457,12 +500,12 @@ Item { width: 1 color: Style.foregroundColor x: Math.min(mouseArea.width, Math.max(0, toolTip.entryX)) - visible: (mouseArea.containsMouse || mouseArea.tooltipping) && !mouseArea.dragging + visible: toolTip.visible } NymeaToolTip { id: toolTip - visible: (mouseArea.containsMouse || mouseArea.tooltipping) && !mouseArea.dragging + visible: (mouseArea.containsMouse || mouseArea.tooltipping) && !mouseArea.dragging && logsModel.count > 0 backgroundItem: chartView backgroundRect: Qt.rect(mouseArea.x + toolTip.x, mouseArea.y + toolTip.y, toolTip.width, toolTip.height) diff --git a/nymea-app/ui/delegates/SensorListDelegate.qml b/nymea-app/ui/delegates/SensorListDelegate.qml index d3f44fa6..b5fe58e7 100644 --- a/nymea-app/ui/delegates/SensorListDelegate.qml +++ b/nymea-app/ui/delegates/SensorListDelegate.qml @@ -42,35 +42,46 @@ BigThingTile { id: dataGrid columns: Math.floor(contentItem.width / 120) + ListModel { + id: interfacesModel + ListElement { interfaceName: "temperaturesensor"; stateName: "temperature" } + ListElement { interfaceName: "humiditysensor"; stateName: "humidity" } + ListElement { interfaceName: "moisturesensor"; stateName: "moisture" } + ListElement { interfaceName: "pressuresensor"; stateName: "pressure" } + ListElement { interfaceName: "lightsensor"; stateName: "lightIntensity" } + ListElement { interfaceName: "conductivitysensor"; stateName: "conductivity" } + ListElement { interfaceName: "noisesensor"; stateName: "noise" } + ListElement { interfaceName: "cosensor"; stateName: "co" } + ListElement { interfaceName: "co2sensor"; stateName: "co2" } + ListElement { interfaceName: "gassensor"; stateName: "gasLevel" } + ListElement { interfaceName: "daylightsensor"; stateName: "daylight" } + ListElement { interfaceName: "presencesensor"; stateName: "isPresent" } + ListElement { interfaceName: "vibrationsensor"; stateName: ""; eventName: "vibrationDetected" } + ListElement { interfaceName: "closablesensor"; stateName: "closed" } + ListElement { interfaceName: "heating"; stateName: "power" } + ListElement { interfaceName: "thermostat"; stateName: "targetTemperature" } + ListElement { interfaceName: "watersensor"; stateName: "waterDetected" } + ListElement { interfaceName: "waterlevelsensor"; stateName: "waterLevel" } + ListElement { interfaceName: "firesensor"; stateName: "fireDetected" } + ListElement { interfaceName: "o2sensor"; stateName: "o2saturation" } + ListElement { interfaceName: "phsensor"; stateName: "ph" } + ListElement { interfaceName: "orpsensor"; stateName: "orp" } + ListElement { interfaceName: "vocsensor"; stateName: "voc" } + ListElement { interfaceName: "pm10sensor"; stateName: "pm10" } + ListElement { interfaceName: "pm25sensor"; stateName: "pm25" } + ListElement { interfaceName: "no2sensor"; stateName: "no2" } + ListElement { interfaceName: "o3sensor"; stateName: "o3" } + } Repeater { model: ListModel { - ListElement { interfaceName: "temperaturesensor"; stateName: "temperature" } - ListElement { interfaceName: "humiditysensor"; stateName: "humidity" } - ListElement { interfaceName: "moisturesensor"; stateName: "moisture" } - ListElement { interfaceName: "pressuresensor"; stateName: "pressure" } - ListElement { interfaceName: "lightsensor"; stateName: "lightIntensity" } - ListElement { interfaceName: "conductivitysensor"; stateName: "conductivity" } - ListElement { interfaceName: "noisesensor"; stateName: "noise" } - ListElement { interfaceName: "cosensor"; stateName: "co" } - ListElement { interfaceName: "co2sensor"; stateName: "co2" } - ListElement { interfaceName: "gassensor"; stateName: "gasLevel" } - ListElement { interfaceName: "daylightsensor"; stateName: "daylight" } - ListElement { interfaceName: "presencesensor"; stateName: "isPresent" } - ListElement { interfaceName: "vibrationsensor"; stateName: ""; eventName: "vibrationDetected" } - ListElement { interfaceName: "closablesensor"; stateName: "closed" } - ListElement { interfaceName: "heating"; stateName: "power" } - ListElement { interfaceName: "thermostat"; stateName: "targetTemperature" } - ListElement { interfaceName: "watersensor"; stateName: "waterDetected" } - ListElement { interfaceName: "waterlevelsensor"; stateName: "waterLevel" } - ListElement { interfaceName: "firesensor"; stateName: "fireDetected" } - ListElement { interfaceName: "o2sensor"; stateName: "o2saturation" } - ListElement { interfaceName: "phsensor"; stateName: "ph" } - ListElement { interfaceName: "orpsensor"; stateName: "orp" } - ListElement { interfaceName: "vocsensor"; stateName: "voc" } - ListElement { interfaceName: "pm10sensor"; stateName: "pm10" } - ListElement { interfaceName: "pm25sensor"; stateName: "pm25" } - ListElement { interfaceName: "no2sensor"; stateName: "no2" } - ListElement { interfaceName: "o3sensor"; stateName: "o3" } + dynamicRoles: true + Component.onCompleted: { + for (var i = 0; i < interfacesModel.count; i++) { + if (itemDelegate.thing.thingClass.interfaces.indexOf(interfacesModel.get(i).interfaceName) >= 0) { + append(interfacesModel.get(i)) + } + } + } } delegate: RowLayout { @@ -82,15 +93,40 @@ BigThingTile { property State stateValue: stateType ? itemDelegate.thing.states.getState(stateType.id) : null property EventType eventType: itemDelegate.thing.thingClass.eventTypes.findByName(model.eventName) - LogsModel { - id: eventLogsModel - engine: sensorValueDelegate.eventType != null ? _engine : null - thingId: itemDelegate.thing.id - typeIds: sensorValueDelegate.eventType != null ? [sensorValueDelegate.eventType.id] : [] - live: true - fetchBlockSize: 1 + + property QtObject eventLogsModel: { + if (model.eventName) { + if (engine.jsonRpcClient.ensureServerVersion("8.0")) { + return eventLogsModelComponent.createObject(sensorValueDelegate) + } + return legacyEventLogsModelComponent.createObject(sensorValueDelegate) + } + return null } + Component { + id: legacyEventLogsModelComponent + LogsModel { + objectName: itemDelegate.thing.name + "-" + model.eventName + engine: sensorValueDelegate.eventType != null ? _engine : null + thingId: itemDelegate.thing.id + typeIds: sensorValueDelegate.eventType != null ? [sensorValueDelegate.eventType.id] : [] + live: true + fetchBlockSize: 1 + } + } + Component { + id: eventLogsModelComponent + NewLogsModel { + engine: _engine + source: "event-" + itemDelegate.thing.id + "-" + model.eventName + live: true + fetchBlockSize: 1 + Component.onCompleted: fetchLogs() + } + } + + ColorIcon { Layout.preferredHeight: Style.iconSize Layout.preferredWidth: height diff --git a/nymea-app/ui/devicepages/StateLogPage.qml b/nymea-app/ui/devicepages/StateLogPage.qml index 9ba29845..03a99246 100644 --- a/nymea-app/ui/devicepages/StateLogPage.qml +++ b/nymea-app/ui/devicepages/StateLogPage.qml @@ -73,6 +73,7 @@ Page { NymeaDialog { title: qsTr("Remove logs?") text: qsTr("Do you want to remove the log for this state and disable logging?") + standardButtons: Dialog.Yes | Dialog.No onAccepted: engine.thingManager.setStateLogging(root.thing.id, root.stateType.id, false) } }