Add a new thing status view (connection, battery, update)
This commit is contained in:
parent
c0aa180be6
commit
1c2d62d56a
@ -429,8 +429,8 @@ void NewLogsModel::logsReply(int commandId, const QVariantMap &data)
|
|||||||
beginInsertRows(QModelIndex(), 0, entries.count() - 1);
|
beginInsertRows(QModelIndex(), 0, entries.count() - 1);
|
||||||
m_list = entries;
|
m_list = entries;
|
||||||
endInsertRows();
|
endInsertRows();
|
||||||
emit entriesAdded(0, entries);
|
|
||||||
}
|
}
|
||||||
|
emit entriesAdded(0, entries);
|
||||||
emit countChanged();
|
emit countChanged();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@ -441,9 +441,9 @@ void NewLogsModel::logsReply(int commandId, const QVariantMap &data)
|
|||||||
});
|
});
|
||||||
m_list.append(entries);
|
m_list.append(entries);
|
||||||
endInsertRows();
|
endInsertRows();
|
||||||
emit entriesAdded(m_list.count() - entries.count(), entries);
|
|
||||||
emit countChanged();
|
|
||||||
}
|
}
|
||||||
|
emit entriesAdded(m_list.count() - entries.count(), entries);
|
||||||
|
emit countChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -144,7 +144,7 @@ public:
|
|||||||
Q_INVOKABLE Param *param(const QUuid ¶mTypeId) const;
|
Q_INVOKABLE Param *param(const QUuid ¶mTypeId) const;
|
||||||
Q_INVOKABLE Param *paramByName(const QString ¶mName) const;
|
Q_INVOKABLE Param *paramByName(const QString ¶mName) const;
|
||||||
|
|
||||||
Q_INVOKABLE virtual int executeAction(const QString &actionName, const QVariantList ¶ms);
|
Q_INVOKABLE virtual int executeAction(const QString &actionName, const QVariantList ¶ms = QVariantList());
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void nameChanged();
|
void nameChanged();
|
||||||
|
|||||||
@ -316,5 +316,7 @@
|
|||||||
<file>ui/devicepages/EventLogPage.qml</file>
|
<file>ui/devicepages/EventLogPage.qml</file>
|
||||||
<file>ui/mainviews/dashboard/DashboardStateDelegate.qml</file>
|
<file>ui/mainviews/dashboard/DashboardStateDelegate.qml</file>
|
||||||
<file>ui/mainviews/dashboard/DashboardSensorDelegate.qml</file>
|
<file>ui/mainviews/dashboard/DashboardSensorDelegate.qml</file>
|
||||||
|
<file>ui/devicepages/ThingStatusPage.qml</file>
|
||||||
|
<file>ui/customviews/MultiStateChart.qml</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|||||||
@ -8,7 +8,7 @@ BigTile {
|
|||||||
|
|
||||||
property Thing thing: null
|
property Thing thing: null
|
||||||
|
|
||||||
readonly property State connectedState: thing.thingClass.interfaces.indexOf("connectable") >= 0 && thing.stateByName("connected")
|
readonly property State connectedState: thing.thingClass.interfaces.indexOf("connectable") >= 0 ? thing.stateByName("connected") : null
|
||||||
readonly property bool isConnected: connectedState === null || connectedState.value === true
|
readonly property bool isConnected: connectedState === null || connectedState.value === true
|
||||||
readonly property bool isEnabled: thing.setupStatus == Thing.ThingSetupStatusComplete && isConnected
|
readonly property bool isEnabled: thing.setupStatus == Thing.ThingSetupStatusComplete && isConnected
|
||||||
|
|
||||||
|
|||||||
@ -12,11 +12,6 @@ RowLayout {
|
|||||||
|
|
||||||
property color color: Style.iconColor
|
property color color: Style.iconColor
|
||||||
|
|
||||||
signal updateIconClicked();
|
|
||||||
signal batteryIconClicked();
|
|
||||||
signal connectionIconClicked();
|
|
||||||
signal setupIconClicked();
|
|
||||||
|
|
||||||
UpdateStatusIcon {
|
UpdateStatusIcon {
|
||||||
id: updateStatusIcon
|
id: updateStatusIcon
|
||||||
Layout.preferredHeight: Style.smallIconSize
|
Layout.preferredHeight: Style.smallIconSize
|
||||||
@ -28,30 +23,34 @@ RowLayout {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: -app.margins / 4
|
anchors.margins: -app.margins / 4
|
||||||
onClicked: {
|
onClicked: {
|
||||||
var dialogComponent = Qt.createComponent("NymeaDialog.qml")
|
if (engine.jsonRpcClient.ensureServerVersion("8.0")) {
|
||||||
var currentVersionState = root.thing.stateByName("currentVersion")
|
pageStack.push("../devicepages/ThingStatusPage.qml", {thing: root.thing})
|
||||||
var availableVersionState = root.thing.stateByName("availableVersion")
|
} else {
|
||||||
var text = qsTr("An update for %1 is available. Do you want to start the update now?").arg(root.thing.name)
|
var dialogComponent = Qt.createComponent("NymeaDialog.qml")
|
||||||
if (currentVersionState) {
|
var currentVersionState = root.thing.stateByName("currentVersion")
|
||||||
text += "\n\n" + qsTr("Current version: %1").arg(currentVersionState.value)
|
var availableVersionState = root.thing.stateByName("availableVersion")
|
||||||
}
|
var text = qsTr("An update for %1 is available. Do you want to start the update now?").arg(root.thing.name)
|
||||||
if (availableVersionState) {
|
if (currentVersionState) {
|
||||||
text += "\n\n" + qsTr("Available version: %1").arg(availableVersionState.value)
|
text += "\n\n" + qsTr("Current version: %1").arg(currentVersionState.value)
|
||||||
|
}
|
||||||
|
if (availableVersionState) {
|
||||||
|
text += "\n\n" + qsTr("Available version: %1").arg(availableVersionState.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
var dialog = dialogComponent.createObject(app,
|
||||||
|
{
|
||||||
|
headerIcon: "../images/system-update.svg",
|
||||||
|
title: qsTr("Update"),
|
||||||
|
text: text,
|
||||||
|
standardButtons: Dialog.Ok | Dialog.Cancel
|
||||||
|
})
|
||||||
|
dialog.accepted.connect(function() {
|
||||||
|
print("starting update")
|
||||||
|
engine.thingManager.executeAction(root.thing.id, root.thing.thingClass.actionTypes.findByName("performUpdate").id)
|
||||||
|
})
|
||||||
|
dialog.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
var dialog = dialogComponent.createObject(app,
|
|
||||||
{
|
|
||||||
headerIcon: "../images/system-update.svg",
|
|
||||||
title: qsTr("Update"),
|
|
||||||
text: text,
|
|
||||||
standardButtons: Dialog.Ok | Dialog.Cancel
|
|
||||||
})
|
|
||||||
dialog.accepted.connect(function() {
|
|
||||||
print("starting update")
|
|
||||||
engine.thingManager.executeAction(root.thing.id, root.thing.thingClass.actionTypes.findByName("performUpdate").id)
|
|
||||||
})
|
|
||||||
dialog.open();
|
|
||||||
root.updateIconClicked()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -66,21 +65,24 @@ RowLayout {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: -app.margins / 4
|
anchors.margins: -app.margins / 4
|
||||||
onClicked: {
|
onClicked: {
|
||||||
root.batteryIconClicked()
|
if (engine.jsonRpcClient.ensureServerVersion("8.0")) {
|
||||||
var levelStateType = root.thing.thingClass.stateTypes.findByName("batteryLevel");
|
pageStack.push("../devicepages/ThingStatusPage.qml", {thing: root.thing})
|
||||||
var criticalStateType = root.thing.thingClass.stateTypes.findByName("batteryCritical");
|
} else {
|
||||||
var stateTypes = []
|
var levelStateType = root.thing.thingClass.stateTypes.findByName("batteryLevel");
|
||||||
if (levelStateType) {
|
var criticalStateType = root.thing.thingClass.stateTypes.findByName("batteryCritical");
|
||||||
stateTypes.push(levelStateType.id)
|
var stateTypes = []
|
||||||
|
if (levelStateType) {
|
||||||
|
stateTypes.push(levelStateType.id)
|
||||||
|
}
|
||||||
|
if (criticalStateType) {
|
||||||
|
stateTypes.push(criticalStateType.id)
|
||||||
|
}
|
||||||
|
pageStack.push("../devicepages/DeviceLogPage.qml",
|
||||||
|
{
|
||||||
|
thing: root.thing,
|
||||||
|
filterTypeIds: stateTypes
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (criticalStateType) {
|
|
||||||
stateTypes.push(criticalStateType.id)
|
|
||||||
}
|
|
||||||
pageStack.push("../devicepages/DeviceLogPage.qml",
|
|
||||||
{
|
|
||||||
thing: root.thing,
|
|
||||||
filterTypeIds: stateTypes
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -95,21 +97,24 @@ RowLayout {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: -app.margins / 4
|
anchors.margins: -app.margins / 4
|
||||||
onClicked: {
|
onClicked: {
|
||||||
root.connectionIconClicked()
|
if (engine.jsonRpcClient.ensureServerVersion("8.0")) {
|
||||||
var signalStateType = root.thing.thingClass.stateTypes.findByName("signalStrength")
|
pageStack.push("../devicepages/ThingStatusPage.qml", {thing: root.thing})
|
||||||
var connectedStateType = root.thing.thingClass.stateTypes.findByName("connected")
|
} else {
|
||||||
var stateTypes = []
|
var signalStateType = root.thing.thingClass.stateTypes.findByName("signalStrength")
|
||||||
if (signalStateType) {
|
var connectedStateType = root.thing.thingClass.stateTypes.findByName("connected")
|
||||||
stateTypes.push(signalStateType.id)
|
var stateTypes = []
|
||||||
|
if (signalStateType) {
|
||||||
|
stateTypes.push(signalStateType.id)
|
||||||
|
}
|
||||||
|
if (connectedStateType) {
|
||||||
|
stateTypes.push(connectedStateType.id)
|
||||||
|
}
|
||||||
|
pageStack.push("../devicepages/DeviceLogPage.qml",
|
||||||
|
{
|
||||||
|
thing: root.thing,
|
||||||
|
filterTypeIds: stateTypes
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (connectedStateType) {
|
|
||||||
stateTypes.push(connectedStateType.id)
|
|
||||||
}
|
|
||||||
pageStack.push("../devicepages/DeviceLogPage.qml",
|
|
||||||
{
|
|
||||||
thing: root.thing,
|
|
||||||
filterTypeIds: stateTypes
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -124,7 +129,6 @@ RowLayout {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: -app.margins / 4
|
anchors.margins: -app.margins / 4
|
||||||
onClicked: {
|
onClicked: {
|
||||||
root.setupIconClicked()
|
|
||||||
pageStack.push("../thingconfiguration/ConfigureThingPage.qml", { thing: root.thing });
|
pageStack.push("../thingconfiguration/ConfigureThingPage.qml", { thing: root.thing });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
656
nymea-app/ui/customviews/MultiStateChart.qml
Normal file
656
nymea-app/ui/customviews/MultiStateChart.qml
Normal file
@ -0,0 +1,656 @@
|
|||||||
|
import QtQuick 2.9
|
||||||
|
import QtQuick.Controls 2.2
|
||||||
|
import QtQuick.Controls.Material 2.2
|
||||||
|
import QtQuick.Layouts 1.1
|
||||||
|
import Nymea 1.0
|
||||||
|
import NymeaApp.Utils 1.0
|
||||||
|
import "../components"
|
||||||
|
import "../customviews"
|
||||||
|
import QtCharts 2.2
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
implicitHeight: width * .6
|
||||||
|
implicitWidth: 400
|
||||||
|
|
||||||
|
// A model with roles:
|
||||||
|
// - thingId: uuid/string
|
||||||
|
// - stateName: string
|
||||||
|
// - color: color
|
||||||
|
// - fillArea: bool (default false)
|
||||||
|
property var statesModel: []
|
||||||
|
property string title: ""
|
||||||
|
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
id: d
|
||||||
|
|
||||||
|
readonly property int range: selectionTabs.currentValue.range
|
||||||
|
readonly property int sampleRate: selectionTabs.currentValue.sampleRate
|
||||||
|
readonly property int visibleValues: range / sampleRate
|
||||||
|
|
||||||
|
property date now: new Date()
|
||||||
|
|
||||||
|
readonly property var startTime: {
|
||||||
|
var date = new Date(fixTime(now));
|
||||||
|
date.setTime(date.getTime() - range * 60000 + 2000);
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property var endTime: {
|
||||||
|
var date = new Date(fixTime(now));
|
||||||
|
date.setTime(date.getTime() + 2000)
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fixTime(timestamp) {
|
||||||
|
return timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureValue(series, timestamp) {
|
||||||
|
if (!series) return;
|
||||||
|
if (series.count == 0) {
|
||||||
|
series.append(timestamp, 0)
|
||||||
|
} else if (series.count == 1) {
|
||||||
|
if (timestamp.getTime() < series.at(0).x) {
|
||||||
|
series.insert(0, timestamp, 0)
|
||||||
|
} else {
|
||||||
|
series.append(timestamp, 0)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (timestamp.getTime() > series.at(0).x) {
|
||||||
|
series.remove(1)
|
||||||
|
series.append(timestamp, 0)
|
||||||
|
} else if (timestamp.getTime() < series.at(1).x) {
|
||||||
|
series.remove(0)
|
||||||
|
series.insert(0, timestamp, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function shrink(series, logsModel) {
|
||||||
|
if (!series) return;
|
||||||
|
series.clear();
|
||||||
|
if (logsModel.count > 0) {
|
||||||
|
ensureValue(series, logsModel.get(0).timestamp)
|
||||||
|
ensureValue(series, logsModel.get(logsModel.count-1).timestamp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshAll() {
|
||||||
|
for (var i = 0; i < root.statesModel.length; i++) {
|
||||||
|
modelsRepeater.itemAt(i).logsModel.fetchLogs()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
id: modelsRepeater
|
||||||
|
model: root.statesModel
|
||||||
|
delegate: Item {
|
||||||
|
id: modelDelegate
|
||||||
|
readonly property string thingId: root.statesModel[index].thingId
|
||||||
|
readonly property string stateName: root.statesModel[index].stateName
|
||||||
|
readonly property color color: root.statesModel[index].color
|
||||||
|
readonly property bool fillArea: root.statesModel[index].hasOwnProperty("fillArea") ? root.statesModel[index].fillArea : false
|
||||||
|
|
||||||
|
readonly property Thing thing: engine.thingManager.things.getThing(root.statesModel[index].thingId)
|
||||||
|
readonly property StateType stateType: thing.thingClass.stateTypes.findByName(stateName)
|
||||||
|
readonly property State thingState: thing.stateByName(stateName)
|
||||||
|
property XYSeries series: null
|
||||||
|
property XYSeries zeroSeries: null
|
||||||
|
property AreaSeries areaSeries: null
|
||||||
|
property ValueAxis axis: null
|
||||||
|
|
||||||
|
readonly property bool isBool: stateType.type.toLowerCase() === "bool"
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: valueAxisComponent
|
||||||
|
ValueAxis {
|
||||||
|
labelFormat: isBool ? " " : "%0.2" /*+ labelsLayout.precision*/ + "f " + Types.toUiUnit(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" && logsModel.minValue != logsModel.maxValue
|
||||||
|
// property double range: Math.abs(valueAxis.max - valueAxis.min)
|
||||||
|
// property double stepSize: range / (valueAxis.tickCount - 1)
|
||||||
|
// property int precision: valueAxis.max - valueAxis.min < 5 ? 2 : 0
|
||||||
|
|
||||||
|
// Repeater {
|
||||||
|
// model: valueAxis.tickCount
|
||||||
|
// delegate: Label {
|
||||||
|
// y: parent.height / (valueAxis.tickCount - 1) * index - font.pixelSize / 2
|
||||||
|
// width: parent.width - Style.smallMargins
|
||||||
|
// horizontalAlignment: Text.AlignRight
|
||||||
|
// property double offset: (valueAxis.tickCount - index - 1) * labelsLayout.stepSize
|
||||||
|
// property double value: valueAxis.min + offset
|
||||||
|
// text: root.stateType ? Types.toUiValue(value, root.stateType.unit).toFixed(labelsLayout.precision) + " " + Types.toUiUnit(root.stateType.unit) : ""
|
||||||
|
// verticalAlignment: Text.AlignTop
|
||||||
|
// font: Style.extraSmallFont
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
// var axis = isBool ? boolAxis : valueAxis
|
||||||
|
|
||||||
|
axis = valueAxisComponent.createObject(chartView)
|
||||||
|
axis.min = Qt.binding(function() {return logsModel.minValue})
|
||||||
|
axis.max = Qt.binding(function() {return logsModel.maxValue})
|
||||||
|
|
||||||
|
series = chartView.createSeries(ChartView.SeriesTypeLine, thing.name, dateTimeAxis, axis)
|
||||||
|
series.color = color
|
||||||
|
series.width = isBool ? 0 : 2
|
||||||
|
|
||||||
|
if (fillArea) {
|
||||||
|
print("creating zero series")
|
||||||
|
zeroSeries = chartView.createSeries(ChartView.SeriesTypeLine, thing.name, dateTimeAxis, axis)
|
||||||
|
zeroSeries.color = color
|
||||||
|
|
||||||
|
areaSeries = chartView.createSeries(ChartView.SeriesTypeArea, thing.name, dateTimeAxis, axis)
|
||||||
|
areaSeries.upperSeries = series
|
||||||
|
areaSeries.lowerSeries = zeroSeries
|
||||||
|
areaSeries.color = Qt.rgba(color.r, color.g, color.b, color.a * .5)
|
||||||
|
areaSeries.borderColor = color
|
||||||
|
areaSeries.borderWidth = isBool ? 0 : 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Component.onDestruction: {
|
||||||
|
if (fillArea) {
|
||||||
|
chartView.removeSeries(zeroSeries)
|
||||||
|
chartView.removeSeries(areaSeries)
|
||||||
|
}
|
||||||
|
chartView.removeSeries(series)
|
||||||
|
axis.destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: selectionTabs
|
||||||
|
onTabSelected: {
|
||||||
|
logsModel.clear()
|
||||||
|
logsModel.fetchLogs()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
property NewLogsModel logsModel: NewLogsModel {
|
||||||
|
// id: logsModel
|
||||||
|
engine: _engine
|
||||||
|
source: thing ? "state-" + thing.id + "-" + stateName : ""
|
||||||
|
startTime: new Date(d.startTime.getTime() - d.range * 1.1 * 60000)
|
||||||
|
endTime: new Date(d.endTime.getTime() + d.range * 1.1 * 60000)
|
||||||
|
sampleRate: stateType.type.toLowerCase() === "bool" ? NewLogsModel.SampleRateAny : d.sampleRate
|
||||||
|
sortOrder: Qt.AscendingOrder
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
print("****** completed", modelDelegate.thingId)
|
||||||
|
ready = true
|
||||||
|
update()
|
||||||
|
}
|
||||||
|
property bool ready: false
|
||||||
|
onSourceChanged: {
|
||||||
|
// print("***** source changed")
|
||||||
|
update()
|
||||||
|
}
|
||||||
|
|
||||||
|
function update() {
|
||||||
|
// print("*********+ source", source, "start", startTime, "end", endTime, ready)
|
||||||
|
if (ready && source != "") {
|
||||||
|
fetchLogs()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
property double minValue
|
||||||
|
property double maxValue
|
||||||
|
|
||||||
|
onBusyChanged: {
|
||||||
|
if (busy) {
|
||||||
|
chartView.busyCounter++
|
||||||
|
} else {
|
||||||
|
chartView.busyCounter--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onEntriesAddedIdx: {
|
||||||
|
print("**** entries added", index, count, "entries in series:", series.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))
|
||||||
|
d.ensureValue(zeroSeries, entry.timestamp)
|
||||||
|
|
||||||
|
if (stateType.type.toLowerCase() == "bool") {
|
||||||
|
var value = entry.values[stateType.name]
|
||||||
|
if (value == null) {
|
||||||
|
value = false;
|
||||||
|
}
|
||||||
|
value *= root.inverted ? -1 : 1
|
||||||
|
var previousEntry = i > 0 ? logsModel.get(i-1) : null;
|
||||||
|
var previousValue = previousEntry ? previousEntry.values[stateType.name] : false
|
||||||
|
if (previousValue == null) {
|
||||||
|
previousValue = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// for booleans, we'll insert the previous value right before the new one so the position is doubled
|
||||||
|
var insertIdx = (index + i) * 2
|
||||||
|
// print("inserting bool 1", insertIdx, entry.timestamp.getTime() - 500, !value, new Date(entry.timestamp.getTime() - 500))
|
||||||
|
series.insert(insertIdx, entry.timestamp.getTime() - 500, previousValue)
|
||||||
|
// print("inserting bool 2", insertIdx + 1, entry.timestamp.getTime(), value, entry.timestamp)
|
||||||
|
series.insert(insertIdx+1, entry.timestamp, value)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
var value = entry.values[stateType.name]
|
||||||
|
if (value == null) {
|
||||||
|
value = 0;
|
||||||
|
}
|
||||||
|
value *= root.inverted ? -1.1 : 1.1
|
||||||
|
|
||||||
|
minValue = minValue == undefined ? value : Math.min(minValue, value)
|
||||||
|
maxValue = maxValue == undefined ? value : Math.max(maxValue, value)
|
||||||
|
|
||||||
|
var insertIdx = index + i
|
||||||
|
series.insert(insertIdx, entry.timestamp, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stateType.type.toLowerCase() == "bool") {
|
||||||
|
|
||||||
|
var last = series.at(series.count-1);
|
||||||
|
if (last.x < d.endTime) {
|
||||||
|
series.append(d.endTime, last.y)
|
||||||
|
d.ensureValue(zeroSeries, d.endTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
print("added entries. now in series:", series.count)
|
||||||
|
|
||||||
|
}
|
||||||
|
onEntriesRemoved: {
|
||||||
|
print("removing:", index, count, series.count)
|
||||||
|
if (stateType.type.toLowerCase() == "bool") {
|
||||||
|
series.removePoints(index * 2, count * 2)
|
||||||
|
if (series.count == 1) {
|
||||||
|
series.removePoints(0, 1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
series.removePoints(index, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.shrink(zeroSeries, logsModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
Label {
|
||||||
|
id: titleLabel
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.margins: Style.smallMargins
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
text: root.title
|
||||||
|
visible: root.title != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectionTabs {
|
||||||
|
id: selectionTabs
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: Style.smallMargins
|
||||||
|
Layout.rightMargin: Style.smallMargins
|
||||||
|
currentIndex: 1
|
||||||
|
model: ListModel {
|
||||||
|
ListElement {
|
||||||
|
modelData: qsTr("Hours")
|
||||||
|
sampleRate: NewLogsModel.SampleRate1Min
|
||||||
|
range: 180 // 3 Hours: 3 * 60
|
||||||
|
}
|
||||||
|
ListElement {
|
||||||
|
modelData: qsTr("Days")
|
||||||
|
sampleRate: NewLogsModel.SampleRate15Mins
|
||||||
|
range: 1440 // 1 Day: 24 * 60
|
||||||
|
}
|
||||||
|
ListElement {
|
||||||
|
modelData: qsTr("Weeks")
|
||||||
|
sampleRate: NewLogsModel.SampleRate1Hour
|
||||||
|
range: 10080 // 7 Days: 7 * 24 * 60
|
||||||
|
}
|
||||||
|
ListElement {
|
||||||
|
modelData: qsTr("Months")
|
||||||
|
sampleRate: NewLogsModel.SampleRate3Hours
|
||||||
|
range: 43200 // 30 Days: 30 * 24 * 60
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onTabSelected: {
|
||||||
|
d.now = new Date()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ChartView {
|
||||||
|
id: chartView
|
||||||
|
anchors.fill: parent
|
||||||
|
// backgroundColor: "transparent"
|
||||||
|
margins.left: 0
|
||||||
|
margins.right: 0
|
||||||
|
margins.bottom: Style.smallMargins //Style.smallIconSize + Style.margins
|
||||||
|
margins.top: 0
|
||||||
|
|
||||||
|
backgroundColor: Style.tileBackgroundColor
|
||||||
|
backgroundRoundness: Style.cornerRadius
|
||||||
|
|
||||||
|
legend.alignment: Qt.AlignBottom
|
||||||
|
legend.labelColor: Style.foregroundColor
|
||||||
|
legend.font: Style.extraSmallFont
|
||||||
|
legend.visible: false
|
||||||
|
|
||||||
|
property int busyCounter: 0
|
||||||
|
|
||||||
|
ActivityIndicator {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
visible: chartView.busyCounter > 0
|
||||||
|
opacity: .5
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
visible: {
|
||||||
|
if (chartView.busyCounter > 0) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for (var i = 0; i < modelsRepeater.count; i++) {
|
||||||
|
if (modelsRepeater.itemAt(i).logsModel.count > 0) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
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 {} }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
DateTimeAxis {
|
||||||
|
id: dateTimeAxis
|
||||||
|
|
||||||
|
min: d.startTime
|
||||||
|
max: d.endTime
|
||||||
|
format: {
|
||||||
|
switch (selectionTabs.currentValue.sampleRate) {
|
||||||
|
case NewLogsModel.SampleRate1Min:
|
||||||
|
case NewLogsModel.SampleRate15Mins:
|
||||||
|
return "hh:mm"
|
||||||
|
case NewLogsModel.SampleRate1Hour:
|
||||||
|
case NewLogsModel.SampleRate3Hours:
|
||||||
|
case NewLogsModel.SampleRate1Day:
|
||||||
|
return "dd.MM."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tickCount: {
|
||||||
|
switch (selectionTabs.currentValue.sampleRate) {
|
||||||
|
case NewLogsModel.SampleRate1Min:
|
||||||
|
case NewLogsModel.SampleRate15Mins:
|
||||||
|
return root.width > 500 ? 13 : 7
|
||||||
|
case NewLogsModel.SampleRate1Hour:
|
||||||
|
return 7
|
||||||
|
case NewLogsModel.SampleRate3Hours:
|
||||||
|
case NewLogsModel.SampleRate1Day:
|
||||||
|
return root.width > 500 ? 12 : 6
|
||||||
|
}
|
||||||
|
}
|
||||||
|
labelsFont: Style.extraSmallFont
|
||||||
|
gridVisible: false
|
||||||
|
minorGridVisible: false
|
||||||
|
lineVisible: false
|
||||||
|
shadesVisible: false
|
||||||
|
labelsColor: Style.foregroundColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: mouseArea
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.leftMargin: chartView.plotArea.x
|
||||||
|
anchors.topMargin: chartView.plotArea.y
|
||||||
|
anchors.rightMargin: chartView.width - chartView.plotArea.width - chartView.plotArea.x
|
||||||
|
anchors.bottomMargin: chartView.height - chartView.plotArea.height - chartView.plotArea.y
|
||||||
|
|
||||||
|
hoverEnabled: true
|
||||||
|
preventStealing: tooltipping || dragging
|
||||||
|
propagateComposedEvents: true
|
||||||
|
|
||||||
|
property int startMouseX: 0
|
||||||
|
property bool dragging: false
|
||||||
|
property bool tooltipping: false
|
||||||
|
|
||||||
|
property var startDatetime: null
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
interval: 300
|
||||||
|
running: mouseArea.pressed
|
||||||
|
onTriggered: {
|
||||||
|
if (!mouseArea.dragging) {
|
||||||
|
mouseArea.tooltipping = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onReleased: {
|
||||||
|
if (mouseArea.dragging) {
|
||||||
|
logsModel.fetchLogs()
|
||||||
|
mouseArea.dragging = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
mouseArea.tooltipping = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
onPressed: {
|
||||||
|
startMouseX = mouseX
|
||||||
|
startDatetime = d.now
|
||||||
|
}
|
||||||
|
|
||||||
|
onDoubleClicked: {
|
||||||
|
if (selectionTabs.currentIndex == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var idx = Math.ceil(mouseArea.mouseX * d.visibleValues / mouseArea.width)
|
||||||
|
var timestamp = new Date(d.startTime.getTime() + (idx * d.sampleRate * 60000))
|
||||||
|
selectionTabs.currentIndex--
|
||||||
|
d.now = new Date(Math.min(new Date().getTime(), timestamp.getTime() + (d.visibleValues / 2) * d.sampleRate * 60000))
|
||||||
|
powerBalanceLogs.fetchLogs()
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseXChanged: {
|
||||||
|
if (!pressed || mouseArea.tooltipping) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (Math.abs(startMouseX - mouseX) < 10) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dragging = true
|
||||||
|
|
||||||
|
var dragDelta = startMouseX - mouseX
|
||||||
|
var totalTime = d.endTime.getTime() - d.startTime.getTime()
|
||||||
|
// dragDelta : timeDelta = width : totalTime
|
||||||
|
var timeDelta = dragDelta * totalTime / mouseArea.width
|
||||||
|
// print("dragging", dragDelta, totalTime, mouseArea.width)
|
||||||
|
d.now = new Date(Math.min(new Date(), new Date(startDatetime.getTime() + timeDelta)))
|
||||||
|
}
|
||||||
|
|
||||||
|
onWheel: {
|
||||||
|
startDatetime = d.now
|
||||||
|
var totalTime = d.endTime.getTime() - d.startTime.getTime()
|
||||||
|
// pixelDelta : timeDelta = width : totalTime
|
||||||
|
var timeDelta = wheel.pixelDelta.x * totalTime / mouseArea.width
|
||||||
|
// print("wheeling", wheel.pixelDelta.x, totalTime, mouseArea.width)
|
||||||
|
d.now = new Date(Math.min(new Date(), new Date(startDatetime.getTime() - timeDelta)))
|
||||||
|
wheelStopTimer.restart()
|
||||||
|
}
|
||||||
|
Timer {
|
||||||
|
id: wheelStopTimer
|
||||||
|
interval: 300
|
||||||
|
repeat: false
|
||||||
|
onTriggered: d.refreshAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
height: parent.height
|
||||||
|
width: 1
|
||||||
|
color: Style.foregroundColor
|
||||||
|
x: Math.min(mouseArea.width, Math.max(0, mouseArea.mouseX))
|
||||||
|
visible: tooltipRepeater.tooltipsVisible
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: updateTimer
|
||||||
|
interval: 0
|
||||||
|
onTriggered: tooltipRepeater.update()
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
id: tooltipRepeater
|
||||||
|
model: root.statesModel.length
|
||||||
|
property var timestamp: new Date(((d.endTime.getTime() - d.startTime.getTime()) * mouseArea.mouseX / mouseArea.width) + d.startTime.getTime())
|
||||||
|
property int tooltipWidth: 130
|
||||||
|
property int xOnRight: Math.max(0, mouseArea.mouseX) + Style.smallMargins
|
||||||
|
property int xOnLeft: Math.min(mouseArea.width, mouseArea.mouseX) - Style.smallMargins - tooltipWidth
|
||||||
|
property int tooltipX: xOnLeft < 0 ? xOnRight : xOnLeft
|
||||||
|
property bool tooltipsVisible: (mouseArea.containsMouse || mouseArea.tooltipping) && !mouseArea.dragging
|
||||||
|
|
||||||
|
|
||||||
|
onTimestampChanged: {
|
||||||
|
updateTimer.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
function update() {
|
||||||
|
var ordered = []
|
||||||
|
insert(tooltipRepeater, ordered);
|
||||||
|
|
||||||
|
for (var i = ordered.length - 1; i >= 0; i--) {
|
||||||
|
var item = ordered[i]
|
||||||
|
var newY = item.realY
|
||||||
|
|
||||||
|
if (i < ordered.length-1) {
|
||||||
|
var previous = ordered[i+1]
|
||||||
|
newY = Math.min(newY, previous.fixedY - item.height/* - Style.extraSmallMargins*/)
|
||||||
|
}
|
||||||
|
|
||||||
|
ordered[i].fixedY = newY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function insert(repeater, array) {
|
||||||
|
for (var i = 0; i < repeater.count; i++) {
|
||||||
|
var item = repeater.itemAt(i);
|
||||||
|
var insertIdx = 0;
|
||||||
|
while (array.length > insertIdx && item.realY > array[insertIdx].realY) {
|
||||||
|
insertIdx++
|
||||||
|
}
|
||||||
|
array.splice(insertIdx, 0, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
delegate: NymeaToolTip {
|
||||||
|
id: tooltip
|
||||||
|
width: tooltipRepeater.tooltipWidth
|
||||||
|
height: layout.implicitHeight + Style.smallMargins * 2
|
||||||
|
|
||||||
|
visible: tooltipRepeater.tooltipsVisible && entry != null
|
||||||
|
x: tooltipRepeater.tooltipX
|
||||||
|
backgroundItem: chartView
|
||||||
|
backgroundRect: Qt.rect(mouseArea.x + x, mouseArea.y + y, width, height)
|
||||||
|
|
||||||
|
property Item chartDelegate: modelsRepeater.count > 0 ? modelsRepeater.itemAt(index) : null
|
||||||
|
property Thing thing: chartDelegate ? chartDelegate.thing : null
|
||||||
|
property NewLogEntry entry: chartDelegate ? chartDelegate.logsModel.find(tooltipRepeater.timestamp) : null
|
||||||
|
property string valueName: chartDelegate ? chartDelegate.stateType.name : ""
|
||||||
|
// property alias iconSource: icon.name
|
||||||
|
property ValueAxis axis: chartDelegate ? chartDelegate.axis : null
|
||||||
|
property int unit: chartDelegate ? chartDelegate.stateType.unit : Types.UnitNone
|
||||||
|
|
||||||
|
readonly property var value: entry ? entry.values[valueName] : null
|
||||||
|
readonly property int realY: entry ? Math.min(Math.max(mouseArea.height - (value * mouseArea.height / axis.max) - height / 2 /*- Style.margins*/, 0), mouseArea.height - height) : 0
|
||||||
|
property int fixedY: 0
|
||||||
|
y: fixedY // Animated
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: layout
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Style.smallMargins
|
||||||
|
|
||||||
|
ColorIcon {
|
||||||
|
id: icon
|
||||||
|
size: Style.smallIconSize
|
||||||
|
color: chartDelegate ? chartDelegate.color : "red"
|
||||||
|
visible: name != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: rect
|
||||||
|
width: Style.extraSmallFont.pixelSize
|
||||||
|
height: width
|
||||||
|
color: chartDelegate ? chartDelegate.color : "red"
|
||||||
|
visible: !icon.visible
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
text: root.statesModel[index].hasOwnProperty("tooltipFunction")
|
||||||
|
? root.statesModel[index].tooltipFunction(tooltip.value)
|
||||||
|
: "%1: %2%3".arg(thing.name).arg(entry ? round(Types.toUiValue(tooltip.value, unit)) : "-").arg(Types.toUiUnit(tooltip.unit))
|
||||||
|
Layout.fillWidth: true
|
||||||
|
font: Style.extraSmallFont
|
||||||
|
elide: Text.ElideMiddle
|
||||||
|
function round(value) {
|
||||||
|
return Math.round(value * 100) / 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
266
nymea-app/ui/devicepages/ThingStatusPage.qml
Normal file
266
nymea-app/ui/devicepages/ThingStatusPage.qml
Normal file
@ -0,0 +1,266 @@
|
|||||||
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
*
|
||||||
|
* 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.9
|
||||||
|
import QtQuick.Controls 2.2
|
||||||
|
import QtQuick.Controls.Material 2.2
|
||||||
|
import QtQuick.Layouts 1.1
|
||||||
|
import Nymea 1.0
|
||||||
|
import "../components"
|
||||||
|
import "../customviews"
|
||||||
|
|
||||||
|
Page {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property Thing thing: null
|
||||||
|
|
||||||
|
header: NymeaHeader {
|
||||||
|
text: qsTr("Status for %1").arg(root.thing.name)
|
||||||
|
onBackPressed: pageStack.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
Flickable {
|
||||||
|
id: flickable
|
||||||
|
anchors.fill: parent
|
||||||
|
contentHeight: layout.implicitHeight
|
||||||
|
interactive: contentHeight > height
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
GridLayout {
|
||||||
|
id: layout
|
||||||
|
width: flickable.width
|
||||||
|
columns: app.landscape ? 2 : 1
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: updateStatusLayout
|
||||||
|
readonly property State currentVersionState: thing.stateByName("currentVersion")
|
||||||
|
readonly property State availableVersionState: thing.stateByName("availableVersion")
|
||||||
|
readonly property State updateStatusState: thing.stateByName("updateStatus")
|
||||||
|
readonly property State updateProgressState: thing.stateByName("updateProgress")
|
||||||
|
|
||||||
|
visible: thing.thingClass.interfaces.indexOf("update") >= 0
|
||||||
|
|
||||||
|
SettingsPageSectionHeader {
|
||||||
|
text: qsTr("Update information")
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.leftMargin: Style.margins
|
||||||
|
Layout.rightMargin: Style.margins
|
||||||
|
Layout.bottomMargin: Style.margins
|
||||||
|
spacing: Style.margins
|
||||||
|
|
||||||
|
ColorIcon {
|
||||||
|
name: "system-update"
|
||||||
|
size: Style.largeIconSize
|
||||||
|
color: updateStatusLayout.updateStatusState != null && updateStatusLayout.updateStatusState.value == "updating" ? Style.accentColor : Style.iconColor
|
||||||
|
RotationAnimation on rotation {
|
||||||
|
from: 0; to: 360
|
||||||
|
duration: 2000
|
||||||
|
running: updateStatusLayout.updateStatusState != null && updateStatusLayout.updateStatusState.value == "updating"
|
||||||
|
loops: Animation.Infinite
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: {
|
||||||
|
switch (updateStatusLayout.updateStatusState.value) {
|
||||||
|
case "idle":
|
||||||
|
return qsTr("Thing is up to date")
|
||||||
|
case "available":
|
||||||
|
return qsTr("Update available")
|
||||||
|
case "updating":
|
||||||
|
return qsTr("Updating...")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: updateStatusLayout.currentVersionState ? qsTr("Installed version: %1").arg(updateStatusLayout.currentVersionState.value) : ""
|
||||||
|
font: Style.smallFont
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
visible: updateStatusLayout.availableVersionState != null && updateStatusLayout.updateStatusState != null && updateStatusLayout.updateStatusState.value === "available"
|
||||||
|
text: updateStatusLayout.availableVersionState ? qsTr("Available version: %1").arg(updateStatusLayout.availableVersionState.value) : ""
|
||||||
|
font: Style.smallFont
|
||||||
|
}
|
||||||
|
|
||||||
|
ProgressBar {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
visible: updateStatusLayout.updateStatusState != null && updateStatusLayout.updateStatusState.value === "updating"
|
||||||
|
value: updateStatusLayout.updateProgressState ? updateStatusLayout.updateProgressState.value : 50
|
||||||
|
indeterminate: updateStatusLayout.updateProgressState == null
|
||||||
|
from: 0
|
||||||
|
to: 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
text: qsTr("Update")
|
||||||
|
Layout.minimumWidth: parent.width / 2
|
||||||
|
visible: updateStatusLayout.updateStatusState && updateStatusLayout.updateStatusState.value === "available"
|
||||||
|
onClicked: {
|
||||||
|
var dialogComponent = Qt.createComponent("../components/NymeaDialog.qml")
|
||||||
|
var currentVersionState = root.thing.stateByName("currentVersion")
|
||||||
|
var availableVersionState = root.thing.stateByName("availableVersion")
|
||||||
|
var text = qsTr("Do you want to start the update now?")
|
||||||
|
if (currentVersionState) {
|
||||||
|
text += "\n\n" + qsTr("Current version: %1").arg(currentVersionState.value)
|
||||||
|
}
|
||||||
|
if (availableVersionState) {
|
||||||
|
text += "\n\n" + qsTr("Available version: %1").arg(availableVersionState.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
var dialog = dialogComponent.createObject(app,
|
||||||
|
{
|
||||||
|
headerIcon: "system-update",
|
||||||
|
title: qsTr("Update"),
|
||||||
|
text: text,
|
||||||
|
standardButtons: Dialog.Ok | Dialog.Cancel
|
||||||
|
})
|
||||||
|
if (!dialog) {
|
||||||
|
print("Error:", dialogComponent.errorString())
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.accepted.connect(function() {
|
||||||
|
print("starting update")
|
||||||
|
root.thing.executeAction("performUpdate")
|
||||||
|
})
|
||||||
|
dialog.open();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: connectionStatusLayout
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
readonly property State connectedState: thing.stateByName("connected")
|
||||||
|
readonly property State signalStrengthState: thing.stateByName("signalStrength")
|
||||||
|
|
||||||
|
visible: thing.thingClass.interfaces.indexOf("connectable") >= 0
|
||||||
|
|
||||||
|
SettingsPageSectionHeader {
|
||||||
|
text: qsTr("Connection information")
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.leftMargin: Style.margins
|
||||||
|
Layout.rightMargin: Style.margins
|
||||||
|
Layout.bottomMargin: Style.margins
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: (connectionStatusLayout.connectedState.value === true ? qsTr("Connected") : qsTr("Disconnected"))
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: connectionStatusLayout.signalStrengthState != null ? connectionStatusLayout.signalStrengthState.value + " %" : ""
|
||||||
|
horizontalAlignment: Text.AlignRight
|
||||||
|
}
|
||||||
|
ConnectionStatusIcon {
|
||||||
|
Layout.preferredHeight: Style.smallIconSize
|
||||||
|
Layout.preferredWidth: height
|
||||||
|
thing: root.thing
|
||||||
|
visible: connectionStatusLayout.signalStrengthState != null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MultiStateChart {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: width / 2
|
||||||
|
statesModel: [
|
||||||
|
{thingId: root.thing.id, stateName: "connected", color: Qt.rgba(Style.green.r, Style.green.g, Style.green.b, .2), fillArea: true, tooltipFunction: function(value) {
|
||||||
|
return value === true ? qsTr("Connected") : qsTr("Disconnected")
|
||||||
|
}},
|
||||||
|
{thingId: root.thing.id, stateName: "signalStrength", color: Style.orange, fillArea: true, tooltipFunction: function(value){ return qsTr("Signal strength: %1 %").arg(value)}},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: batteryStatusLayout
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
readonly property State batteryCriticalState: thing.stateByName("batteryCritical")
|
||||||
|
readonly property State batteryLevelState: thing.stateByName("batteryLevel")
|
||||||
|
|
||||||
|
visible: thing.thingClass.interfaces.indexOf("battery") >= 0
|
||||||
|
|
||||||
|
SettingsPageSectionHeader {
|
||||||
|
text: qsTr("Battery information")
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.margins: Style.margins
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: batteryStatusLayout.batteryCriticalState.value === true ? qsTr("Battery level critical") : qsTr("Battery level ok")
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: batteryStatusLayout.batteryLevelState != null ? batteryStatusLayout.batteryLevelState.value + " %" : ""
|
||||||
|
horizontalAlignment: Text.AlignRight
|
||||||
|
}
|
||||||
|
BatteryStatusIcon {
|
||||||
|
Layout.preferredHeight: Style.smallIconSize
|
||||||
|
Layout.preferredWidth: height
|
||||||
|
thing: root.thing
|
||||||
|
visible: batteryStatusLayout.batteryLevelState != null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MultiStateChart {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: width / 2
|
||||||
|
statesModel: [
|
||||||
|
{thingId: root.thing.id, stateName: "batteryCritical", color: Qt.rgba(Style.red.r, Style.red.g, Style.red.b, .2), fillArea: true, tooltipFunction(value) {
|
||||||
|
return value === true ? qsTr("Critical") : qsTr("OK")
|
||||||
|
}},
|
||||||
|
{thingId: root.thing.id, stateName: "batteryLevel", color: Style.orange, fillArea: true, tooltipFunction: function(value){ return qsTr("Battery level: %1 %").arg(value)}},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user