Merge PR #496: Improve smartmeter and sensor views

This commit is contained in:
Jenkins nymea 2021-01-06 19:32:01 +01:00
commit 913b7741ba
18 changed files with 643 additions and 351 deletions

View File

@ -140,7 +140,7 @@ void UpnpDiscovery::readData()
data.resize(socket->pendingDatagramSize()); data.resize(socket->pendingDatagramSize());
socket->readDatagram(data.data(), data.size(), &hostAddress, &port); socket->readDatagram(data.data(), data.size(), &hostAddress, &port);
qDebug() << "Received UPnP datagram:" << data; // qDebug() << "Received UPnP datagram:" << data;
// if the data contains the HTTP OK header... // if the data contains the HTTP OK header...
if (data.contains("HTTP/1.1 200 OK")) { if (data.contains("HTTP/1.1 200 OK")) {

View File

@ -322,7 +322,7 @@ void LogsModel::fetchMore(const QModelIndex &parent)
params.insert("limit", m_blockSize); params.insert("limit", m_blockSize);
params.insert("offset", m_list.count()); params.insert("offset", m_list.count());
qDebug() << "Fetching logs from" << m_startTime.toString() << "to" << m_endTime.toString() << "with offset" << m_list.count() << "and limit" << m_blockSize; // qDebug() << "Fetching logs from" << m_startTime.toString() << "to" << m_endTime.toString() << "with offset" << m_list.count() << "and limit" << m_blockSize;
m_engine->jsonRpcClient()->sendCommand("Logging.GetLogEntries", params, this, "logsReply"); m_engine->jsonRpcClient()->sendCommand("Logging.GetLogEntries", params, this, "logsReply");
// qDebug() << "GetLogEntries called"; // qDebug() << "GetLogEntries called";

View File

@ -419,7 +419,7 @@ void LogsModelNg::fetchMore(const QModelIndex &parent)
params.insert("limit", m_blockSize); params.insert("limit", m_blockSize);
params.insert("offset", m_list.count()); params.insert("offset", m_list.count());
qDebug() << "Fetching logs:" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson()); // qDebug() << "Fetching logs:" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson());
m_engine->jsonRpcClient()->sendCommand("Logging.GetLogEntries", params, this, "logsReply"); m_engine->jsonRpcClient()->sendCommand("Logging.GetLogEntries", params, this, "logsReply");
// qDebug() << "GetLogEntries called"; // qDebug() << "GetLogEntries called";

View File

@ -46,12 +46,34 @@ void XYSeriesAdapter::setBaseSeries(QtCharts::QXYSeries *series)
connect(m_baseSeries, &QtCharts::QXYSeries::pointAdded, this, [=](int index){ connect(m_baseSeries, &QtCharts::QXYSeries::pointAdded, this, [=](int index){
if (m_series->count() > index) { if (m_series->count() > index) {
m_series->replace(index, m_series->at(index).x(), calculateSampleValue(index)); qreal value = calculateSampleValue(index);
m_series->replace(index, m_series->at(index).x(), value);
if (value < m_minValue) {
m_minValue = value;
qDebug() << "New min:" << m_minValue;
emit minValueChanged();
}
if (value > m_maxValue) {
m_maxValue = value;
qDebug() << "New max:" << m_maxValue;
emit maxValueChanged();
}
} }
}); });
connect(m_baseSeries, &QtCharts::QXYSeries::pointReplaced, this, [=](int index){ connect(m_baseSeries, &QtCharts::QXYSeries::pointReplaced, this, [=](int index){
if (m_series->count() > index) { if (m_series->count() > index) {
m_series->replace(index, m_series->at(index).x(), calculateSampleValue(index)); qreal value = calculateSampleValue(index);
m_series->replace(index, m_series->at(index).x(), value);
if (value < m_minValue) {
m_minValue = value;
qDebug() << "New min:" << m_minValue;
emit minValueChanged();
}
if (value > m_maxValue) {
m_maxValue = value;
qDebug() << "New max:" << m_maxValue;
emit maxValueChanged();
}
} }
}); });
} }
@ -155,10 +177,12 @@ void XYSeriesAdapter::logEntryAdded(LogEntry *entry)
if (value < m_minValue) { if (value < m_minValue) {
m_minValue = value; m_minValue = value;
// qDebug() << "New min:" << m_minValue;
emit minValueChanged(); emit minValueChanged();
} }
if (value > m_maxValue) { if (value > m_maxValue) {
m_maxValue = value; m_maxValue = value;
// qDebug() << "New max:" << m_maxValue;
emit maxValueChanged(); emit maxValueChanged();
} }
} }

View File

@ -297,6 +297,8 @@ Interfaces::Interfaces(QObject *parent) : QAbstractListModel(parent)
addInterface("wirelessconnectable", tr("Wireless devices"), {"connectable"}); addInterface("wirelessconnectable", tr("Wireless devices"), {"connectable"});
addStateType("wirelessconnectable", "signalStrength", QVariant::UInt, false, tr("Signal strength"), tr("Signal strength changed")); addStateType("wirelessconnectable", "signalStrength", QVariant::UInt, false, tr("Signal strength"), tr("Signal strength changed"));
addInterface("watersensor", tr("Water sensors"), {"sensor"});
addStateType("watersensor", "watterDetected", QVariant::Double, false, tr("Water detected"), tr("Water detected changed"));
} }
int Interfaces::rowCount(const QModelIndex &parent) const int Interfaces::rowCount(const QModelIndex &parent) const

View File

@ -253,5 +253,6 @@
<file>ui/images/media/ambeo.svg</file> <file>ui/images/media/ambeo.svg</file>
<file>ui/images/thermostat/cooling.svg</file> <file>ui/images/thermostat/cooling.svg</file>
<file>ui/images/thermostat/heating.svg</file> <file>ui/images/thermostat/heating.svg</file>
<file>ui/images/sensors/water.svg</file>
</qresource> </qresource>
</RCC> </RCC>

View File

