317 lines
11 KiB
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
|
|
}
|
|
}
|