318 lines
12 KiB
QML
318 lines
12 KiB
QML
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
*
|
|
* Copyright (C) 2013 - 2024, nymea GmbH
|
|
* Copyright (C) 2024 - 2025, chargebyte austria GmbH
|
|
*
|
|
* This file is part of nymea-app.
|
|
*
|
|
* nymea-app is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* nymea-app 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 nymea-app. If not, see <https://www.gnu.org/licenses/>.
|
|
*
|
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
import QtQuick
|
|
import QtQuick.Controls
|
|
import QtQuick.Layouts
|
|
import QtQuick.Controls.Material
|
|
import Nymea
|
|
|
|
import "../components"
|
|
import "../utils"
|
|
|
|
ThingPageBase {
|
|
id: root
|
|
|
|
readonly property var powerStateType: thing.thingClass.stateTypes.findByName("power")
|
|
readonly property var powerState: thing.states.getState(powerStateType.id)
|
|
readonly property var powerActionType: thing.thingClass.actionTypes.findByName("power");
|
|
|
|
readonly property bool isOn: powerState && powerState.value === true
|
|
|
|
QtObject {
|
|
id: d
|
|
property var pendingRuleCreationId: -1
|
|
property var now: new Date()
|
|
}
|
|
Timer {
|
|
running: true
|
|
repeat: true
|
|
interval: 5000
|
|
onTriggered: {
|
|
d.now = new Date()
|
|
}
|
|
}
|
|
|
|
Component.onCompleted: {
|
|
cleanupRules();
|
|
}
|
|
|
|
function waterFor(minutes) {
|
|
var rule = engine.ruleManager.createNewRule();
|
|
var now = new Date();
|
|
var offDate = new Date(now.getTime() + (minutes * 60000));
|
|
|
|
rule.name = qsTr("Turn %1 off at %2").arg(root.thing.name).arg(Qt.formatDateTime(offDate))
|
|
|
|
var timeEvent = rule.timeDescriptor.timeEventItems.createNewTimeEventItem();
|
|
timeEvent.dateTime = offDate;
|
|
timeEvent.repeatingOption.repeatingMode = RepeatingOption.RepeatingModeNone;
|
|
rule.timeDescriptor.timeEventItems.addTimeEventItem(timeEvent);
|
|
|
|
var ruleAction = rule.actions.createNewRuleAction();
|
|
ruleAction.thingId = root.thing.id;
|
|
ruleAction.actionTypeId = root.thing.thingClass.actionTypes.findByName("power").id;
|
|
ruleAction.ruleActionParams.setRuleActionParam(ruleAction.actionTypeId, false);
|
|
rule.actions.addRuleAction(ruleAction)
|
|
|
|
d.pendingRuleCreationId = engine.ruleManager.addRule(rule);
|
|
}
|
|
|
|
function cleanupRules() {
|
|
print("cleaning up stale oneshot watering rules")
|
|
for (var i = 0; i < engine.tagsManager.tags.count; i++) {
|
|
var tag = engine.tagsManager.tags.get(i);
|
|
if (tag.tagId === "oneshot-watering") {
|
|
print("have a oneshot-watering tag")
|
|
// Delete it if the timer expired already
|
|
var rule = engine.ruleManager.rules.getRule(tag.ruleId)
|
|
if (rule.timeDescriptor.timeEventItems.get(0).dateTime < new Date()) {
|
|
print("need to cleanup rule:", tag.ruleId, rule.timeDescriptor.timeEventItems.get(0).dateTime)
|
|
engine.ruleManager.removeRule(tag.ruleId)
|
|
} else {
|
|
print("Rule still pending:", tag.ruleId)
|
|
}
|
|
// Also delete it if the irrigation is off already
|
|
if (root.powerState.value === false) {
|
|
engine.ruleManager.removeRule(tag.ruleId);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: engine.ruleManager
|
|
onAddRuleReply: {
|
|
if (commandId == d.pendingRuleCreationId) {
|
|
d.pendingRuleCreationId = -1
|
|
if (ruleError != RuleManager.RuleErrorNoError) {
|
|
var comp = Qt.createComponent("../components/ErrorDialog.qml")
|
|
var popup = comp.createObject(app, {errorCode: ruleError})
|
|
popup.open();
|
|
return;
|
|
}
|
|
// Tag the rule so we can clean identify it
|
|
engine.tagsManager.tagRule(ruleId, "oneshot-watering", root.thing.id)
|
|
|
|
// Off rule has been added. Turning on now
|
|
if (root.powerState.value === false) {
|
|
engine.thingManager.executeAction(root.thing.id, root.powerActionType.id, [{paramTypeId: root.powerActionType.id, value: true}])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: root.powerState
|
|
onValueChanged: cleanupRules()
|
|
}
|
|
|
|
LogsModelNg {
|
|
id: history
|
|
engine: _engine
|
|
thingId: root.thing.id
|
|
typeIds: [root.powerStateType.id]
|
|
property var lastWatering: count > 0 ? get(0).timestamp : null
|
|
live: true
|
|
}
|
|
|
|
TagsProxyModel {
|
|
id: tagsProxy
|
|
tags: engine.tagsManager.tags
|
|
filterTagId: "oneshot-watering"
|
|
filterValue: root.thing.id
|
|
}
|
|
|
|
ActionQueue {
|
|
id: actionQueue
|
|
thing: root.thing
|
|
stateName: "power"
|
|
}
|
|
|
|
GridLayout {
|
|
id: mainGrid
|
|
anchors.fill: parent
|
|
columns: app.landscape ? 2 : 1
|
|
|
|
CircleBackground {
|
|
Layout.fillWidth: true
|
|
Layout.fillHeight: true
|
|
Layout.leftMargin: Style.hugeMargins; Layout.rightMargin: Style.hugeMargins; Layout.topMargin: Style.hugeMargins
|
|
iconSource: "irrigation"
|
|
onColor: app.interfaceToColor("irrigation")
|
|
on: actionQueue.pendingValue || root.powerState.value
|
|
onClicked: actionQueue.sendValue(!root.powerState.value)
|
|
}
|
|
|
|
|
|
ColumnLayout {
|
|
Layout.fillWidth: true
|
|
Layout.fillHeight: true
|
|
Layout.margins: Style.margins
|
|
Item { Layout.fillWidth: true; Layout.fillHeight: true }
|
|
Label {
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: Style.margins; Layout.rightMargin: Style.margins
|
|
horizontalAlignment: Text.AlignHCenter
|
|
text: root.isOn ? qsTr("Watering since")
|
|
: history.lastWatering ? qsTr("Last watering")
|
|
: qsTr("This irrigation has not been used yet")
|
|
}
|
|
|
|
Label {
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: Style.margins; Layout.rightMargin: Style.margins
|
|
horizontalAlignment: Text.AlignHCenter
|
|
text: history.lastWatering ? Qt.formatDateTime(history.lastWatering) : ""
|
|
font.pixelSize: app.largeFont
|
|
color: Style.accentColor
|
|
}
|
|
Label {
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: Style.margins; Layout.rightMargin: Style.margins; Layout.bottomMargin: Style.margins
|
|
horizontalAlignment: Text.AlignHCenter
|
|
font.pixelSize: app.smallFont
|
|
text: {
|
|
if (!history.lastWatering) {
|
|
return ""
|
|
}
|
|
|
|
var n = Math.floor((d.now - history.lastWatering) / 60 / 1000)
|
|
|
|
n = Math.max(0, n)
|
|
|
|
if (root.isOn) {
|
|
if (n < 60) {
|
|
return qsTr("%n minute(s)", "", n);
|
|
}
|
|
n /= 60;
|
|
if (n < 24) {
|
|
return qsTr("%n hour(s)", "", n);
|
|
}
|
|
n /= 24;
|
|
return qsTr("%n day(s)", "", n);
|
|
|
|
}
|
|
|
|
if (n < 60) {
|
|
return qsTr("%n minute(s) ago", "", n);
|
|
}
|
|
n /= 60;
|
|
if (n < 24) {
|
|
return qsTr("%n hour(s) ago", "", n);
|
|
}
|
|
n /= 24;
|
|
return qsTr("%n day(s) ago", "", n);
|
|
}
|
|
}
|
|
|
|
Label {
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: Style.margins; Layout.rightMargin: Style.margins; Layout.topMargin: Style.margins
|
|
horizontalAlignment: Text.AlignHCenter
|
|
text: tagsProxy.count > 0 ?
|
|
//: Irrigation will be turned of at, e.g. 09:00
|
|
qsTr("Watering until")
|
|
//: Turn on irrigation for, e.g. 5 minutes
|
|
: root.isOn ? qsTr("Turn off in") : qsTr("Water for")
|
|
}
|
|
|
|
GridLayout {
|
|
columns: 3
|
|
visible: tagsProxy.count == 0
|
|
|
|
Button {
|
|
Layout.fillWidth: true
|
|
text: qsTr("1 minute")
|
|
onClicked: root.waterFor(1)
|
|
}
|
|
Button {
|
|
Layout.fillWidth: true
|
|
text: qsTr("2 minutes")
|
|
onClicked: root.waterFor(2)
|
|
}
|
|
Button {
|
|
Layout.fillWidth: true
|
|
text: qsTr("5 minutes")
|
|
onClicked: root.waterFor(5)
|
|
}
|
|
Button {
|
|
Layout.fillWidth: true
|
|
text: qsTr("15 minutes")
|
|
onClicked: root.waterFor(15)
|
|
}
|
|
Button {
|
|
Layout.fillWidth: true
|
|
text: qsTr("30 minutes")
|
|
onClicked: root.waterFor(30)
|
|
}
|
|
Button {
|
|
Layout.fillWidth: true
|
|
text: qsTr("1 hour")
|
|
onClicked: root.waterFor(60)
|
|
}
|
|
}
|
|
|
|
Label {
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: Style.margins; Layout.rightMargin: Style.margins
|
|
horizontalAlignment: Text.AlignHCenter
|
|
visible: tagsProxy.count > 0
|
|
font.pixelSize: app.largeFont
|
|
color: Style.accentColor
|
|
text: tagsProxy.count == 0 ? "" : Qt.formatDateTime(engine.ruleManager.rules.getRule(tagsProxy.get(0).ruleId).timeDescriptor.timeEventItems.get(0).dateTime)
|
|
}
|
|
Label {
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: Style.margins; Layout.rightMargin: Style.margins; Layout.bottomMargin: Style.margins
|
|
horizontalAlignment: Text.AlignHCenter
|
|
visible: tagsProxy.count > 0
|
|
font.pixelSize: app.smallFont
|
|
text: {
|
|
if (tagsProxy.count == 0) {
|
|
return ""
|
|
}
|
|
|
|
var end = engine.ruleManager.rules.getRule(tagsProxy.get(0).ruleId).timeDescriptor.timeEventItems.get(0).dateTime
|
|
var n = Math.floor((end - d.now) / 60 / 1000)
|
|
|
|
n = Math.max(0, n);
|
|
|
|
if (n < 60) {
|
|
return qsTr("%n minute(s) left", "", n);
|
|
}
|
|
n /= 60;
|
|
if (n < 24) {
|
|
return qsTr("%n hour(s) left", "", n);
|
|
}
|
|
n /= 24;
|
|
return qsTr("%n day(s) left", "", n);
|
|
}
|
|
}
|
|
|
|
Item { Layout.fillWidth: true; Layout.fillHeight: true }
|
|
}
|
|
}
|
|
}
|