@ -291,6 +291,8 @@ ApplicationWindow {
return Qt.resolvedUrl("images/sensors/closable.svg") return Qt.resolvedUrl("images/sensors/closable.svg")
case "windspeedsensor": case "windspeedsensor":
return Qt.resolvedUrl("images/sensors/windspeed.svg") return Qt.resolvedUrl("images/sensors/windspeed.svg")
case "watersensor":
return Qt.resolvedUrl("images/sensors/water.svg")
case "media": case "media":
case "mediacontroller": case "mediacontroller":
case "mediaplayer": case "mediaplayer":
@ -390,6 +392,31 @@ ApplicationWindow {
id: styleBase id: styleBase
} }
function stateColor(stateName) {
// Try to load color map from style
if (Style.stateColors[stateName]) {
return Style.stateColors[stateName];
}
if (styleBase.stateColors[stateName]) {
return styleBase.stateColors[stateName];
}
console.warn("stateColor(): Color not set for state", stateName)
return "grey";
}
function stateIcon(stateName) {
var iconMap = {
"currentPower": "energy.svg",
"totalEnergyConsumed": "smartmeter.svg",
"totalEnergyProduced": "smartmeter.svg",
}
if (!iconMap[stateName]) {
console.warn("stateIcon(): Icon not set for state", stateName)
}
return Qt.resolvedUrl("images/" + iconMap[stateName]);
}
function interfaceToColor(name) { function interfaceToColor(name) {
// Try to load color map from style // Try to load color map from style
if (Style.interfaceColors[name]) { if (Style.interfaceColors[name]) {

View File

@ -35,13 +35,20 @@ Item {
"presencesensor": "darkblue", "presencesensor": "darkblue",
"closablesensor": "green", "closablesensor": "green",
"smartmeterproducer": "lightgreen", "smartmeterproducer": "lightgreen",
"smartmeterconsumer": "orange", "extendedsmartmeterproducer": "lightgreen",
"extendedsmartmeterproducer": "blue", "smartmeterconsumer": "deepskyblue",
"extendedsmartmeterconsumer": "blue", "extendedsmartmeterconsumer": "deepskyblue",
"heating" : "gainsboro", "heating" : "gainsboro",
"thermostat": "dodgerblue", "thermostat": "dodgerblue",
"irrigation": "lightblue", "irrigation": "lightblue",
"windspeedsensor": "blue", "windspeedsensor": "blue",
"ventilation": "lightblue" "ventilation": "lightblue",
"watersensor": "aqua"
}
property var stateColors: {
"totalEnergyConsumed": "orange",
"totalEnergyProduced": "lightgreen",
"currentPower": "deepskyblue",
} }
} }

View File

@ -36,8 +36,8 @@ ToolButton {
property alias color: image.color property alias color: image.color
contentItem: Item { contentItem: Item {
height: 20 height: app.iconSize
width: 20 width: app.iconSize
ColorIcon { ColorIcon {
id: image id: image
anchors.fill: parent anchors.fill: parent

View File

@ -37,7 +37,8 @@ import Nymea 1.0
ChartView { ChartView {
id: chart id: chart
backgroundColor: Style.backgroundColor backgroundColor: Style.tileBackgroundColor
backgroundRoundness: Style.tileRadius
theme: ChartView.ChartThemeLight theme: ChartView.ChartThemeLight
legend.labelColor: Style.foregroundColor legend.labelColor: Style.foregroundColor
legend.font.pixelSize: app.smallFont legend.font.pixelSize: app.smallFont

View File

@ -44,13 +44,15 @@ Item {
property Device device: null property Device device: null
property StateType stateType: null property StateType stateType: null
property int roundTo: 2 property int roundTo: 2
property color color: Style.accentColor
property string iconSource: ""
property alias title: titleLabel.text
readonly property var valueState: device.states.getState(stateType.id) readonly property var valueState: device.states.getState(stateType.id)
readonly property var deviceClass: engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); readonly property var deviceClass: engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId);
readonly property bool hasConnectable: deviceClass.interfaces.indexOf("connectable") >= 0 readonly property bool hasConnectable: deviceClass.interfaces.indexOf("connectable") >= 0
readonly property var connectedStateType: hasConnectable ? deviceClass.stateTypes.findByName("connected") : null readonly property var connectedStateType: hasConnectable ? deviceClass.stateTypes.findByName("connected") : null
property color color: Style.accentColor
property string iconSource: ""
LogsModelNg { LogsModelNg {
id: logsModelNg id: logsModelNg
@ -72,11 +74,27 @@ Item {
viewStartTime: xAxis.min viewStartTime: xAxis.min
} }
ColumnLayout { ChartView {
id: chartView
anchors.fill: parent anchors.fill: parent
spacing: 0 margins.top: app.iconSize + app.margins
margins.bottom: app.margins / 2
margins.left: 0
margins.right: 0
backgroundColor: Style.tileBackgroundColor
backgroundRoundness: Style.tileRadius
legend.visible: false
legend.labelColor: Style.foregroundColor
titleColor: Style.foregroundColor
titleFont.pixelSize: app.largeFont
animationDuration: 300
animationOptions: ChartView.SeriesAnimations
RowLayout { RowLayout {
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins anchors { left: parent.left; top: parent.top; right: parent.right; topMargin: app.margins / 2; leftMargin: app.margins * 1.5; rightMargin: app.margins }
ColorIcon { ColorIcon {
Layout.preferredHeight: app.iconSize Layout.preferredHeight: app.iconSize
Layout.preferredWidth: app.iconSize Layout.preferredWidth: app.iconSize
@ -84,21 +102,14 @@ Item {
visible: root.iconSource.length > 0 visible: root.iconSource.length > 0
color: root.color color: root.color
} }
Led {
visible: root.stateType.type.toLowerCase() === "bool"
state: root.valueState.value === true ? "on" : "off"
}
Label { Label {
id: titleLabel
Layout.fillWidth: true Layout.fillWidth: true
text: root.stateType.type.toLowerCase() === "bool" text: root.stateType.type.toLowerCase() === "bool"
? root.stateType.displayName ? root.stateType.displayName
: 1.0 * Math.round(Types.toUiValue(root.valueState.value, root.stateType.unit) * Math.pow(10, root.roundTo)) / Math.pow(10, root.roundTo) + " " + Types.toUiUnit(root.stateType.unit) : 1.0 * Math.round(Types.toUiValue(root.valueState.value, root.stateType.unit) * Math.pow(10, root.roundTo)) / Math.pow(10, root.roundTo) + " " + Types.toUiUnit(root.stateType.unit)
font.pixelSize: app.largeFont font.pixelSize: app.largeFont
} }
HeaderButton { HeaderButton {
imageSource: "../images/zoom-out.svg" imageSource: "../images/zoom-out.svg"
onClicked: { onClicked: {
@ -106,7 +117,6 @@ Item {
xAxis.min = newTime; xAxis.min = newTime;
} }
} }
HeaderButton { HeaderButton {
imageSource: "../images/zoom-in.svg" imageSource: "../images/zoom-in.svg"
enabled: xAxis.timeDiff > (60 * 30) enabled: xAxis.timeDiff > (60 * 30)
@ -117,21 +127,6 @@ Item {
} }
} }
ChartView {
id: chartView
Layout.fillWidth: true
Layout.fillHeight: true
margins.top: 0
margins.bottom: 0
margins.left: 0
margins.right: 0
backgroundColor: Material.background
legend.visible: false
legend.labelColor: Style.foregroundColor
animationDuration: 300
animationOptions: ChartView.SeriesAnimations
ValueAxis { ValueAxis {
id: yAxis id: yAxis
max: { max: {
@ -143,8 +138,8 @@ Item {
} }
} }
min: Math.floor(logsModelNg.minValue - Math.abs(logsModelNg.minValue * .05)) min: Math.floor(logsModelNg.minValue - Math.abs(logsModelNg.minValue * .05))
// onMinChanged: applyNiceNumbers(); // onMinChanged: applyNiceNumbers();
// onMaxChanged: applyNiceNumbers(); // onMaxChanged: applyNiceNumbers();
labelsFont.pixelSize: app.smallFont labelsFont.pixelSize: app.smallFont
labelFormat: { labelFormat: {
switch (root.stateType.type.toLowerCase()) { switch (root.stateType.type.toLowerCase()) {
@ -259,7 +254,7 @@ Item {
id: connectedLineSeries id: connectedLineSeries
onPointAdded: { onPointAdded: {
var newPoint = connectedLineSeries.at(index) var newPoint = connectedLineSeries.at(index)
// print("pointadded", newPoint.x, newPoint.y) // print("pointadded", newPoint.x, newPoint.y)
} }
} }
@ -284,7 +279,7 @@ Item {
id: lineSeries1 id: lineSeries1
onPointAdded: { onPointAdded: {
var newPoint = lineSeries1.at(index) var newPoint = lineSeries1.at(index)
// print("pointadded", newPoint.x, newPoint.y) // print("pointadded", newPoint.x, newPoint.y)
if (newPoint.x > lineSeries0.at(0).x) { if (newPoint.x > lineSeries0.at(0).x) {
lineSeries0.replace(0, newPoint.x, 0) lineSeries0.replace(0, newPoint.x, 0)
@ -387,7 +382,7 @@ Item {
} }
// figure out if we scrolled too far // figure out if we scrolled too far
var overshoot = xAxis.max.getTime() - now.getTime() var overshoot = xAxis.max.getTime() - now.getTime()
// print("overshoot is:", overshoot, "oldMax", xAxis.max, "newMax", now, "oldMin", xAxis.min, "newMin", new Date(xAxis.min.getTime() - overshoot)) // print("overshoot is:", overshoot, "oldMax", xAxis.max, "newMax", now, "oldMin", xAxis.min, "newMin", new Date(xAxis.min.getTime() - overshoot))
if (overshoot > 0) { if (overshoot > 0) {
var range = xAxis.max - xAxis.min var range = xAxis.max - xAxis.min
xAxis.max = now xAxis.max = now
@ -418,7 +413,7 @@ Item {
onWheel: { onWheel: {
scrollRightLimited(-wheel.pixelDelta.x) scrollRightLimited(-wheel.pixelDelta.x)
// zoomInLimited(wheel.pixelDelta.y) // zoomInLimited(wheel.pixelDelta.y)
} }
onPositionChanged: { onPositionChanged: {
@ -445,5 +440,5 @@ Item {
} }
} }
} }
}
} }

View File

@ -627,6 +627,7 @@ MainPageTile {
ListElement { ifaceName: "presencesensor"; stateName: "isPresent" } ListElement { ifaceName: "presencesensor"; stateName: "isPresent" }
ListElement { ifaceName: "closablesensor"; stateName: "closed" } ListElement { ifaceName: "closablesensor"; stateName: "closed" }
ListElement { ifaceName: "lightsensor"; stateName: "lightIntensity" } ListElement { ifaceName: "lightsensor"; stateName: "lightIntensity" }
ListElement { ifaceName: "watersensor"; stateName: "waterDetected" }
ListElement { ifaceName: "co2sensor"; stateName: "co2" } ListElement { ifaceName: "co2sensor"; stateName: "co2" }
ListElement { ifaceName: "conductivity"; stateName: "conductivity" } ListElement { ifaceName: "conductivity"; stateName: "conductivity" }
ListElement { ifaceName: "noisesensor"; stateName: "noise" } ListElement { ifaceName: "noisesensor"; stateName: "noise" }
@ -717,6 +718,5 @@ MainPageTile {
} }
} }
} }
} }
} }

View File

@ -85,6 +85,7 @@ DeviceListPageBase {
ListElement { interfaceName: "closablesensor"; stateName: "closed" } ListElement { interfaceName: "closablesensor"; stateName: "closed" }
ListElement { interfaceName: "heating"; stateName: "power" } ListElement { interfaceName: "heating"; stateName: "power" }
ListElement { interfaceName: "thermostat"; stateName: "targetTemperature" } ListElement { interfaceName: "thermostat"; stateName: "targetTemperature" }
ListElement { interfaceName: "watersensor"; stateName: "waterDetected" }
} }
delegate: RowLayout { delegate: RowLayout {
@ -128,6 +129,8 @@ DeviceListPageBase {
return sensorValueDelegate.stateValue && sensorValueDelegate.stateValue.value === true ? qsTr("Presence") : qsTr("Vacant"); return sensorValueDelegate.stateValue && sensorValueDelegate.stateValue.value === true ? qsTr("Presence") : qsTr("Vacant");
case "daylightsensor": case "daylightsensor":
return sensorValueDelegate.stateValue && sensorValueDelegate.stateValue.value === true ? qsTr("Daytime") : qsTr("Nighttime"); return sensorValueDelegate.stateValue && sensorValueDelegate.stateValue.value === true ? qsTr("Daytime") : qsTr("Nighttime");
case "watersensor":
return sensorValueDelegate.stateValue && sensorValueDelegate.stateValue.value === true ? qsTr("Wet") : qsTr("Dry");
case "heating": case "heating":
return sensorValueDelegate.stateValue && sensorValueDelegate.stateValue.value === true ? qsTr("On") : qsTr("Off"); return sensorValueDelegate.stateValue && sensorValueDelegate.stateValue.value === true ? qsTr("On") : qsTr("Off");
default: default:
@ -144,7 +147,7 @@ DeviceListPageBase {
} }
Led { Led {
id: led id: led
visible: sensorValueDelegate.stateType && sensorValueDelegate.stateType.type.toLowerCase() === "bool" && ["presencesensor", "daylightsensor", "heating", "closablesensor"].indexOf(model.interfaceName) < 0 visible: sensorValueDelegate.stateType && sensorValueDelegate.stateType.type.toLowerCase() === "bool" && ["presencesensor", "daylightsensor", "heating", "closablesensor", "watersensor"].indexOf(model.interfaceName) < 0
state: visible && sensorValueDelegate.stateValue.value === true ? "on" : "off" state: visible && sensorValueDelegate.stateValue.value === true ? "on" : "off"
} }
Item { Item {

View File

@ -103,8 +103,8 @@ DeviceListPageBase {
Layout.preferredHeight: app.iconSize Layout.preferredHeight: app.iconSize
Layout.preferredWidth: height Layout.preferredWidth: height
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
color: app.interfaceToColor(model.interfaceName) color: app.stateColor(model.stateName)
name: app.interfaceToIcon(model.interfaceName) name: app.stateIcon(model.stateName)
} }
Label { Label {

View File

@ -41,18 +41,22 @@ DevicePageBase {
Flickable { Flickable {
id: listView id: listView
anchors { fill: parent } anchors { fill: parent }
topMargin: app.margins / 2
interactive: contentHeight > height interactive: contentHeight > height
contentHeight: contentGrid.implicitHeight contentHeight: contentGrid.implicitHeight
GridLayout { GridLayout {
id: contentGrid id: contentGrid
width: parent.width width: parent.width - app.margins
anchors.horizontalCenter: parent.horizontalCenter
columns: Math.ceil(width / 600) columns: Math.ceil(width / 600)
rowSpacing: 0
columnSpacing: 0
Repeater { Repeater {
model: ListModel { model: ListModel {
Component.onCompleted: { Component.onCompleted: {
var supportedInterfaces = ["temperaturesensor", "humiditysensor", "pressuresensor", "moisturesensor", "lightsensor", "conductivitysensor", "noisesensor", "co2sensor", "presencesensor", "daylightsensor", "closablesensor"] var supportedInterfaces = ["temperaturesensor", "humiditysensor", "pressuresensor", "moisturesensor", "lightsensor", "conductivitysensor", "noisesensor", "co2sensor", "presencesensor", "daylightsensor", "closablesensor", "watersensor"]
for (var i = 0; i < supportedInterfaces.length; i++) { for (var i = 0; i < supportedInterfaces.length; i++) {
if (root.deviceClass.interfaces.indexOf(supportedInterfaces[i]) >= 0) { if (root.deviceClass.interfaces.indexOf(supportedInterfaces[i]) >= 0) {
append({name: supportedInterfaces[i]}); append({name: supportedInterfaces[i]});
@ -67,6 +71,7 @@ DevicePageBase {
Layout.preferredHeight: item.implicitHeight Layout.preferredHeight: item.implicitHeight
property StateType stateType: root.deviceClass.stateTypes.findByName(interfaceStateMap[modelData]) property StateType stateType: root.deviceClass.stateTypes.findByName(interfaceStateMap[modelData])
property State state: root.thing.stateByName(interfaceStateMap[modelData])
property string interfaceName: modelData property string interfaceName: modelData
// sourceComponent: stateType && stateType.type.toLowerCase() === "bool" ? boolComponent : graphComponent // sourceComponent: stateType && stateType.type.toLowerCase() === "bool" ? boolComponent : graphComponent
@ -83,7 +88,8 @@ DevicePageBase {
"co2sensor": "co2", "co2sensor": "co2",
"presencesensor": "isPresent", "presencesensor": "isPresent",
"daylightsensor": "daylight", "daylightsensor": "daylight",
"closablesensor": "closed" "closablesensor": "closed",
"watersensor": "waterDetected"
} }
} }
@ -96,12 +102,32 @@ DevicePageBase {
id: graphComponent id: graphComponent
GenericTypeGraph { GenericTypeGraph {
id: graph
device: root.device device: root.device
color: app.interfaceToColor(interfaceName) color: app.interfaceToColor(interfaceName)
iconSource: app.interfaceToIcon(interfaceName) iconSource: app.interfaceToIcon(interfaceName)
implicitHeight: width * .6 implicitHeight: width * .6
property string interfaceName: parent.interfaceName property string interfaceName: parent.interfaceName
stateType: parent.stateType stateType: parent.stateType
property State state: parent.state
Binding {
target: graph
property: "title"
when: ["presencesensor", "daylightsensor", "closablesensor", "watersensor"].indexOf(graph.interfaceName) >= 0
value: {
switch (graph.interfaceName) {
case "presencesensor":
return graph.state.value === true ? qsTr("Presence") : qsTr("Vacant")
case "daylightsensor":
return graph.state.value === true ? qsTr("Daytimet") : qsTr("Nighttime")
case "closablesensor":
return graph.state.value === true ? qsTr("Closed") : qsTr("Open")
case "watersensor":
return graph.state.value === true ? qsTr("Wet") : qsTr("Dry")
}
}
}
} }
} }

View File

@ -38,42 +38,68 @@ import "../customviews"
DevicePageBase { DevicePageBase {
id: root id: root
readonly property State totalEnergyConsumedState: root.thing.stateByName("totalEnergyConsumed")
readonly property StateType totalEnergyConsumedStateType: root.thing.thingClass.stateTypes.findByName("totalEnergyConsumed")
readonly property State totalEnergyProducedState: root.thing.stateByName("totalEnergyProduced")
readonly property StateType totalEnergyProducedStateType: root.thing.thingClass.stateTypes.findByName("totalEnergyProduced")
Flickable { Flickable {
anchors.fill: parent anchors.fill: parent
topMargin: app.margins / 2
contentHeight: contentGrid.height contentHeight: contentGrid.height
interactive: contentHeight > height interactive: contentHeight > height
GridLayout { GridLayout {
id: contentGrid id: contentGrid
width: parent.width width: parent.width - app.margins
columns: Math.min(width / 300, contentModel.count) anchors.horizontalCenter: parent.horizontalCenter
columns: 1
Repeater { BigTile {
model: ListModel { Layout.preferredWidth: contentGrid.width / contentGrid.columns
id: contentModel showHeader: true
Component.onCompleted: { header: Label {
if (root.deviceClass.interfaces.indexOf("extendedsmartmeterproducer") >= 0 text: qsTr("Total energy consumption")
|| root.deviceClass.interfaces.indexOf("extendedsmartmeterconsumer") >= 0) {
append( {interface: "extendedsmartmeterproducer", stateTypeName: "currentPower" })
} }
if (root.deviceClass.interfaces.indexOf("smartmeterproducer") >= 0) {
append( {interface: "smartmeterproducer", stateTypeName: "totalEnergyProduced" }) contentItem: RowLayout {
ColorIcon {
Layout.preferredHeight: app.iconSize
Layout.preferredWidth: app.iconSize
name: app.stateIcon("totalEnergyConsumed")
color: app.stateColor("totalEnergyConsumed")
} }
if (root.deviceClass.interfaces.indexOf("smartmeterconsumer") >= 0) {
append( {interface: "smartmeterconsumer", stateTypeName: "totalEnergyConsumed" }) Label {
Layout.fillWidth: true
text: root.totalEnergyConsumedState.value.toFixed(2) + " " + root.totalEnergyConsumedStateType.unitString
font.pixelSize: app.largeFont
} }
print("shown graphs are", count) ColorIcon {
Layout.preferredHeight: app.iconSize
Layout.preferredWidth: app.iconSize
name: app.stateIcon("totalEnergyProduced")
color: app.stateColor("totalEnergyProduced")
visible: root.totalEnergyProducedState !== null
}
Label {
Layout.fillWidth: true
text: root.totalEnergyProducedState.value.toFixed(2) + " " + root.totalEnergyProducedStateType.unitString
font.pixelSize: app.largeFont
visible: root.totalEnergyProducedState !== null
} }
} }
delegate: GenericTypeGraph { }
GenericTypeGraph {
Layout.preferredWidth: contentGrid.width / contentGrid.columns Layout.preferredWidth: contentGrid.width / contentGrid.columns
device: root.device device: root.device
stateType: root.deviceClass.stateTypes.findByName(model.stateTypeName) stateType: root.deviceClass.stateTypes.findByName("currentPower")
color: app.interfaceToColor(model.interface) color: app.stateColor("currentPower")
iconSource: app.interfaceToIcon(model.interface) iconSource: app.stateIcon("currentPower")
roundTo: 5 roundTo: 5
} }
} }
} }
}
} }

View File

@ -0,0 +1,177 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="96"
height="96"
id="svg4874"
version="1.1"
inkscape:version="1.0.1 (1.0.1+r74)"
viewBox="0 0 96 96.000001"
sodipodi:docname="water.svg">
<defs
id="defs4876" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.6199993"
inkscape:cx="42.494274"
inkscape:cy="50.51304"
inkscape:document-units="px"
inkscape:current-layer="g4780"
showgrid="true"
showborder="true"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:snap-bbox="true"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:snap-bbox-midpoints="true"
inkscape:object-paths="true"
inkscape:snap-intersection-paths="true"
inkscape:object-nodes="true"
inkscape:snap-smooth-nodes="true"
inkscape:snap-midpoints="true"
inkscape:snap-object-midpoints="true"
inkscape:snap-center="true"
showguides="true"
inkscape:guide-bbox="true"
inkscape:snap-global="true"
inkscape:document-rotation="0"
inkscape:window-width="1380"
inkscape:window-height="873"
inkscape:window-x="60"
inkscape:window-y="27"
inkscape:window-maximized="1">
<inkscape:grid
type="xygrid"
id="grid5451"
empspacing="8" />
<sodipodi:guide
orientation="1,0"
position="8,-8.0000001"
id="guide4063" />
<sodipodi:guide
orientation="1,0"
position="4,-8.0000001"
id="guide4065" />
<sodipodi:guide
orientation="0,1"
position="-8,88.000001"
id="guide4067" />
<sodipodi:guide
orientation="0,1"
position="-8,92.000001"
id="guide4069" />
<sodipodi:guide
orientation="0,1"
position="104,4"
id="guide4071" />
<sodipodi:guide
orientation="0,1"
position="-5,8.0000001"
id="guide4073" />
<sodipodi:guide
orientation="1,0"
position="88,-8.0000001"
id="guide4077" />
<sodipodi:guide
orientation="0,1"
position="-8,84.000001"
id="guide4074" />
<sodipodi:guide
orientation="1,0"
position="12,-8.0000001"
id="guide4076" />
<sodipodi:guide
orientation="1,0"
position="84,-8.0000001"
id="guide4080" />
<sodipodi:guide
position="48,-8.0000001"
orientation="1,0"
id="guide4170" />
<sodipodi:guide
position="-8,48"
orientation="0,1"
id="guide4172" />
<sodipodi:guide
position="92,-8.0000001"
orientation="1,0"
id="guide4760" />
</sodipodi:namedview>
<metadata
id="metadata4879">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(67.857146,-78.50504)">
<g
transform="matrix(0,-1,-1,0,373.50506,516.50504)"
id="g4845"
style="display:inline">
<g
inkscape:export-ydpi="90"
inkscape:export-xdpi="90"
inkscape:export-filename="next01.png"
transform="matrix(-0.9996045,0,0,1,575.94296,-611.00001)"
id="g4778"
inkscape:label="Layer 1">
<g
transform="matrix(-1,0,0,1,575.99999,611)"
id="g4780"
style="display:inline">
<rect
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:none;stroke-width:4;marker:none;enable-background:accumulate"
id="rect4782"
width="96.037987"
height="96"
x="-438.00244"
y="345.36221"
transform="scale(-1,1)" />
<path
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.8;marker:none;enable-background:accumulate"
d="m 410.99179,429.36224 h -4.0016 c 0,0 4.20227,-10.94333 4.00158,-19.82534 -0.20069,-8.88201 -7.79075,-23.17621 -8.00316,-32.09889 -0.21242,-8.92267 4.00158,-20.07579 4.00158,-20.07579 h 4.0016 c 0,0 -4.2233,11.28389 -4.00158,20.2116 0.22172,8.92771 7.80091,23.13419 8.00316,31.99414 0.20225,8.85995 -4.00158,19.79428 -4.00158,19.79428 z"
id="path4182"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cczzcczzc" />
<path
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.8;marker:none;enable-background:accumulate"
d="m 392.09463,429.36224 h -4.0016 c 0,0 4.20227,-10.94333 4.00158,-19.82534 -0.20069,-8.88201 -7.79075,-23.17621 -8.00316,-32.09889 -0.21242,-8.92267 4.00158,-20.07579 4.00158,-20.07579 h 4.0016 c 0,0 -4.2233,11.28389 -4.00158,20.2116 0.22172,8.92771 7.80091,23.13419 8.00316,31.99414 0.20225,8.85995 -4.00158,19.79428 -4.00158,19.79428 z"
id="path851"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cczzcczzc" />
<path
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.8;marker:none;enable-background:accumulate"
d="m 373.01945,429.36224 h -4.0016 c 0,0 4.20227,-10.94333 4.00158,-19.82534 -0.20069,-8.88201 -7.79075,-23.17621 -8.00316,-32.09889 -0.21242,-8.92267 4.00158,-20.07579 4.00158,-20.07579 h 4.0016 c 0,0 -4.2233,11.28389 -4.00158,20.2116 0.22172,8.92771 7.80091,23.13419 8.00316,31.99414 0.20225,8.85995 -4.00158,19.79428 -4.00158,19.79428 z"
id="path853"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cczzcczzc" />
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

@ -79,7 +79,8 @@ MainViewBase {
legend.alignment: Qt.AlignBottom legend.alignment: Qt.AlignBottom
legend.font.pixelSize: app.smallFont legend.font.pixelSize: app.smallFont
legend.visible: false legend.visible: false
backgroundColor: Style.backgroundColor backgroundColor: Style.tileBackgroundColor
backgroundRoundness: Style.tileRadius
titleColor: Style.foregroundColor titleColor: Style.foregroundColor
title: qsTr("Power usage history") title: qsTr("Power usage history")
@ -150,7 +151,7 @@ MainViewBase {
} }
Component.onCompleted: { Component.onCompleted: {
print("creating series") print("creating series", consumer.thing.name, index)
seriesAdapter.ensureSamples(xAxis.min, xAxis.max) seriesAdapter.ensureSamples(xAxis.min, xAxis.max)
var areaSeries = chartView.createSeries(ChartView.SeriesTypeArea, consumer.thing.name, xAxis, yAxis) var areaSeries = chartView.createSeries(ChartView.SeriesTypeArea, consumer.thing.name, xAxis, yAxis)
areaSeries.upperSeries = upperSeries; areaSeries.upperSeries = upperSeries;
@ -179,8 +180,8 @@ MainViewBase {
ValueAxis { ValueAxis {
id: yAxis id: yAxis
readonly property XYSeriesAdapter adapter: consumersRepeater.itemAt(consumersRepeater.count - 1).adapter; readonly property XYSeriesAdapter adapter: consumersRepeater.itemAt(consumersRepeater.count - 1).adapter;
max: Math.ceil(adapter.maxValue + Math.abs(adapter.maxValue * .05)) max: Math.ceil(Math.max(adapter.maxValue * 0.95, adapter.maxValue * 1.05))
min: Math.floor(adapter.minValue - Math.abs(adapter.minValue * .05)) min: Math.floor(Math.min(adapter.minValue * 0.95, adapter.minValue * 1.05))
// This seems to crash occationally // This seems to crash occationally
// onMinChanged: applyNiceNumbers(); // onMinChanged: applyNiceNumbers();
// onMaxChanged: applyNiceNumbers(); // onMaxChanged: applyNiceNumbers();
@ -354,6 +355,8 @@ MainViewBase {
SmartMeterChart { SmartMeterChart {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: width * .7 Layout.preferredHeight: width * .7
backgroundColor: Style.tileBackgroundColor
backgroundRoundness: Style.tileRadius
meters: producers meters: producers
title: qsTr("Total produced energy") title: qsTr("Total produced energy")
visible: producers.count > 0 visible: producers.count > 0