WIP: drexel und weiss experience

This commit is contained in:
Michael Zanetti 2019-03-08 15:41:36 +01:00
parent effb5a137f
commit ae0a3279ec
12 changed files with 456 additions and 13 deletions

View File

@ -137,7 +137,7 @@ Connection *NymeaConnection::currentConnection() const
void NymeaConnection::sendData(const QByteArray &data)
{
if (connected()) {
qDebug() << "sending data:" << data;
// qDebug() << "sending data:" << data;
m_currentTransport->sendData(data);
} else {
qWarning() << "Connection: Not connected. Cannot send.";
@ -382,7 +382,7 @@ void NymeaConnection::updateActiveBearers()
QList<QNetworkConfiguration> configs = m_networkConfigManager->allConfigurations(QNetworkConfiguration::Active);
// qDebug() << "Network configuations:" << configs.count();
foreach (const QNetworkConfiguration &config, configs) {
qDebug() << "Candidate network config:" << config.name() << config.bearerTypeFamily() << config.bearerTypeName();
// qDebug() << "Candidate network config:" << config.name() << config.bearerTypeFamily() << config.bearerTypeName();
// NOTE: iOS doesn't correctly report bearer types. It'll be Unknown all the time. Let's hardcode it to WiFi for that...
#if defined(Q_OS_IOS)

View File

@ -101,6 +101,20 @@ void DevicesProxy::setFilterTagId(const QString &filterTag)
}
}
QString DevicesProxy::filterDeviceClassId() const
{
return m_filterDeviceClassId;
}
void DevicesProxy::setFilterDeviceClassId(const QString &filterDeviceClassId)
{
if (m_filterDeviceClassId != filterDeviceClassId) {
m_filterDeviceClassId = filterDeviceClassId;
emit filterDeviceClassIdChanged();
invalidateFilter();
}
}
QStringList DevicesProxy::shownInterfaces() const
{
return m_shownInterfaces;
@ -231,6 +245,11 @@ bool DevicesProxy::filterAcceptsRow(int source_row, const QModelIndex &source_pa
return false;
}
}
if (!m_filterDeviceClassId.isEmpty()) {
if (device->deviceClassId() != m_filterDeviceClassId) {
return false;
}
}
DeviceClass *deviceClass = m_engine->deviceManager()->deviceClasses()->getDeviceClass(device->deviceClassId());
if (!m_shownInterfaces.isEmpty()) {
bool foundMatch = false;

View File

@ -38,6 +38,7 @@ class DevicesProxy : public QSortFilterProxyModel
Q_PROPERTY(Engine* engine READ engine WRITE setEngine NOTIFY engineChanged)
Q_PROPERTY(DevicesProxy *parentProxy READ parentProxy WRITE setParentProxy NOTIFY parentProxyChanged)
Q_PROPERTY(QString filterTagId READ filterTagId WRITE setFilterTagId NOTIFY filterTagIdChanged)
Q_PROPERTY(QString filterDeviceClassId READ filterDeviceClassId WRITE setFilterDeviceClassId NOTIFY filterDeviceClassIdChanged)
Q_PROPERTY(QStringList shownInterfaces READ shownInterfaces WRITE setShownInterfaces NOTIFY shownInterfacesChanged)
Q_PROPERTY(QStringList hiddenInterfaces READ hiddenInterfaces WRITE setHiddenInterfaces NOTIFY hiddenInterfacesChanged)
Q_PROPERTY(QString nameFilter READ nameFilter WRITE setNameFilter NOTIFY nameFilterChanged)
@ -62,6 +63,9 @@ public:
QString filterTagId() const;
void setFilterTagId(const QString &filterTag);
QString filterDeviceClassId() const;
void setFilterDeviceClassId(const QString &filterDeviceClassId);
QStringList shownInterfaces() const;
void setShownInterfaces(const QStringList &shownInterfaces);
@ -86,6 +90,7 @@ signals:
void engineChanged();
void parentProxyChanged();
void filterTagIdChanged();
void filterDeviceClassIdChanged();
void shownInterfacesChanged();
void hiddenInterfacesChanged();
void nameFilterChanged();
@ -100,6 +105,7 @@ private:
Engine *m_engine = nullptr;
DevicesProxy *m_parentProxy = nullptr;
QString m_filterTagId;
QString m_filterDeviceClassId;
QStringList m_shownInterfaces;
QStringList m_hiddenInterfaces;
QString m_nameFilter;

View File

@ -37,7 +37,7 @@ public:
StateTypeIdRole
};
explicit States(QObject *parent = 0);
explicit States(QObject *parent = nullptr);
QList<State *> states();

View File

@ -170,5 +170,6 @@
<file>ui/images/sensors/presence.svg</file>
<file>ui/images/powersocket.svg</file>
<file>ui/images/dial.svg</file>
<file>ui/images/ventilation.svg</file>
</qresource>
</RCC>

View File

@ -165,5 +165,6 @@
<file>ui/thingconfiguration/ConfigureThingPage.qml</file>
<file>ui/connection/CertificateDialog.qml</file>
<file>ui/experiences/garagegates/Main.qml</file>
<file>ui/experiences/heating/Main.qml</file>
</qresource>
</RCC>

View File

@ -50,7 +50,6 @@ ApplicationWindow {
RootItem {
id: rootItem
anchors.fill: parent
anchors.topMargin: PlatformHelper.getSafeAreaMargins(app)
}
NymeaDiscovery {

View File

@ -24,8 +24,8 @@ ColumnLayout {
}
readonly property State deviceState: device && stateType ? device.states.getState(stateType.id) : null
readonly property double from: dial.stateType.minValue
readonly property double to: dial.stateType.maxValue
readonly property double from: dial.stateType ? dial.stateType.minValue : 0
readonly property double to: dial.stateType ? dial.stateType.maxValue : 100
readonly property double anglePerStep: maxAngle / dial.steps
readonly property double startAngle: -(dial.steps * dial.anglePerStep) / 2
@ -90,7 +90,7 @@ ColumnLayout {
Label {
id: topLabel
Layout.fillWidth: true
text: rotateMouseArea.currentValue + dial.stateType.unitString
text: rotateMouseArea.currentValue + (dial.stateType ? dial.stateType.unitString : "")
font.pixelSize: app.largeFont * 1.5
horizontalAlignment: Text.AlignHCenter
visible: dial.showValueLabel && dial.stateType !== null
@ -101,7 +101,6 @@ ColumnLayout {
Layout.fillWidth: true
Layout.fillHeight: true
Item {
id: innerDial
@ -110,7 +109,6 @@ ColumnLayout {
anchors.centerIn: parent
rotation: dial.startAngle
Rectangle {
anchors.fill: rotationButton
radius: height / 2
@ -167,7 +165,7 @@ ColumnLayout {
width: parent.width
height: width
radius: width / 2
color: dial.angleToValue(parent.rotation) <= dial.deviceState.value ? d.poweredColor : d.offColor
color: dial.deviceState && dial.angleToValue(parent.rotation) <= dial.deviceState.value ? d.poweredColor : d.offColor
Behavior on color { ColorAnimation { duration: 200 } }
}
}
@ -247,8 +245,8 @@ ColumnLayout {
dragging = false;
}
readonly property int decimals: dial.stateType.type.toLowerCase() === "int" ? 0 : 1
property var currentValue: dial.deviceState.value.toFixed(decimals)
readonly property int decimals: dial.stateType && dial.stateType.type.toLowerCase() === "int" ? 0 : 1
property var currentValue: dial.deviceState ? dial.deviceState.value.toFixed(decimals) : 0
property date lastVibration: new Date()
property int startX
property int startY

View File

@ -10,6 +10,7 @@ Item {
property alias from: slider.from
property alias to: slider.to
property alias stepSize: slider.stepSize
property alias snapMode: slider.snapMode
readonly property real rawValue: slider.value

View File

@ -23,6 +23,9 @@ DevicePageBase {
readonly property StateType boostStateType: device.deviceClass.stateTypes.findByName("boost")
readonly property State boostState: boostStateType ? device.states.getState(boostStateType.id) : null
Component.onCompleted: {
print("d:", root.device, root.targetTemperatureStateType, root.percentageStateType)
}
GridLayout {
anchors.fill: parent
@ -33,10 +36,11 @@ DevicePageBase {
id: dial
Layout.fillWidth: true
Layout.fillHeight: true
visible: root.targetTemperatureStateType || root.percentageStateType
// visible: root.targetTemperatureStateType || root.percentageStateType
device: root.device
stateType: root.targetTemperatureStateType ? root.targetTemperatureStateType : root.percentageStateType
}
Rectangle {

View File

@ -0,0 +1,385 @@
import QtQuick 2.3
import QtQuick.Layouts 1.2
import QtQuick.Controls 2.2
import QtQuick.Controls.Material 2.2
import "qrc:/ui/components"
import Nymea 1.0
import QtGraphicalEffects 1.0
Item {
id: root
readonly property string title: qsTr("Heating")
readonly property string icon: Qt.resolvedUrl("qrc:/ui/images/radiator.svg")
readonly property Device duwWpDevice: duwWpFilterModel.count > 0 ? duwWpFilterModel.get(0) : null
readonly property Device duwLuDevice: duwLuFilterModel.count > 0 ? duwLuFilterModel.get(0) : null
readonly property State temperatureState: duwWpDevice ? duwWpDevice.states.getState(duwWpDevice.deviceClass.stateTypes.findByName("temperature").id) : null
readonly property State targetTemperatureState: duwWpDevice ? duwWpDevice.states.getState(duwWpDevice.deviceClass.stateTypes.findByName("targetTemperature").id) : null
readonly property State ventilationModeState: duwLuDevice ? duwLuDevice.states.getState(duwLuDevice.deviceClass.stateTypes.findByName("ventilationMode").id) : null
function ventilationModeToSliderValue(ventilationMode) {
switch (ventilationMode) {
case "Automatic":
case "Party":
return 0
case "Manual level 0":
return 0;
case "Manual level 1":
return 1;
case "Manual level 2":
return 2;
case "Manual level 3":
return 3;
}
return 0;
}
function ventilationModeToUiMode(ventilationMode) {
switch (ventilationMode) {
case "Automatic":
return 0
case "Party":
return 1;
case "Manual level 0":
case "Manual level 1":
case "Manual level 2":
case "Manual level 3":
return 2;
}
}
function uiModeToVentilationMode(uiMode, sliderValue) {
switch (uiMode) {
case 0:
return "Automatic";
case 1:
return "Party";
case 2:
return "Manual level " + Math.floor(sliderValue)
}
}
function setVentilationMode(uiModeIndex, sliderIndex) {
var params =[];
var param = {};
param["paramTypeId"] = root.ventilationModeState.stateTypeId
param["value"] = root.uiModeToVentilationMode(uiModeIndex, sliderIndex)
params.push(param)
engine.deviceManager.executeAction(root.duwLuDevice.id, root.ventilationModeState.stateTypeId, params)
}
function setTargetTemp(targetTemp) {
// We don't want to spam with set value calls so we're going to queue them up and only send one at a time
if (d.pendingCallId != -1) {
d.queuedTargetTemp = targetTemp;
d.setTempPending = true;
return;
}
var params = []
var param = {}
param["paramTypeId"] = root.targetTemperatureState.stateTypeId
param["value"] = targetTemp
params.push(param)
d.pendingCallId = engine.deviceManager.executeAction(root.duwWpDevice.id, root.targetTemperatureState.stateTypeId, params)
d.setTempPending = false;
}
Connections {
target: engine.deviceManager
onExecuteActionReply: {
print("executeActionReply:", params["id"])
if (params["id"] === d.pendingCallId) {
d.pendingCallId = -1;
if (d.setTempPending) {
setTargetTemp(d.queuedTargetTemp)
}
}
}
}
QtObject {
id: d
property int pendingCallId: -1
property bool setTempPending: false
property real queuedTargetTemp: 0
}
DevicesProxy {
id: duwWpFilterModel
engine: _engine
filterDeviceClassId: "e548f962-92db-4110-8279-10fbcde35f93"
}
DevicesProxy {
id: duwLuFilterModel
engine: _engine
filterDeviceClassId: "0de8e21e-392a-4790-a78a-b1a7eaa7571b"
}
EmptyViewPlaceholder {
anchors.centerIn: parent
width: parent.width - app.margins * 2
text: qsTr("There is no drexel und weiss heating system set up yet.")
imageSource: "qrc:/ui/images/radiator.svg"
buttonText: qsTr("Set up now")
visible: duwWpFilterModel.count === 0 && !engine.deviceManager.fetchingData
}
Item {
id: mainView
anchors.fill: parent
visible: root.duwWpDevice !== null
ColumnLayout {
anchors.fill: parent
anchors.margins: app.margins
RowLayout {
spacing: app.margins
ColorIcon {
Layout.preferredHeight: app.iconSize
Layout.preferredWidth: app.iconSize
name: "qrc:/ui/images/weathericons/wind.svg"
color: app.accentColor
}
Led {
}
}
Label {
text: qsTr("Current temperature")
font.pixelSize: app.smallFont
}
RowLayout {
ColorIcon {
Layout.preferredHeight: app.iconSize
Layout.preferredWidth: app.iconSize
name: "qrc:/ui/images/sensors/temperature.svg"
color: app.accentColor
}
Label {
text: root.temperatureState ? root.temperatureState.value.toFixed(1) + "°C" : "N/A"
Layout.fillWidth: true
font.pixelSize: app.largeFont * 2
}
}
Item {
Layout.preferredHeight: app.margins * 2
Layout.fillWidth: true
}
Label {
text: qsTr("Temperature, °C")
font.pixelSize: app.largeFont
}
Label {
text: (d.pendingCallId !== -1 || d.setTempPending) ? d.queuedTargetTemp.toFixed(1) :
root.targetTemperatureState ? root.targetTemperatureState.value.toFixed(1) : "N/A"
font.pixelSize: app.largeFont * 4
}
Item {
Layout.preferredHeight: app.margins * 2
Layout.fillWidth: true
}
ColorIcon {
Layout.preferredHeight: app.iconSize * 1.5
Layout.preferredWidth: height
Layout.leftMargin: width
color: app.accentColor
name: "qrc:/ui/images/share.svg"
}
Label {
text: qsTr("Automate this thing")
color: app.accentColor
font.pixelSize: app.smallFont
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
}
RowLayout {
Layout.leftMargin: parent.width * .05
Layout.rightMargin: parent.width * .2
spacing: app.margins
ColorIcon {
Layout.preferredHeight: app.iconSize
Layout.preferredWidth: app.iconSize
color: app.accentColor
name: "qrc:/ui/images/ventilation.svg"
}
RowLayout {
Layout.fillWidth: true
Layout.maximumHeight: app.iconSize
spacing: 0
Repeater {
model: ListModel {
ListElement { text: qsTr("Auto") }
ListElement { text: qsTr("Party") }
ListElement { text: qsTr("Manual") }
}
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
border.width: 1
border.color: app.accentColor
color: root.ventilationModeState && root.ventilationModeToUiMode(root.ventilationModeState.value) === index ? app.accentColor : "transparent"
Label {
anchors.centerIn: parent
text: model.text
font.pixelSize: app.smallFont
}
MouseArea {
anchors.fill: parent
onClicked: {
root.setVentilationMode(index, ventilationSlider.value)
}
}
}
}
}
}
Slider {
id: ventilationSlider
Layout.fillWidth: true
Layout.leftMargin: parent.width * .05
Layout.rightMargin: parent.width * .05
from: 0
to: 4
stepSize: 1
live: false
snapMode: Slider.SnapAlways
enabled: root.ventilationModeState && root.ventilationModeToUiMode(root.ventilationModeState.value) === 2
opacity: enabled ? 1 : .2
value: root.ventilationModeState ? root.ventilationModeToSliderValue(root.ventilationModeState.value) : 0
onMoved: root.setVentilationMode(2, ventilationSlider.value)
}
ProgressButton {
imageSource: "qrc:/ui/images/system-shutdown.svg"
Layout.preferredHeight: app.iconSize * 1.5
Layout.preferredWidth: height
Layout.alignment: Qt.AlignHCenter
}
Label {
text: qsTr("Hold to turn off")
font.pixelSize: app.smallFont
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
}
}
Item {
height: parent.height * .85
width: height
anchors.left: parent.right
anchors.leftMargin: -width * .25
anchors.top: parent.top
anchors.topMargin: -height * .05
z: -1
Rectangle {
id: outerRadius
anchors.fill: parent
radius: width / 2
border.width: 3
color: "transparent"
border.color: app.accentColor
}
Glow {
anchors.fill: parent
source: outerRadius
// color: "#f45b69"
color: Qt.rgba(app.accentColor.r, app.accentColor.g, app.accentColor.b, .5)
radius: 8
samples: 17
spread: 0.5
}
Rectangle {
id: innerRadius
anchors.fill: parent
anchors.margins: parent.width * .02
radius: width / 2
border.width: 2
color: "transparent"
border.color: app.accentColor
Repeater {
id: ticksRepeater
model: 180
Item {
height: isBold ? 3 : 2
width: parent.width - 2
anchors.centerIn: parent
rotation: index * 360 / ticksRepeater.count
readonly property int isBold: index % 10 === 0
// Rectangle { anchors.fill: parent; color: "blue" }
Rectangle { height: parent.height; width: parent.isBold ? 20 : 10; color: app.accentColor }
}
}
}
MouseArea {
anchors.fill: parent
property real startAnglePress
property real startAngleDial
property real startTemp
onPressed: {
startAnglePress = calculateAngle(mouseX, mouseY)
startAngleDial = innerRadius.rotation
startTemp = root.targetTemperatureState.value
print("angle:", calculateAngle(mouseX, mouseY))
}
onPositionChanged: {
var currentAngle = calculateAngle(mouseX, mouseY)
var angleDiff = currentAngle - startAnglePress
var tempDiff = Math.round(angleDiff / 2) / 10
var newTemp = startTemp + tempDiff
innerRadius.rotation = startAngleDial + angleDiff
print("new degree value", newTemp)
root.setTargetTemp(newTemp);
}
function calculateAngle(mouseX, mouseY) {
// transform coords to center of dial
mouseX -= width / 2
mouseY -= height / 2
var rad = Math.atan(mouseY / mouseX);
var angle = rad * 180 / Math.PI
angle += 90;
if (mouseX < 0 && mouseY >= 0) angle = 180 + angle;
if (mouseX < 0 && mouseY < 0) angle = 180 + angle;
return angle;
}
}
}
}
}

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="svg4874" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 96 96" style="enable-background:new 0 0 96 96;" xml:space="preserve">
<style type="text/css">
.st0{fill:#808080;}
</style>
<title>settings</title>
<path class="st0" d="M56.4,79.1c-1.5,0-3-0.3-4.4-0.9c-3.2-1.4-4.8-4.4-4.4-8.2c0.4-2.6,1.5-5.1,3.2-7.2c1.3-1.9,2.3-3.4,2.2-5.5
c-1.5,0.8-3.2,1.2-4.9,1.2c-1.5,3.1-3.7,5.8-6.4,7.9c-2.6,2-11.7,7.8-20.7-0.5c-3.7-3.4-5.1-9.7-3.1-14.1c1.4-3.2,4.4-4.8,8.2-4.4
c2.6,0.4,5.1,1.5,7.2,3.2c1.9,1.3,3.4,2.3,5.4,2.2c-0.8-1.5-1.2-3.2-1.2-4.9c-3.1-1.5-5.8-3.7-7.9-6.4c-2-2.6-7.8-11.7,0.5-20.7
c3.4-3.7,9.7-5.1,14.1-3.1c3.2,1.4,4.8,4.4,4.4,8.2c-0.4,2.6-1.5,5.1-3.2,7.2c-1.3,1.9-2.3,3.4-2.2,5.5c1.9-1,4.1-1.4,6.3-1.1
c1,0.1,1.6,1,1.5,2c-0.1,1-1,1.6-2,1.5l0,0c-1.5-0.2-3.1,0.1-4.4,0.9c-0.5,0.3-0.9,0.6-1.3,1c-0.7,0.7-1.8,0.6-2.5-0.1
c-0.2-0.2-0.3-0.4-0.4-0.6c-0.2-0.6-0.4-1.2-0.5-1.8c-0.9-4.1,0.9-6.8,2.6-9.2c1.2-1.7,2.3-3.4,2.6-5.6c0.2-2.3-0.5-3.8-2.3-4.6
c-3-1.3-7.7-0.3-10.1,2.3c-6.2,6.8-2.2,13.5-0.3,16.2c1.9,2.6,4.6,4.6,7.6,5.8c0.8,0.3,1.2,1.1,1.1,1.9c-0.2,1.5,0.1,3.1,0.9,4.4
c0.3,0.5,0.6,0.9,1,1.3c0.7,0.7,0.6,1.8-0.1,2.5c-0.2,0.2-0.4,0.3-0.6,0.4c-0.6,0.2-1.2,0.4-1.8,0.5c-4.1,0.9-6.8-0.9-9.2-2.6
c-1.7-1.2-3.4-2.3-5.6-2.6c-2.3-0.2-3.8,0.5-4.6,2.3c-1.3,3-0.3,7.7,2.3,10.1c6.8,6.2,13.6,2.2,16.2,0.3c2.6-1.9,4.6-4.6,5.8-7.6
c0.3-0.8,1.1-1.2,1.9-1.1c1.5,0.2,3.1-0.1,4.4-0.9c0.5-0.3,0.9-0.6,1.3-1c0.7-0.7,1.8-0.6,2.5,0.1c0.2,0.2,0.3,0.4,0.4,0.6
c0.2,0.6,0.4,1.2,0.5,1.8c0.9,4.1-0.9,6.8-2.6,9.1c-1.2,1.7-2.3,3.4-2.6,5.6c-0.2,2.3,0.5,3.8,2.3,4.6c3,1.3,7.7,0.3,10.1-2.3
c6.2-6.8,2.2-13.6,0.3-16.2c-1.9-2.6-4.6-4.6-7.6-5.8c-0.8-0.3-1.2-1.1-1.1-1.9c0.2-1.5-0.1-3.1-0.9-4.4c-0.3-0.5-0.6-0.9-1-1.3
c-0.7-0.7-0.6-1.8,0.1-2.5c0.2-0.2,0.4-0.3,0.6-0.4c0.6-0.2,1.2-0.4,1.8-0.5c4.1-0.9,6.8,0.9,9.1,2.6c1.7,1.2,3.4,2.3,5.6,2.6
c2.3,0.2,3.8-0.5,4.6-2.3c1.3-3,0.3-7.7-2.3-10.1c-6.8-6.2-13.6-2.2-16.2-0.3c-1,0.8-1.9,1.7-2.8,2.6c-0.6,0.7-1.7,0.8-2.5,0.2
c-0.7-0.6-0.8-1.7-0.2-2.5c1-1.1,2.1-2.2,3.3-3.1c2.6-2,11.7-7.8,20.7,0.5c3.7,3.4,5.1,9.7,3.1,14.1c-1.4,3.2-4.4,4.8-8.2,4.4
c-2.6-0.4-5.1-1.5-7.2-3.2c-1.9-1.3-3.4-2.3-5.5-2.2c0.8,1.5,1.2,3.2,1.2,4.9c3.1,1.5,5.8,3.7,7.9,6.4c2,2.6,7.8,11.7-0.5,20.7
C63.5,77.7,60,79.1,56.4,79.1z"/>
<path class="st0" d="M48,89.8C24.9,89.8,6.2,71.1,6.2,48C6.2,24.9,24.9,6.2,48,6.2c23.1,0,41.8,18.7,41.8,41.8
C89.8,71.1,71.1,89.8,48,89.8z M48,9.7C26.8,9.7,9.7,26.8,9.7,48S26.8,86.3,48,86.3S86.3,69.2,86.3,48c0,0,0,0,0,0
C86.3,26.8,69.2,9.7,48,9.7z"/>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB