diff --git a/ENNOID-BMS-Tool.pro b/ENNOID-BMS-Tool.pro
index c40953e..11e49b2 100644
--- a/ENNOID-BMS-Tool.pro
+++ b/ENNOID-BMS-Tool.pro
@@ -173,6 +173,8 @@ TRANSLATIONS = translations/cubo_en.ts \
translations/cubo_ru.ts \
translations/cubo_it.ts
+RC_ICONS = qml/Icons/cubo-icon.ico
+
DISTFILES += \
android/AndroidManifest.xml \
android/gradle/wrapper/gradle-wrapper.jar \
diff --git a/qml/Controls/ChartView.qml b/qml/Controls/ChartView.qml
index 8bdcb1a..b358979 100644
--- a/qml/Controls/ChartView.qml
+++ b/qml/Controls/ChartView.qml
@@ -1,6 +1,8 @@
import QtQuick 2.12
import QtCharts 2.3
+import QtQuick.Shapes 1.12
+import Controls 1.0 as Controls
import Utils 1.0
ChartView {
@@ -20,6 +22,7 @@ ChartView {
property int defaultXMax: 10
property int defaultYMax: 1
property bool autoScaling: true
+ property int selectedSeriesIndex: 0
ValueAxis {
id: valueAxisX
@@ -45,10 +48,47 @@ ChartView {
titleVisible: false
}
+ Shape {
+ id: currentPointLine
+ implicitWidth: 1
+ implicitHeight: chart.plotArea.height
+ x: chart.plotArea.x
+ y: chart.plotArea.y
+
+ ShapePath {
+ strokeColor: Palette.borderColor
+ strokeStyle: ShapePath.SolidLine
+ startX: 0
+ startY: 0
+ PathLine { x: 0; y: currentPointLine.height }
+ }
+ }
+
+ Rectangle {
+ id: currentPointInfo
+
+ width: currentPointInfoLabel.width
+ height: currentPointInfoLabel.height
+ color: Palette.backgroundColor
+ border.width: 1
+ border.color: Palette.borderColor
+ opacity: 0.9
+ radius: 6
+ visible: false
+
+ Controls.SubtitleLabel {
+ id: currentPointInfoLabel
+ horizontalAlignment: Text.AlignHCenter
+ anchors.centerIn: parent
+ padding: 10
+ }
+ }
+
MouseArea {
- id: chartMouseAreaA
+ id: chartMouseArea
anchors.fill: parent
acceptedButtons: Qt.LeftButton
+ hoverEnabled: true
property real lastMouseX: 0
property real lastMouseY: 0
@@ -59,27 +99,28 @@ ChartView {
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
}
+ lastMouseX = mouseX
+ chart.autoScaling = false
}
+
+ updatePointInfo()
}
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
}
+ lastMouseY = mouseY
+ chart.autoScaling = false
}
+
+ updatePointInfo()
}
onPressed: {
if (mouse.button === Qt.LeftButton) {
@@ -88,13 +129,71 @@ ChartView {
}
}
onWheel: {
- if (wheel.angleDelta.y > 0) {
- chart.zoomIn()
- chart.autoScaling = false
- } else {
- chart.zoomOut()
- chart.autoScaling = false
+ var scaleFactor = 0.75
+ var area = chart.plotArea
+ chart.autoScaling = false
+
+ var yAxisPosition = mouseX <= chart.plotArea.x
+ if (yAxisPosition) {
+ if (wheel.angleDelta.y > 0) {
+ chart.zoomIn(Qt.rect(area.x, area.y + Math.round(area.height * (1 - scaleFactor) / 2), area.width, Math.round(area.height * scaleFactor)))
+ } else {
+ chart.zoomIn(Qt.rect(area.x, area.y - Math.round((area.height / scaleFactor) * (1 - scaleFactor) / 2), area.width, Math.round(area.height / scaleFactor)))
+ }
+ return
}
+
+ var xAxisPosition = mouseY >= (chart.plotArea.y + chart.plotArea.height)
+ if (xAxisPosition) {
+ if (wheel.angleDelta.y > 0) {
+ chart.zoomIn(Qt.rect(area.x + area.width * (1 - scaleFactor) / 2, area.y, Math.round(area.width * scaleFactor), area.height))
+ } else {
+ chart.zoomIn(Qt.rect(area.x - (area.width / scaleFactor) * (1 - scaleFactor) / 2, area.y, Math.round(area.width / scaleFactor), area.height))
+ }
+ return
+ }
+
+ if (wheel.angleDelta.y > 0) {
+ let zoomedInArea = Qt.rect(area.x + Math.round((mouseX - area.x) * (1 - scaleFactor)),
+ area.y + Math.round((mouseY - area.y) * (1 - scaleFactor)),
+ Math.round(area.width * scaleFactor),
+ Math.round(area.height * scaleFactor))
+ chart.zoomIn(zoomedInArea)
+ } else {
+ let zoomedOutArea = Qt.rect(area.x - Math.floor((mouseX - area.x) * (1 - scaleFactor)),
+ area.y - Math.floor((mouseY - area.y) * (1 - scaleFactor)),
+ Math.round(area.width * (1 + (1 - scaleFactor))),
+ Math.round(area.height * (1 + (1 - scaleFactor))))
+ chart.zoomIn(zoomedOutArea)
+ }
+
+ updatePointInfo()
+ }
+
+ function updatePointInfo() {
+ var currentPointVisibility = mouseX >= chart.plotArea.x && mouseX <= chart.plotArea.x + chart.plotArea.width &&
+ mouseY >= chart.plotArea.y && mouseY <= chart.plotArea.y + chart.plotArea.height
+
+ currentPointInfo.visible = currentPointVisibility
+ currentPointInfo.x = mouseX + 5
+ currentPointInfo.y = mouseY - currentPointInfo.height - 5
+
+ var currentX = Math.ceil(mapToValue(Qt.point(mouseX, mouseY), chart.series(selectedSeriesIndex)).x)
+ var selectedPoint = Qt.point(0, 0)
+
+ if (currentX < chart.series(selectedSeriesIndex).count && currentX > 0) {
+ selectedPoint = chart.series(selectedSeriesIndex).at(currentX)
+ currentPointInfoLabel.text = selectedPoint.x + ", " +selectedPoint.y
+ } else {
+ currentPointInfo.visible = false
+ }
+
+ currentPointLine.visible = currentPointVisibility
+ currentPointLine.x = mouseX
}
}
+
+ onSeriesAdded: if (count === 1) {
+ series.pointAdded.connect(function (index){ chartMouseArea.updatePointInfo() })
+ }
}
diff --git a/qml/Controls/ImageButton.qml b/qml/Controls/ImageButton.qml
new file mode 100644
index 0000000..c4f1a67
--- /dev/null
+++ b/qml/Controls/ImageButton.qml
@@ -0,0 +1,27 @@
+import QtQuick 2.12
+import QtQuick.Controls 2.12
+
+import Utils 1.0
+
+Button {
+ id: control
+
+ icon.width: 24
+ icon.height: 24
+
+ contentItem: Image {
+ source: control.icon.source
+ sourceSize.width: control.icon.width
+ sourceSize.height: control.icon.height
+ fillMode: Image.PreserveAspectFit
+ }
+
+ background: Rectangle {
+ implicitWidth: 44
+ implicitHeight: 44
+ opacity: enabled ? 1 : 0.3
+ color: control.pressed ? Palette.pressedButtonColor :
+ control.hovered ? Palette.hoveredButtonColor : Palette.buttonColor
+ radius: 5
+ }
+}
diff --git a/qml/Controls/TextField.qml b/qml/Controls/TextField.qml
index e5af946..c61f75a 100644
--- a/qml/Controls/TextField.qml
+++ b/qml/Controls/TextField.qml
@@ -9,6 +9,8 @@ TextField {
color: Palette.textColor
selectByMouse: true
selectionColor: Palette.selectedTextBackgroundColor
+ leftPadding: 16
+ rightPadding: 16
background: Rectangle {
color: enabled ? Palette.backgroundColor : Palette.hoveredBackgroundColor
diff --git a/qml/Controls/qmldir b/qml/Controls/qmldir
index 426b3c6..29ecb8f 100644
--- a/qml/Controls/qmldir
+++ b/qml/Controls/qmldir
@@ -24,3 +24,4 @@ BusyIndicator 1.0 BusyIndicator.qml
MenuItemDelegate 1.0 MenuItemDelegate.qml
ScrollIndicator 1.0 ScrollIndicator.qml
OutlineImageButton 1.0 OutlineImageButton.qml
+ImageButton 1.0 ImageButton.qml
diff --git a/qml/Icons/cubo-icon.ico b/qml/Icons/cubo-icon.ico
new file mode 100644
index 0000000..74750a7
Binary files /dev/null and b/qml/Icons/cubo-icon.ico differ
diff --git a/qml/MainWindow.qml b/qml/MainWindow.qml
index 74387e9..a2b34d9 100644
--- a/qml/MainWindow.qml
+++ b/qml/MainWindow.qml
@@ -9,7 +9,7 @@ import Utils 1.0
ApplicationWindow {
id: window
- title: qsTr("Cubo Verde BMS tool")
+ title: qsTr("Cubo Verde BMS")
width: 1366
height: 768
visible: true
diff --git a/qml/Screens/AkbMonitorScreen.qml b/qml/Screens/AkbMonitorScreen.qml
index 099f275..182c908 100644
--- a/qml/Screens/AkbMonitorScreen.qml
+++ b/qml/Screens/AkbMonitorScreen.qml
@@ -6,6 +6,8 @@ import Cubo 1.0
import Utils 1.0
Item {
+ id: root
+
ColumnLayout {
spacing: 15
anchors.fill: parent
@@ -298,6 +300,8 @@ Item {
Connections {
target: BmsInterface.commands()
+ enabled: root.visible
+
onValuesReceived: {
batteryChargeLevelLabel.text = values.soC
batteryVoltageLabel.text = MathHelper.roundDouble(values.packVoltage)
@@ -334,7 +338,7 @@ Item {
Timer {
id: refreshValuesTimer
- interval: 5000
+ interval: 1000
onTriggered: getValues()
}
diff --git a/qml/Screens/BmsSettingsScreen.qml b/qml/Screens/BmsSettingsScreen.qml
index bc16222..96454d9 100644
--- a/qml/Screens/BmsSettingsScreen.qml
+++ b/qml/Screens/BmsSettingsScreen.qml
@@ -15,13 +15,15 @@ RowLayout {
signal needWait(bool active, string text)
- spacing: contentColumnSpacing
+ spacing: contentColumnSpacing - 10
Flickable {
id: settingsFlickable
clip: true
+ contentWidth: width - rightMargin - leftMargin
contentHeight: configLayout.height
boundsBehavior: Flickable.StopAtBounds
+ rightMargin: 10
ColumnLayout {
id: configLayout
diff --git a/qml/Screens/CellMonitorScreen.qml b/qml/Screens/CellMonitorScreen.qml
index 48ce212..a9fc42e 100644
--- a/qml/Screens/CellMonitorScreen.qml
+++ b/qml/Screens/CellMonitorScreen.qml
@@ -8,6 +8,8 @@ import Cubo 1.0
import Utils 1.0
Item {
+ id: root
+
Component {
id: cellListHeader
@@ -22,6 +24,7 @@ Item {
Layout.fillWidth: true
Controls.SubtitleLabel {
+ id: indexTitleLabel
text: qsTr("#")
color: Palette.tableHeaderTextColor
anchors.centerIn: parent
@@ -35,6 +38,7 @@ Item {
Layout.fillWidth: true
Controls.SubtitleLabel {
+ id: voltageTitleLabel
text: qsTr("Voltage")
color: Palette.tableHeaderTextColor
anchors.centerIn: parent
@@ -48,6 +52,7 @@ Item {
Layout.fillWidth: true
Controls.SubtitleLabel {
+ id: balancingTitleLabel
text: qsTr("Balancing")
color: Palette.tableHeaderTextColor
anchors.centerIn: parent
@@ -64,15 +69,16 @@ Item {
RowLayout {
spacing: 10
width: ListView.view.width
- height: 38
+ height: 36
Item {
- Layout.preferredWidth: parent.width / 6 - 20
+ Layout.preferredWidth: parent.width / 6 - 24
}
Controls.SubtitleLabel {
id: indexLabel
color: Palette.tableHeaderTextColor
+ horizontalAlignment: Text.AlignHCenter
Layout.preferredWidth: 25
Layout.alignment: Qt.AlignCenter
}
@@ -96,7 +102,7 @@ Item {
}
Item {
- Layout.preferredWidth: parent.width / 6 - 20
+ Layout.preferredWidth: parent.width / 6 - 24
}
Component.onCompleted: {
@@ -157,12 +163,14 @@ Item {
Connections {
target: BmsInterface.commands()
+ enabled: root.visible
+
onCellsReceived: {
firstCellGroup.model = []
var firstModel = []
for (var i = 0; i < Math.min(cellCount, 16); ++i) {
- firstModel.push({"voltage": MathHelper.roundDouble(cellVoltageArray[i]), "balancing": cellVoltageArray[i] < 0})
+ firstModel.push({"voltage": Math.abs(MathHelper.roundDouble(cellVoltageArray[i], 3)), "balancing": cellVoltageArray[i] < 0})
}
firstCellGroup.model = firstModel
@@ -171,7 +179,7 @@ Item {
var secondModel = []
for (var j = 16; j < Math.min(cellCount, 32); ++j) {
- secondModel.push({"voltage": MathHelper.roundDouble(cellVoltageArray[j]), "balancing": cellVoltageArray[i] < 0})
+ secondModel.push({"voltage": Math.abs(MathHelper.roundDouble(cellVoltageArray[j], 3)), "balancing": cellVoltageArray[i] < 0})
}
secondCellGroup.model = secondModel
}
@@ -187,7 +195,7 @@ Item {
Timer {
id: refreshValuesTimer
- interval: 5000
+ interval: 1000
onTriggered: getValues()
}
diff --git a/qml/Screens/VisualizationScreen.qml b/qml/Screens/VisualizationScreen.qml
index 4bc7748..4e01bb2 100644
--- a/qml/Screens/VisualizationScreen.qml
+++ b/qml/Screens/VisualizationScreen.qml
@@ -8,9 +8,10 @@ import Cubo 1.0
import Utils 1.0
ColumnLayout {
+ id: root
spacing: 20
- property int currentTime: 0
+ property real currentTime: 0
property var voltageData: []
property var currentData: []
@@ -86,6 +87,7 @@ ColumnLayout {
property Controls.ChartView chartItem: undefined
property color seriesColor: "black"
property bool horizontal: true
+ property bool selected: false
Controls.CheckBox {
id: checkSeries
@@ -114,9 +116,17 @@ ColumnLayout {
Rectangle {
color: seriesColor
radius: width / 2
- Layout.preferredWidth: 18
- Layout.preferredHeight: 18
+ border.width: selected ? 2 : 0
+ border.color: "black"
+ Layout.preferredWidth: selected ? 21 : 18
+ Layout.preferredHeight: selected ? 21 : 18
Layout.rightMargin: 20
+ Layout.alignment: Qt.AlignCenter
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: if (!selected) selected = true
+ }
}
Component.onCompleted: {
@@ -124,6 +134,10 @@ ColumnLayout {
horizontal = !ListView.view
seriesColor = chartItem.series(modelData).color
checkSeries.text = Qt.binding(function(){ return chartItem.series(modelData).name })
+
+ selectedChanged.connect(function (){ if (selected) chartItem.selectedSeriesIndex = index })
+ chartItem.selectedSeriesIndexChanged.connect(function (){ if (typeof(selected) != "undefined") selected = chartItem.selectedSeriesIndex === index })
+ selected = chartItem.selectedSeriesIndex === index
}
}
}
@@ -289,44 +303,6 @@ ColumnLayout {
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")
@@ -382,6 +358,73 @@ ColumnLayout {
return min
}
}
+
+ Controls.Button {
+ property string pauseText: qsTr("Pause data collection")
+ property string resumeText: qsTr("Resume data collection")
+ text: pauseText
+ onClicked: {
+ if (timer.running) {
+ text = resumeText
+ timer.stop()
+ } else {
+ text = pauseText
+ 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
+ }
+
+ RowLayout {
+ spacing: 10
+
+ Controls.SubtitleLabel {
+ text: qsTr("Interval, s")
+ }
+
+ Controls.TextField {
+ id: intervalField
+ validator: DoubleValidator { bottom: 0.1; top: 100; decimals: 1; locale: "en-US" }
+ text: "1"
+ onEditingFinished: timer.interval = parseFloat(text) * 1000
+ Layout.preferredHeight: 44
+ }
+
+ Controls.ImageButton {
+ icon.source: "qrc:/Icons/check-indicator.svg"
+ icon.width: 32
+ icon.height: 32
+ Layout.preferredWidth: 42
+ Layout.preferredHeight: 42
+ }
+ }
}
Connections {
@@ -395,6 +438,7 @@ ColumnLayout {
Connections {
target: BmsInterface.commands()
+ enabled: root.visible
onValuesReceived: {
addValueToChart(values.packVoltage, voltageData, voltageLoader, 0, 0.05)
@@ -428,7 +472,7 @@ ColumnLayout {
addValueToChart(cellVoltageArray[j], cellListData, cellListLoader, j, 0.05)
}
- currentTime += 1
+ currentTime = Math.round((timer.interval / 1000 + currentTime + Number.EPSILON) * 10) / 10
}
}
diff --git a/qml/Utils/MathHelper.qml b/qml/Utils/MathHelper.qml
index 777cc05..e291f71 100644
--- a/qml/Utils/MathHelper.qml
+++ b/qml/Utils/MathHelper.qml
@@ -2,7 +2,8 @@ pragma Singleton
import QtQuick 2.12
QtObject {
- function roundDouble(value) {
- return Math.round((value + Number.EPSILON) * 100) / 100
+ function roundDouble(value, decimals = 2) {
+ var factor = Math.pow(10, decimals)
+ return Math.round((value + Number.EPSILON) * factor) / factor
}
}
diff --git a/qml/qml_items.qrc b/qml/qml_items.qrc
index 304e3b4..ec37fbc 100644
--- a/qml/qml_items.qrc
+++ b/qml/qml_items.qrc
@@ -40,5 +40,6 @@
Controls/MenuItemDelegate.qml
Controls/ScrollIndicator.qml
Controls/OutlineImageButton.qml
+ Controls/ImageButton.qml