Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

326 change scaling for the power import export graph #384

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions components/ValueRange.qml
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@ import "/components/Utils.js" as Utils
QtObject {
property real value: NaN
readonly property real valueAsRatio: _valueAsRatio
property real maximumValue: NaN // if NaN, the max is dynamically adjusted to the maximum encountered value
property real maximumValue: NaN // if NaN, _max is dynamically adjusted to the maximum encountered value
readonly property real maximumSeen: isNaN(maximumValue) ? _max : _actualMaximum
readonly property real minimumSeen: _min
Copy link
Contributor

@blammit blammit Jul 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just realized, minimumSeen/maximumSeen is incorrect for these, as the _max can also be just the maximumValue, rather than the maximum value seen. So how about actualMinimum and actualMaximum instead? What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to handle the case where maximumValue !== NaN


property real _valueAsRatio: 0
property real _min: NaN
property real _max: isNaN(maximumValue) ? NaN : maximumValue
property real _actualMaximum: NaN

onValueChanged: {
// If value=NaN, or if only one value has been received, the min/max cannot yet be
Expand All @@ -27,7 +30,14 @@ QtObject {
return
}
_min = Math.min(_min, value)
_max = isNaN(maximumValue) ? Math.max(_max, value) : maximumValue

if (isNaN(maximumValue)) {
_max = Math.max(_max, value)
} else {
_max = maximumValue
_actualMaximum = Math.max(_actualMaximum, value)
}

if (!isNaN(_max) && value >= _max) {
_valueAsRatio = 1
return
Expand Down
12 changes: 12 additions & 0 deletions data/mock/config/MockDataSimulator.qml
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,18 @@ QtObject {
notificationsConfig.showToastNotification(notifType)
event.accepted = true
break
case Qt.Key_P:
Global.dcInputs.power = 0
Global.acInputs.generatorInput = null
if (event.modifiers & Qt.ControlModifier) {
Global.acInputs.power = 0
} else if (event.modifiers & Qt.ShiftModifier) {
Global.acInputs.power += 200
} else {
Global.acInputs.power -= 200
}
event.accepted = true
break
case Qt.Key_T:
root.timersActive = !root.timersActive
pageConfigTitle.text = "Timers on: " + root.timersActive
Expand Down
132 changes: 115 additions & 17 deletions pages/BriefMonitorPanel.qml
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ Column {
Column {
width: parent.width
spacing: Theme.geometry.briefPage.sidePanel.generator.columnSpacing
visible: !generatorColumn.visible && (!!Global.acInputs.power || !!Global.dcInputs.power)
visible: !generatorColumn.visible && !(isNaN(Global.acInputs.power) && isNaN(Global.dcInputs.power))

Item {
width: parent.width
Expand All @@ -181,35 +181,141 @@ Column {
LoadGraph {
id: gridGraph

/*
Graph A
<Power>
1000W 1.0 |
0.9 |
0.8 |
0.7 |
0.6 |
0.5 |
0.4 | ___________
0.3 | / \_________ (only shows imported power. 'graphShowsExportPower' is false)
0.2 |____/
imported power ^ 0.1 |
0W 0.0 |---------------------------> <Time>



Graph B
<Power>
1000 W 1.0 |
0.9 |
0.8 | ___________ (e.g. +600W)
0.7 | / \_______
imported power ^ 0.6 | /
0W 0.5 |..../....................... (shows imported and exported power. 'graphShowsExportPower' is true)
exported power v 0.4 | /
0.3 |__/ (e.g. -400W)
0.2 |
0.1 |
-1000W 0.0 |----------------------------> <Time>

blammit marked this conversation as resolved.
Show resolved Hide resolved
*/

// If we have ever seen power exported to the grid, the graph shows imported and exported power, as in Graph B.
// Otherwise, we only show imported power, as in Graph A.
property bool graphShowsExportPower: false

property real _oldGraphPowerRange: NaN

function addNewValue() {
graphShowsExportPower = inputsPower.minimumSeen < 0

// If we show export power, the minimum scale of the y axis goes from -1000W to +1000W
// If we don't show export power, the minimum scale of the y axis goes from 0W to +1000W
const minimumRangeWatts = graphShowsExportPower
? Theme.animation.loadGraph.minimumRange.watts * 2
: Theme.animation.loadGraph.minimumRange.watts
const peakPowerImportedOrExported = Math.max(Math.abs(inputsPower.maximumSeen), Math.abs(inputsPower.minimumSeen))
const graphPowerRange = graphShowsExportPower // This represents the difference in power between y=0 and y=1 on the graph
? Math.max(2 * peakPowerImportedOrExported, minimumRangeWatts)
: Math.max(inputsPower.maximumSeen, minimumRangeWatts)

// in Graph A, when the graph is at y=0.0, power is zero.
// in Graph B, when the graph is at y=0.5, power is zero.
const normalizedZeroPowerPoint = graphShowsExportPower ? 0.5 : 0.0
const normalizedPower = (inputsPower.value / graphPowerRange) + normalizedZeroPowerPoint

if (_oldGraphPowerRange != graphPowerRange) {
if (!isNaN(_oldGraphPowerRange)) {
const scalingFactor = graphPowerRange / _oldGraphPowerRange
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume _oldGraphPowerRange can't be zero here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's right, it starts at 1000 and can only increase in value

scaleHistoricalData(scalingFactor, normalizedZeroPowerPoint)
}
_oldGraphPowerRange = graphPowerRange
}
addValue(normalizedPower)
normalizedPowerSlider.value = normalizedPower
}

function scaleHistoricalData(scalingFactor, normalizedZeroPowerPoint) {
// If our historical power data was like this: [-1000, 1000, -1000, 1000, ...], and inputsPower.minimumSeen === -1000,
// and inputsPower.maximumSeen === 1000, our graph 'y' values would be like this: [-1, 0, -1, 1, ...]
// If we then got a new power import reading of 5000W, we need to scale down all of the historical data by
// a factor of 5, eg. [-0.2, 0.2, -0.2, 0.2, ...]
for (let i = 0; i < model.length; ++i) {
model[i] = normalizedZeroPowerPoint + (model[i] - normalizedZeroPowerPoint) / scalingFactor
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is model here a javascript property (an array), or a C++ thing? If it's a C++ thing, each element set here will cause whole-property writeback, as well as whole-property signal emission. Might not be a problem, but if it is, you might want to do something like:

var tempModel = model;
for (let i = 0; i < tempModel.length; ++i) tempModel[i] = normalizedZeroPowerPoint+(tempModel[i]-normalizedZeroPowerPoint)/scalingFactor;
model = tempModel;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ooooh, nice catch. It's a javascript array, and it's used as the model for 12 PathCubic objects in a ShapePath. I ran the debugger over it, and it seems to update the entire model, then it goes and updates each of the 12 PathCubic objects once. So, I think it's accidentally ok (I hadn't considered this). Would you prefer it updated anyway?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, it's fine as-is. I was just lazy and didn't check whether it was a javascript array.

}
}

onGraphShowsExportPowerChanged: {
// This can only ever change from false to true.
// If the system has never exported power, the graph values start at 0 (importing 0 power)
// and go to 1 (peak power import).
// The first time the system exports power, we need to scale and offset the historical power values.
// E.g. An old value of 0 (meaning importing 0 power) has to be changed to 0.5
// An old value of 0.2 (meaning importing 20% of peak power) has to be changed to 0.6,
// as the range has doubled, and the zero-point has changed from 0 to 0.5.
for (let i = 0; i < model.length; ++i) {
if (model[i] < 0.5) {
model[i] = model[i]/2 + 0.5
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again just flagging the model element set causing whole-property-writeback possibility

}
}
}

anchors {
right: parent.right
bottom: gridQuantityLabel.bottom
bottomMargin: gridQuantityLabel.bottomPadding
}
initialModelValue: 0.5

// For a system that sometimes exports power, 0.5 represents 0 power.
// For a system that only imports power, 0 represents 0 power.
initialModelValue: graphShowsExportPower ? 0.5 : 0.0
interval: Theme.geometry.briefPage.sidePanel.loadGraph.intervalMs
enableAnimation: root.opacity > 0 && BackendConnection.applicationVisible
warningThreshold: 0.5
belowThresholdFillColor1: Theme.color.briefPage.background
belowThresholdFillColor2: Theme.color.briefPage.background
belowThresholdBackgroundColor1: Theme.color.briefPage.sidePanel.loadGraph.nominal.gradientColor1
belowThresholdBackgroundColor2: Theme.color.briefPage.sidePanel.loadGraph.nominal.gradientColor2
belowThresholdFillColor1: graphShowsExportPower
? Theme.color.briefPage.background
: Theme.color.briefPage.sidePanel.loadGraph.nominal.gradientColor1
belowThresholdFillColor2: graphShowsExportPower
? Theme.color.briefPage.background
: Theme.color.briefPage.sidePanel.loadGraph.nominal.gradientColor2
belowThresholdBackgroundColor1: graphShowsExportPower
? Theme.color.briefPage.sidePanel.loadGraph.nominal.gradientColor1
: Theme.color.briefPage.background
belowThresholdBackgroundColor2: graphShowsExportPower
? Theme.color.briefPage.sidePanel.loadGraph.nominal.gradientColor2
: Theme.color.briefPage.background
horizontalGradientColor1: Theme.color.briefPage.background
horizontalGradientColor2: "transparent"

Timer {
interval: parent.interval
running: root.opacity > 0 && BackendConnection.applicationVisible
repeat: true
onTriggered: gridGraph.addValue(gridPower.normalizedValueAsRatio)
onTriggered: parent.addNewValue()
}
}
}
Slider {
id: normalizedPowerSlider

enabled: false // not interactive
width: parent.width
height: Theme.geometry.briefPage.sidePanel.generator.slider.height
value: gridPower.normalizedValueAsRatio
value: gridGraph.normalizedPower || 0
showHandle: false

Behavior on value { NumberAnimation { duration: Theme.animation.briefPage.sidePanel.sliderValueChange.duration } }
Expand Down Expand Up @@ -282,15 +388,7 @@ Column {
}

ValueRange {
id: gridPower

// If the current grid power is 0W, normalizedValueAsRatio equals 0.5, regardless of maximum / minimum seen power.
// If the maximum seen power is 20W, and the minimum seen power is 10W, and the current power is 10W,
// normalizedValueAsRatio equals 0.75.
// If the maximum seen power is -10W, and the minimum seen power is -100W, and the current power is -20W,
// normalizedValueAsRatio equals 0.4.
readonly property real normalizedValueAsRatio: 0.5 + (value / normalizedRange)
readonly property real normalizedRange: Math.abs(_max) > Math.abs(_min) ? 2*Math.abs(_max) : 2*Math.abs(_min)
id: inputsPower

value: Utils.sumRealNumbers(Global.acInputs.power, Global.dcInputs.power)
}
Expand Down
1 change: 0 additions & 1 deletion src/enums.h
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,6 @@ class Enums : public QObject
DcInputs_InputType_Alternator,
DcInputs_InputType_DcGenerator,
DcInputs_InputType_Wind
// DcInputs_InputType_Solar ?
};
Q_ENUM(DcInputs_InputType)

Expand Down
2 changes: 1 addition & 1 deletion src/veutil
1 change: 1 addition & 0 deletions themes/animation/Animation.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,6 @@
"animation.navBar.initialize.fade.duration": 400,
"animation.settings.radioButtonPage.autoClose.duration": 600,
"animation.loadGraph.model.length": 12,
"animation.loadGraph.minimumRange.watts": 1000,
"animation.solarHistoryErrorView.expand.duration": 100
}