Implemented a visualization screen for various board parameters

This commit is contained in:
Yury Shuvakin
2022-08-25 07:59:16 +03:00
parent 8abae06275
commit d1ba61acd8
19 changed files with 675 additions and 15 deletions

View File

@@ -9,7 +9,7 @@ Button {
contentItem: Text {
text: control.text
font.pixelSize: 16
font.weight: Font.ExtraBold
font.weight: Font.Bold
opacity: enabled ? 1.0 : 0.3
color: Palette.alternativeTextColor
horizontalAlignment: Text.AlignHCenter

100
qml/Controls/ChartView.qml Normal file
View File

@@ -0,0 +1,100 @@
import QtQuick 2.12
import QtCharts 2.3
import Utils 1.0
ChartView {
id: chart
antialiasing: true
backgroundColor: Palette.backgroundColor
margins.left: 20
legend.visible: false
legend.alignment: Qt.AlignRight
legend.markerShape: Legend.MarkerShapeCircle
property AbstractAxis xAxis: valueAxisX
property AbstractAxis yAxis: valueAxisY
property int defaultXMax: 10
property int defaultYMax: 1
property bool autoScaling: true
ValueAxis {
id: valueAxisX
min: 0
max: chart.defaultXMax
tickCount: 8
gridVisible: false
gridLineColor: Palette.borderColor
labelsColor: Palette.contentTextColor
labelsFont.pixelSize: 18
color: Palette.borderColor
titleVisible: false
}
ValueAxis {
id: valueAxisY
min: 0
max: chart.defaultYMax
tickCount: 6
labelsColor: Palette.contentTextColor
labelsFont.pixelSize: 18
color: Palette.borderColor
titleVisible: false
}
MouseArea {
id: chartMouseAreaA
anchors.fill: parent
acceptedButtons: Qt.LeftButton
property real lastMouseX: 0
property real lastMouseY: 0
property real scrollThreshold: 0
onMouseXChanged: {
if ((mouse.buttons & Qt.LeftButton) == Qt.LeftButton) {
if (mouseX - lastMouseX > scrollThreshold) {
chart.scrollLeft(mouseX - lastMouseX)
lastMouseX = mouseX
chart.autoScaling = false
} else if (mouseX - lastMouseX < scrollThreshold) {
chart.scrollRight(lastMouseX - mouseX)
lastMouseX = mouseX
chart.autoScaling = false
}
}
}
onMouseYChanged: {
if ((mouse.buttons & Qt.LeftButton) == Qt.LeftButton) {
if (mouseY - lastMouseY > scrollThreshold) {
chart.scrollUp(mouseY - lastMouseY)
lastMouseY = mouseY
chart.autoScaling = false
} else if (mouseY - lastMouseY < scrollThreshold) {
chart.scrollDown(lastMouseY - mouseY)
lastMouseY = mouseY
chart.autoScaling = false
}
}
}
onPressed: {
if (mouse.button === Qt.LeftButton) {
lastMouseX = mouseX
lastMouseY = mouseY
}
}
onWheel: {
if (wheel.angleDelta.y > 0) {
chart.zoomIn()
chart.autoScaling = false
} else {
chart.zoomOut()
chart.autoScaling = false
}
}
}
}

View File

@@ -28,7 +28,7 @@ CheckBox {
contentItem: Text {
text: control.text
font.pixelSize: 18
font.weight: Font.ExtraBold
font.weight: Font.Bold
color: Palette.textColor
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter

View File

@@ -6,6 +6,6 @@ import Utils 1.0
Label {
color: Palette.contentTextColor
font.pixelSize: 18
font.weight: Font.Medium
font.weight: Font.Normal
elide: Text.ElideRight
}

View File

@@ -6,7 +6,7 @@ import Utils 1.0
Label {
color: Palette.textColor
font.pixelSize: 18
font.weight: Font.ExtraBold
font.weight: Font.Bold
elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter

View File

@@ -7,7 +7,7 @@ Label {
id: control
color: Palette.selectedTextColor
font.pixelSize: 18
font.weight: Font.Medium
font.weight: Font.Normal
font.underline : true
elide: Text.ElideRight

View File

@@ -9,7 +9,7 @@ Button {
contentItem: Text {
text: control.text
font.pixelSize: 16
font.weight: Font.ExtraBold
font.weight: Font.Bold
opacity: enabled ? 1.0 : 0.3
color: control.hovered ? Palette.alternativeTextColor : Palette.textColor
horizontalAlignment: Text.AlignHCenter

View File

@@ -6,6 +6,6 @@ import Utils 1.0
Label {
color: Palette.textColor
font.pixelSize: 18
font.weight: Font.ExtraBold
font.weight: Font.Bold
elide: Text.ElideRight
}

20
qml/Controls/TabBar.qml Normal file
View File

@@ -0,0 +1,20 @@
import QtQuick 2.12
import QtQuick.Controls 2.12
import Utils 1.0
TabBar {
id: control
background: Rectangle {
color: Palette.backgroundColor
Rectangle {
height: 1
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
color: Palette.borderColor
}
}
}

View File

@@ -0,0 +1,44 @@
import QtQuick 2.12
import QtQuick.Controls 2.12
import Utils 1.0
TabButton {
id: control
contentItem: Text {
text: control.text
font.pixelSize: 18
font.weight: Font.Bold
opacity: enabled ? 1.0 : 0.3
color: control.checked ? Palette.selectedTextColor : Palette.textColor
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
background: Item {
implicitWidth: 150
implicitHeight: 58
Rectangle {
property bool selected: control.checked
height: 2
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
color: Palette.alternativeBackgroundColor
visible: selected
// Behavior on selected {
// PropertyAnimation {
// properties: "selected";
// easing.type: Easing.OutElastic;
// easing.amplitude: 0.2;
// easing.period: 0.2
// }
// }
}
}
}

View File

@@ -6,6 +6,6 @@ import Utils 1.0
Label {
color: Palette.selectedTextColor
font.pixelSize: 28
font.weight: Font.ExtraBold
font.weight: Font.Bold
elide: Text.ElideRight
}

View File

@@ -16,3 +16,6 @@ LabelWithBackground 1.0 LabelWithBackground.qml
CheckBox 1.0 CheckBox.qml
LineSeparator 1.0 LineSeparator.qml
LinkLabel 1.0 LinkLabel.qml
TabButton 1.0 TabButton.qml
TabBar 1.0 TabBar.qml
ChartView 1.0 ChartView.qml

View File

@@ -133,7 +133,7 @@ ApplicationWindow {
Label {
text: modelData.text
font.weight: Font.ExtraBold
font.weight: Font.Bold
color: Palette.alternativeTextColor
visible: !pane.minimized
}
@@ -173,12 +173,13 @@ ApplicationWindow {
qsTr("AKB monitor"),
qsTr("Cell monitor"),
qsTr("BMS settings"),
qsTr("Visualization"),
]
Label {
text: topBar.labels[stack.currentIndex]
font.pixelSize: 38
font.weight: Font.ExtraBold
font.weight: Font.Bold
}
Item {
@@ -207,6 +208,9 @@ ApplicationWindow {
Screens.BmsSettingsScreen {
}
Screens.VisualizationScreen {
}
}
}
}

View File

@@ -660,7 +660,7 @@ RowLayout {
function getValues() {
if (BmsInterface.isPortConnected() && visible) {
BmsInterface.bmsConfig().updateDone()
BmsInterface.commands().getBMSconf()
}
}
}

View File

@@ -70,7 +70,7 @@ Item {
}
Controls.SubtitleLabel {
text: index
text: index + 1
color: Palette.tableHeaderTextColor
Layout.preferredWidth: 25
Layout.alignment: Qt.AlignCenter

View File

@@ -0,0 +1,479 @@
import QtQuick 2.12
import QtQuick.Layouts 1.12
import QtCharts 2.3
import Controls 1.0 as Controls
import Cubo 1.0
import Utils 1.0
ColumnLayout {
spacing: 20
property int currentTime: 0
property var voltageData: []
property var currentData: []
property var batteryTemperatureData: []
property var bmsTemperatureData: []
property var cellVoltageData: []
property var cellListData: []
Controls.Frame {
padding: 20
leftPadding: 1
rightPadding: 1
ColumnLayout {
anchors.fill: parent
Controls.TabBar {
id: bar
implicitWidth: 300
Controls.TabButton {
text: qsTr("Voltage")
width: bar.width / 6 * 0.9
}
Controls.TabButton {
text: qsTr("Current")
width: bar.width / 6 * 0.9
}
Controls.TabButton {
text: qsTr("Battery temperature")
width: bar.width / 6 * 1.2
}
Controls.TabButton {
text: qsTr("BMS temperature")
width: bar.width / 6 * 1.2
}
Controls.TabButton {
text: qsTr("Cell voltage")
width: bar.width / 6 * 0.8
}
Controls.TabButton {
text: qsTr("Cell list")
width: bar.width / 6 * 0.9
}
Layout.fillWidth: true
}
StackLayout {
width: parent.width
currentIndex: bar.currentIndex
Component {
id: legendDelegate
RowLayout {
width: ListView.view.width
height: 60
property Controls.ChartView chartItem: undefined
property color seriesColor: "black"
Controls.CheckBox {
id: checkSeries
checked: true
onCheckedChanged: {
if (checked) {
chartItem.series(modelData).color = seriesColor
} else {
chartItem.series(modelData).color = "transparent"
}
}
Layout.fillWidth: true
}
Rectangle {
color: seriesColor
radius: width / 2
Layout.preferredWidth: 18
Layout.preferredHeight: 18
Layout.rightMargin: 20
}
Component.onCompleted: {
chartItem = ListView.view.chartItem
seriesColor = chartItem.series(modelData).color
checkSeries.text = chartItem.series(modelData).name
}
}
}
Component {
id: chartComponent
RowLayout {
property string xLabel: ""
property string yLabel: ""
property Controls.ChartView chart: chartView
property int seriesCount: 0
ColumnLayout {
spacing: -20
Controls.ContentLabel {
z: 1
text: yLabel
Layout.topMargin: 20
Layout.leftMargin: 30
Layout.alignment: Qt.AlignLeft
}
Controls.ChartView {
id: chartView
Layout.fillWidth: true
Layout.fillHeight: true
}
Controls.ContentLabel {
z: 1
text: xLabel
Layout.alignment: Qt.AlignRight
Layout.rightMargin: 30
Layout.bottomMargin: 10
}
Layout.fillWidth: true
Layout.fillHeight: true
}
ListView {
clip: true
model: seriesCount
delegate: legendDelegate
property Controls.ChartView chartItem: chart
Layout.preferredWidth: 300
Layout.fillHeight: true
}
}
}
Loader {
id: voltageLoader
sourceComponent: chartComponent
Layout.fillWidth: true
Layout.fillHeight: true
Component.onCompleted: {
item.xLabel = Qt.binding(function() { return qsTr("Time, s") })
item.yLabel = Qt.binding(function() { return qsTr("Voltage, V") })
}
}
Loader {
id: currentLoader
sourceComponent: chartComponent
Layout.fillWidth: true
Layout.fillHeight: true
Component.onCompleted: {
item.xLabel = Qt.binding(function() { return qsTr("Time, s") })
item.yLabel = Qt.binding(function() { return qsTr("Current, A") })
}
}
Loader {
id: batteryTemperatureLoader
sourceComponent: chartComponent
Layout.fillWidth: true
Layout.fillHeight: true
Component.onCompleted: {
item.xLabel = Qt.binding(function() { return qsTr("Time, s") })
item.yLabel = Qt.binding(function() { return qsTr("Temperature, °C") })
}
}
Loader {
id: bmsTemperatureLoader
sourceComponent: chartComponent
Layout.fillWidth: true
Layout.fillHeight: true
Component.onCompleted: {
item.xLabel = Qt.binding(function() { return qsTr("Time, s") })
item.yLabel = Qt.binding(function() { return qsTr("Temperature, °C") })
}
}
Loader {
id: cellVoltageLoader
sourceComponent: chartComponent
Layout.fillWidth: true
Layout.fillHeight: true
Component.onCompleted: {
item.xLabel = Qt.binding(function() { return qsTr("Time, s") })
item.yLabel = Qt.binding(function() { return qsTr("Voltage, V") })
}
}
Loader {
id: cellListLoader
sourceComponent: chartComponent
Layout.fillWidth: true
Layout.fillHeight: true
Component.onCompleted: {
item.xLabel = Qt.binding(function() { return qsTr("Time, s") })
item.yLabel = Qt.binding(function() { return qsTr("Voltage, V") })
}
}
Layout.fillWidth: true
Layout.fillHeight: true
}
}
Layout.fillWidth: true
Layout.fillHeight: true
}
RowLayout {
spacing: 20
Controls.Button {
text: timer.running ? qsTr("Pause data collection") : qsTr("Resume data collection")
onClicked: {
if (timer.running)
timer.stop()
else
timer.start()
}
}
Controls.Button {
text: qsTr("Clear data")
onClicked: {
currentTime = 0
clearData(voltageData, voltageLoader)
clearData(currentData, currentLoader)
clearData(batteryTemperatureData, batteryTemperatureLoader)
clearData(bmsTemperatureData, bmsTemperatureLoader)
clearData(cellVoltageData, cellVoltageLoader)
clearData(cellListData, cellListLoader)
resetZoomButton.clicked()
}
function clearData(dataObject, chartObject) {
for (var i = 0; i < dataObject.length; ++i) {
dataObject[i].splice(0, dataObject[i].length)
var series = chartObject.item.chart.series(i)
series.removePoints(0, series.count)
}
}
}
Item {
Layout.fillWidth: true
}
Controls.Button {
id: resetZoomButton
text: qsTr("Reset zoom")
onClicked: {
resetChartZoom(voltageData, voltageLoader)
resetChartZoom(currentData, currentLoader)
resetChartZoom(batteryTemperatureData, batteryTemperatureLoader)
resetChartZoom(bmsTemperatureData, bmsTemperatureLoader)
resetChartZoom(cellVoltageData, cellVoltageLoader)
resetChartZoom(cellListData, cellListLoader)
}
function resetChartZoom(dataObject, chartObject) {
chartObject.item.chart.zoomReset()
chartObject.item.chart.autoScaling = true
chartObject.item.chart.axes[1].min = closestMinValue(getMinValue(dataObject), 0.05)
chartObject.item.chart.axes[1].max = closestMaxValue(getMaxValue(dataObject), 0.05)
chartObject.item.chart.axes[0].min = 0
chartObject.item.chart.axes[0].max = Math.max(chartObject.item.chart.defaultXMax, chartObject.item.chart.series(0).at(chartObject.item.chart.series(0).count - 1).x)
}
function getMaxValue(dataObject, seriesIndex) {
if (dataObject.length === 0 || dataObject[0].length === 0) {
return 0
}
var max = dataObject[0][0].y
for (var i = 0; i < dataObject.length; ++i) {
for (var j = 0; j < dataObject[i].length; ++j) {
if (dataObject[i][j].y > max) {
max = dataObject[i][j].y
}
}
}
return max
}
function getMinValue(dataObject) {
if (dataObject.length === 0 || dataObject[0].length === 0) {
return 0
}
var min = dataObject[0][0].y
for (var i = 0; i < dataObject.length; ++i) {
for (var j = 0; j < dataObject[i].length; ++j) {
if (dataObject[i][j].y < min) {
min = dataObject[i][j].y
}
}
}
return min
}
}
}
Connections {
target: BmsInterface.commands()
onValuesReceived: {
addValueToChart(values.packVoltage, voltageData, voltageLoader, 0, 0.05)
addValueToChart(values.packCurrent, currentData, currentLoader, 0, 0.05)
addValueToChart(values.tempBMSHigh, batteryTemperatureData, batteryTemperatureLoader, 0, 0.05)
addValueToChart(values.tempBMSAverage, batteryTemperatureData, batteryTemperatureLoader, 1, 0.05)
addValueToChart(values.tempBMSLow, batteryTemperatureData, batteryTemperatureLoader, 2, 0.05)
addValueToChart(values.tempBattHigh, bmsTemperatureData, bmsTemperatureLoader, 0, 0.05)
addValueToChart(values.tempBattAverage, bmsTemperatureData, bmsTemperatureLoader, 1, 0.05)
addValueToChart(values.tempBattLow, bmsTemperatureData, bmsTemperatureLoader, 2, 0.05)
addValueToChart(values.cVHigh, cellVoltageData, cellVoltageLoader, 0, 0.05)
addValueToChart(values.cVAverage, cellVoltageData, cellVoltageLoader, 1, 0.05)
addValueToChart(values.cVLow, cellVoltageData, cellVoltageLoader, 2, 0.05)
}
onCellsReceived: {
for (var i = cellListLoader.item.chart.count; i < cellCount; ++i) {
cellListLoader.item.chart.createSeries(ChartView.SeriesTypeLine, qsTr("Cell #") + (i + 1).toString(),
cellListLoader.item.chart.xAxis, cellListLoader.item.chart.yAxis)
cellListLoader.item.chart.axes[0].max = 10
cellListLoader.item.seriesCount = cellListLoader.item.chart.count
}
for (var j = 0; j < cellCount; ++j) {
addValueToChart(cellVoltageArray[j], cellListData, cellListLoader, j, 0.05)
}
currentTime += 1
}
}
function addValueToChart(value, dataObject, chartObject, seriesIndex, yScale) {
if (seriesIndex >= dataObject.length) {
for (var i = dataObject.length; i <= seriesIndex; ++i) {
dataObject.push([])
}
}
var seriesData = dataObject[seriesIndex]
seriesData.push(Qt.point(currentTime, value))
chartObject.item.chart.series(seriesIndex).append(currentTime, value)
if (currentTime > chartObject.item.chart.defaultXMax && chartObject.item.chart.autoScaling) {
chartObject.item.chart.axes[0].max = currentTime
}
if (seriesData.length < 2 || (value >= chartObject.item.chart.axes[1].max && chartObject.item.chart.autoScaling)) {
chartObject.item.chart.axes[1].max = closestMaxValue(value, yScale)
}
if (seriesData.length < 2 || (value <= chartObject.item.chart.axes[1].min && chartObject.item.chart.autoScaling)) {
chartObject.item.chart.axes[1].min = closestMinValue(value, yScale)
}
}
function closestMaxValue(value, scale) {
return value === 0 ? 1 : Math.ceil(value * (value > 0 ? (1 + scale) : (1 - scale)))
}
function closestMinValue(value, scale) {
return value === 0 ? -1 : Math.floor(value * (value > 0 ? (1 - scale) : (1 + scale)))
}
Timer {
id: timer
running: true
interval: 1000
triggeredOnStart: true
onTriggered: {
BmsInterface.commands().getValues()
BmsInterface.commands().getCells()
Qt.callLater(start)
}
}
Connections {
target: BmsInterface
onPortConnectedChanged: getValues()
}
onVisibleChanged: getValues()
function getValues() {
if (BmsInterface.isPortConnected() && visible) {
timer.start()
} else {
timer.stop()
}
}
Component.onCompleted: {
voltageLoader.item.chart.createSeries(ChartView.SeriesTypeLine, qsTr("Voltage indicator"),
voltageLoader.item.chart.xAxis, voltageLoader.item.chart.yAxis)
voltageLoader.item.chart.axes[0].max = 10
voltageLoader.item.seriesCount = voltageLoader.item.chart.count
currentLoader.item.chart.createSeries(ChartView.SeriesTypeLine, qsTr("Current indicator"),
currentLoader.item.chart.xAxis, currentLoader.item.chart.yAxis)
currentLoader.item.chart.axes[0].max = 10
currentLoader.item.seriesCount = currentLoader.item.chart.count
batteryTemperatureLoader.item.chart.createSeries(ChartView.SeriesTypeLine, qsTr("Maximum temperature"),
batteryTemperatureLoader.item.chart.xAxis, batteryTemperatureLoader.item.chart.yAxis)
batteryTemperatureLoader.item.chart.createSeries(ChartView.SeriesTypeLine, qsTr("Average temperature"),
batteryTemperatureLoader.item.chart.xAxis, batteryTemperatureLoader.item.chart.yAxis)
batteryTemperatureLoader.item.chart.createSeries(ChartView.SeriesTypeLine, qsTr("Minimum temperature"),
batteryTemperatureLoader.item.chart.xAxis, batteryTemperatureLoader.item.chart.yAxis)
batteryTemperatureLoader.item.chart.axes[0].max = 10
batteryTemperatureLoader.item.seriesCount = batteryTemperatureLoader.item.chart.count
bmsTemperatureLoader.item.chart.createSeries(ChartView.SeriesTypeLine, qsTr("Maximum temperature"),
bmsTemperatureLoader.item.chart.xAxis, bmsTemperatureLoader.item.chart.yAxis)
bmsTemperatureLoader.item.chart.createSeries(ChartView.SeriesTypeLine, qsTr("Average temperature"),
bmsTemperatureLoader.item.chart.xAxis, bmsTemperatureLoader.item.chart.yAxis)
bmsTemperatureLoader.item.chart.createSeries(ChartView.SeriesTypeLine, qsTr("Minimum temperature"),
bmsTemperatureLoader.item.chart.xAxis, bmsTemperatureLoader.item.chart.yAxis)
bmsTemperatureLoader.item.chart.axes[0].max = 10
bmsTemperatureLoader.item.seriesCount = bmsTemperatureLoader.item.chart.count
cellVoltageLoader.item.chart.createSeries(ChartView.SeriesTypeLine, qsTr("Maximum voltage"),
cellVoltageLoader.item.chart.xAxis, cellVoltageLoader.item.chart.yAxis)
cellVoltageLoader.item.chart.createSeries(ChartView.SeriesTypeLine, qsTr("Average voltage"),
cellVoltageLoader.item.chart.xAxis, cellVoltageLoader.item.chart.yAxis)
cellVoltageLoader.item.chart.createSeries(ChartView.SeriesTypeLine, qsTr("Minimum voltage"),
cellVoltageLoader.item.chart.xAxis, cellVoltageLoader.item.chart.yAxis)
cellVoltageLoader.item.chart.axes[0].max = 10
cellVoltageLoader.item.seriesCount = cellVoltageLoader.item.chart.count
}
}

View File

@@ -3,3 +3,4 @@ ConnectionScreen 1.0 ConnectionScreen.qml
AkbMonitorScreen 1.0 AkbMonitorScreen.qml
CellMonitorScreen 1.0 CellMonitorScreen.qml
BmsSettingsScreen 1.0 BmsSettingsScreen.qml
VisualizationScreen 1.0 VisualizationScreen.qml

View File

@@ -26,5 +26,9 @@
<file>Controls/CheckBox.qml</file>
<file>Controls/LineSeparator.qml</file>
<file>Controls/LinkLabel.qml</file>
<file>Screens/VisualizationScreen.qml</file>
<file>Controls/TabButton.qml</file>
<file>Controls/TabBar.qml</file>
<file>Controls/ChartView.qml</file>
</qresource>
</RCC>