nymea-app-energy-overlay/common/ui/components/QuickModeControl.qml

317 lines
11 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-energy-overlay.
*
* nymea-app-energy-overlay 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-energy-overlay 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-energy-overlay. If not, see <https://www.gnu.org/licenses/>.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Qt5Compat.GraphicalEffects
import QtQuick.Shapes
import Nymea
import "qrc:/ui/components"
CircleControlBase {
id: root
QtObject {
id: internal
property int pendingCommandId: -1
property real pendingMaxChargingCurrent: -1
property real queuedMaxChargingCurrent: -1
property real visualMaxChargingCurrent: {
if (queuedMaxChargingCurrent != -1)
return queuedMaxChargingCurrent
if (pendingMaxChargingCurrent != -1)
return pendingMaxChargingCurrent
if (maxChargingCurrentState)
return maxChargingCurrentState.value
return sliderControl.value
}
function setMaxChargingCurrent(maxChargingCurrent) {
internal.pendingMaxChargingCurrent = maxChargingCurrent
if (internal.pendingCommandId === -1) {
internal.pendingCommandId = root.evCharger.executeAction("maxChargingCurrent", [{paramName: "maxChargingCurrent", value: maxChargingCurrent}])
console.warn("---> executing action", maxChargingCurrent, internal.pendingCommandId)
} else {
console.warn("---> queue", maxChargingCurrent, "[A]")
internal.queuedMaxChargingCurrent = maxChargingCurrent
}
}
}
Connections {
target: engine.thingManager
onExecuteActionReply: (commandId) => {
if (commandId === internal.pendingCommandId) {
internal.pendingCommandId = -1
internal.pendingMaxChargingCurrent = -1
console.warn("---> action execution finished", commandId)
if (internal.queuedMaxChargingCurrent >= 0) {
console.warn("---> execute queued value", internal.queuedMaxChargingCurrent)
internal.setMaxChargingCurrent(internal.queuedMaxChargingCurrent)
internal.queuedMaxChargingCurrent = -1
}
}
}
}
Connections {
target: root.maxChargingCurrentState
onValueChanged: {
if (internal.pendingCommandId >= 0)
return
root.sliderControl.value = root.maxChargingCurrentState.value
}
}
onChargingChanged: {
if (!chargingConfiguration)
return;
console.log("--> Charging changed", chargingConfiguration.evChargerThingId, charging)
}
onMaxChargingCurrentStateChanged: {
if (maxChargingCurrentState) {
// Set the value of the slider
root.sliderControl.value = maxChargingCurrentState.value
}
}
Connections {
target: sliderControl
onHandleReleased: {
console.log("Setting target value:", sliderControl.value,
"Limits are:",
"Wallbox-min:", root.maxChargingCurrentState.minValue,
"Car-min:", root.carMinChargingCurrentState ? root.carMinChargingCurrentState.value : "n/a",
"Wallbox-max:", maxChargingCurrentState.maxValue,
"Household-max:", nymeaEnergy.phasePowerLimit)
internal.setMaxChargingCurrent(sliderControl.value)
}
onValueChanged: {
internal.setMaxChargingCurrent(sliderControl.value)
}
}
Item {
id: centerControl
anchors.centerIn: parent
width: parent.width - 2 * root.handleSize - 2 * root.trackWidth
height: width
readonly property real radius: width / 2
readonly property real angleSpacing: 3
Shape {
id: upperSegment
width: parent.width
height: parent.height / 2
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
layer.enabled: true
layer.samples: 8
ShapePath {
fillColor: Style.darkGray
strokeColor: "transparent"
strokeWidth: 0
capStyle: ShapePath.FlatCap
PathAngleArc {
radiusX: centerControl.radius
radiusY: centerControl.radius
centerX: upperSegment.width / 2
centerY: upperSegment.height
startAngle: -180 + centerControl.angleSpacing
sweepAngle: 180 - 2 * centerControl.angleSpacing
}
}
ColumnLayout {
anchors.centerIn: parent
Label {
id: chargingCurrentLabel
Layout.alignment: Qt.AlignHCenter
text: internal.visualMaxChargingCurrent + "A"
color: "white"
font.bold: true
}
Label {
Layout.alignment: Qt.AlignHCenter
text: qsTr("Charging current")
color: "white"
}
}
}
DropShadow {
anchors.fill: upperSegment
source: upperSegment
radius: 4
samples: 9
color: Qt.rgba(Style.foregroundColor.r, Style.foregroundColor.g, Style.foregroundColor.b, 20)
}
Shape {
id: lowerSegment
width: parent.width
height: parent.height / 2
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
layer.enabled: true
layer.samples: 8
property bool buttonBusy: false
function setBusy() {
lowerSegment.buttonBusy = true
busyTimer.start()
}
Timer {
id: busyTimer
interval: 4000
repeat: false
onTriggered: lowerSegment.buttonBusy = false
}
ShapePath {
fillColor: root.chargerStateColor
strokeColor: "transparent"
strokeWidth: 0
capStyle: ShapePath.FlatCap
Behavior on fillColor {
ColorAnimation {
duration: Style.slowAnimationDuration
}
}
PathAngleArc {
radiusX: centerControl.radius
radiusY: centerControl.radius
centerX: upperSegment.width / 2
centerY: 0
startAngle: centerControl.angleSpacing
sweepAngle: 180 - 2 * centerControl.angleSpacing
}
}
BusyIndicator {
anchors.centerIn: parent
width: Style.largeIconSize
height: width
visible: lowerSegment.buttonBusy && root.enabled
running: lowerSegment.buttonBusy
}
ColorIcon {
id: powerButtonIcon
anchors.centerIn: parent
size: Style.bigIconSize
visible: !lowerSegment.buttonBusy && root.enabled
color: "white"
source: powerState && powerState.value === true ? "/images/pause-charging.svg" : "/images/start-charging.svg"
}
MouseArea {
id: powerButtonMouseArea
anchors.fill: parent
propagateComposedEvents: true
property point centerPt: Qt.point(powerButtonMouseArea.width / 2, 0)
enabled: !lowerSegment.buttonBusy && root.enabled
onPressed: (mouse) => {
var clickedDistance = Math.sqrt(Math.pow((powerButtonMouseArea.mouseX - powerButtonMouseArea.centerPt.x), 2) + Math.pow((powerButtonMouseArea.mouseY - powerButtonMouseArea.centerPt.y), 2));
if (clickedDistance >= centerControl.radius) {
mouse.accepted = false
}
}
onReleased: (mouse) => {
var clickedDistance = Math.sqrt(Math.pow((powerButtonMouseArea.mouseX - powerButtonMouseArea.centerPt.x), 2) + Math.pow((powerButtonMouseArea.mouseY - powerButtonMouseArea.centerPt.y), 2));
if (clickedDistance >= centerControl.radius) {
mouse.accepted = false
}
}
onClicked: (mouse) => {
// Note: we propagate the mouse event to the lower mouse areas, therefore we do not accept it here
// The slider mouse area needs the event too
// Check if we are within the lower segment
var clickedDistance = Math.sqrt(Math.pow((powerButtonMouseArea.mouseX - powerButtonMouseArea.centerPt.x), 2) + Math.pow((powerButtonMouseArea.mouseY - powerButtonMouseArea.centerPt.y), 2));
if (clickedDistance < centerControl.radius) {
console.log("Power clicked", !powerState.value)
lowerSegment.setBusy()
evCharger.executeAction("power", [{paramName: "power", value: !powerState.value}])
PlatformHelper.vibrate(PlatformHelper.HapticsFeedbackImpact)
} else {
mouse.accepted = false
}
}
}
}
DropShadow {
anchors.fill: lowerSegment
source: lowerSegment
radius: 4
samples: 9
color: Qt.rgba(Style.foregroundColor.r, Style.foregroundColor.g, Style.foregroundColor.b, 20)
}
}
ColorOverlay {
id: disableEffect
anchors.fill: root
source: root
color: "#ff808080"
opacity: 0.8
visible: !root.enabled
}
}