Files
CuboBmsTool/qml/Screens/VisualizationScreen.qml

529 lines
22 KiB
QML

import QtQuick 2.12
import QtQuick.Controls 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.8
}
Controls.TabButton {
text: qsTr("Battery temperature")
width: bar.width / 6 * 1.3
}
Controls.TabButton {
text: qsTr("BMS temperature")
width: bar.width / 6 * 1.1
}
Controls.TabButton {
text: qsTr("Cell voltage")
width: bar.width / 6 * 1
}
Controls.TabButton {
text: qsTr("Cell list")
width: bar.width / 6 * 0.85
}
Layout.fillWidth: true
}
StackLayout {
width: parent.width
currentIndex: bar.currentIndex
Component {
id: legendDelegate
RowLayout {
width: ListView.view ? ListView.view.width : 200
height: 60
property Controls.ChartView chartItem: undefined
property color seriesColor: "black"
property bool horizontal: true
Controls.CheckBox {
id: checkSeries
checked: true
spacing: 10
onCheckedChanged: {
if (checked) {
chartItem.series(modelData).color = seriesColor
} else {
chartItem.series(modelData).color = "transparent"
}
}
Layout.fillWidth: !horizontal
}
Rectangle {
color: seriesColor
radius: width / 2
Layout.preferredWidth: 18
Layout.preferredHeight: 18
Layout.rightMargin: 20
}
Component.onCompleted: {
chartItem = ListView.view ? ListView.view.chartItem : parent.chartItem
horizontal = !ListView.view
seriesColor = chartItem.series(modelData).color
checkSeries.text = Qt.binding(function(){ return chartItem.series(modelData).name })
}
}
}
Component {
id: chartComponent
RowLayout {
property string xLabel: ""
property string yLabel: ""
property Controls.ChartView chart: chartView
property int seriesCount: 0
property bool horizontalLegend: true
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
}
RowLayout {
visible: horizontalLegend
spacing: 0
property Controls.ChartView chartItem: chart
Repeater {
model: seriesCount
delegate: legendDelegate
}
Layout.topMargin: 20
Layout.leftMargin: 20
}
Layout.fillWidth: true
Layout.fillHeight: true
}
ListView {
clip: true
model: seriesCount
delegate: legendDelegate
visible: !horizontalLegend
boundsBehavior: Flickable.StopAtBounds
ScrollBar.vertical: Controls.ScrollBar {}
property Controls.ChartView chartItem: chart
Layout.preferredWidth: 200
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") })
item.horizontalLegend = false
}
}
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: Translator
onCurrentLanguageChanged: {
for (var i = 0; i < cellListLoader.item.chart.count; ++i) {
cellListLoader.item.chart.series(i).name = qsTr("Cell #") + (i + 1).toString()
}
}
}
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.series(0).name = Qt.binding(function(){ return qsTr("Voltage indicator") })
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.series(0).name = Qt.binding(function(){ return qsTr("Current indicator") })
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.series(0).name = Qt.binding(function(){ return qsTr("Maximum temperature") })
batteryTemperatureLoader.item.chart.series(1).name = Qt.binding(function(){ return qsTr("Average temperature") })
batteryTemperatureLoader.item.chart.series(2).name = Qt.binding(function(){ return qsTr("Minimum temperature") })
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.series(0).name = Qt.binding(function(){ return qsTr("Maximum temperature") })
bmsTemperatureLoader.item.chart.series(1).name = Qt.binding(function(){ return qsTr("Average temperature") })
bmsTemperatureLoader.item.chart.series(2).name = Qt.binding(function(){ return qsTr("Minimum temperature") })
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.series(0).name = Qt.binding(function(){ return qsTr("Maximum voltage") })
cellVoltageLoader.item.chart.series(1).name = Qt.binding(function(){ return qsTr("Average voltage") })
cellVoltageLoader.item.chart.series(2).name = Qt.binding(function(){ return qsTr("Minimum voltage") })
cellVoltageLoader.item.chart.axes[0].max = 10
cellVoltageLoader.item.seriesCount = cellVoltageLoader.item.chart.count
}
}