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.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 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 } }