Initial commit (project based on widgets)

This commit is contained in:
Yury Shuvakin
2022-08-01 21:53:36 +03:00
parent d9396cdc2f
commit 14a7aa699f
411 changed files with 95119 additions and 0 deletions

219
mobile/AdcMap.qml Normal file
View File

@@ -0,0 +1,219 @@
/*
Copyright 2018 Benjamin Vedder benjamin@vedder.se
This file is part of VESC Tool.
VESC Tool is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
VESC Tool is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import Ennoid.bmsinterface 1.0
import Ennoid.commands 1.0
import Ennoid.configparams 1.0
Item {
property int parentWidth: 10
property real vMin: 0.0
property real vMax: 0.0
property real vCenter: 0.0
property real vNow: 0.0
property real valueNow: 0.5
property real vMin2: 0.0
property real vMax2: 0.0
property real vNow2: 0.0
property real valueNow2: 0.5
property bool resetDone: true
property Commands mCommands: VescIf.commands()
property ConfigParams mAppConf: VescIf.appConfig()
property ConfigParams mInfoConf: VescIf.infoConfig()
function openDialog() {
dialog.open()
}
function updateDisplay() {
resultArea.text =
"Value : " + parseFloat(valueNow).toFixed(2) + "\n" +
"Value2 : " + parseFloat(valueNow2).toFixed(2) + "\n\n" +
"Now : " + parseFloat(vNow).toFixed(2) + " V\n" +
"Min : " + parseFloat(vMin).toFixed(2) + " V\n" +
"Max : " + parseFloat(vMax).toFixed(2) + " V\n" +
"Center : " + parseFloat(vCenter).toFixed(2) + " V\n\n" +
"Now2 : " + parseFloat(vNow2).toFixed(2) + " V\n" +
"Min2 : " + parseFloat(vMin2).toFixed(2) + " V\n" +
"Max2 : " + parseFloat(vMax2).toFixed(2) + " V"
valueBar.value = valueNow
valueBar2.value = valueNow2
}
Component.onCompleted: {
updateDisplay()
}
Dialog {
id: dialog
standardButtons: Dialog.Close
modal: true
focus: true
width: parentWidth - 20
height: Math.min(implicitHeight, column.height - 40)
closePolicy: Popup.CloseOnEscape
x: 10
y: 10
ScrollView {
anchors.fill: parent
clip: true
contentWidth: parent.width
ColumnLayout {
anchors.fill: parent
spacing: 0
TextArea {
id: resultArea
Layout.fillWidth: true
Layout.preferredHeight: 300
readOnly: true
wrapMode: TextEdit.WordWrap
font.family: "DejaVu Sans Mono"
}
ProgressBar {
id: valueBar
Layout.fillWidth: true
Layout.bottomMargin: 5
from: 0.0
to: 1.0
value: 0.0
}
ProgressBar {
id: valueBar2
Layout.fillWidth: true
from: 0.0
to: 1.0
value: 0.0
}
RowLayout {
Layout.fillWidth: true
Button {
text: "Help"
Layout.preferredWidth: 50
Layout.fillWidth: true
onClicked: {
VescIf.emitMessageDialog(
mInfoConf.getLongName("app_adc_mapping_help"),
mInfoConf.getDescription("app_adc_mapping_help"),
true, true)
}
}
Button {
text: "Reset"
Layout.preferredWidth: 50
Layout.fillWidth: true
onClicked: {
vMin = 0.0
vMax = 0.0
vCenter = 0.0
vMin2 = 0.0
vMax2 = 0.0
resetDone = true
updateDisplay()
}
}
}
Button {
text: "Apply & Write"
Layout.fillWidth: true
onClicked: {
mAppConf.updateParamDouble("app_adc_conf.voltage_start", vMin)
mAppConf.updateParamDouble("app_adc_conf.voltage_end", vMax)
mAppConf.updateParamDouble("app_adc_conf.voltage_center", vCenter)
mAppConf.updateParamDouble("app_adc_conf.voltage2_start", vMin2)
mAppConf.updateParamDouble("app_adc_conf.voltage2_end", vMax2)
VescIf.emitStatusMessage("Start, End and Center ADC Voltages Applied", true)
mCommands.setAppConf()
}
}
}
}
}
Timer {
id: rtTimer
interval: 50
running: true
repeat: true
onTriggered: {
if (VescIf.isPortConnected() && dialog.visible) {
mCommands.getDecodedAdc()
}
}
}
Connections {
target: mCommands
onDecodedAdcReceived: {
valueNow = value
vNow = voltage
valueNow2 = value2
vNow2 = voltage2
if (resetDone) {
resetDone = false
vMin = vNow
vMax = vNow
vMin2 = vNow2
vMax2 = vNow2
}
if (vNow < vMin) {
vMin = vNow
}
if (vNow > vMax) {
vMax = vNow
}
var range = vMax - vMin
var pos = vNow - vMin
if (pos > (range / 4.0) && pos < ((3.0 * range) / 4.0)) {
vCenter = vNow
} else {
vCenter = range / 2.0 + vMin
}
if (vNow2 < vMin2) {
vMin2 = vNow2
}
if (vNow2 > vMax2) {
vMax2 = vNow2
}
updateDisplay()
}
}
}

286
mobile/ConfigPageCell.qml Normal file
View File

@@ -0,0 +1,286 @@
/*
Copyright 2020 Kevin Dionne kevin.dionne@ennoid.me
This file is part of ENNOID-BMS Tool.
ENNOID-BMS Tool is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ENNOID-BMS Tool is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import Ennoid.bmsinterface 1.0
import Ennoid.commands 1.0
import Ennoid.configparams 1.0
Item {
property Commands mCommands: VescIf.commands()
property var editorsVisible: []
property bool isHorizontal: width > height
ParamEditors {
id: editors
}
onIsHorizontalChanged: {
//updateEditors()
}
function addSeparator(text) {
editorsVisible.push(editors.createSeparator(scrollCol, text))
editorsVisible[editorsVisible.length - 1].Layout.columnSpan = isHorizontal ? 2 : 1
}
function destroyEditors() {
for (var i = 0;i < editorsVisible.length;i++) {
editorsVisible[i].destroy();
}
editorsVisible = []
}
function createEditorMc(param) {
editorsVisible.push(editors.createEditorMc(scrollCol, param))
editorsVisible[editorsVisible.length - 1].Layout.preferredWidth = 500
editorsVisible[editorsVisible.length - 1].Layout.fillsWidth = true
}
function updateEditors() {
destroyEditors()
switch (pageBox.currentText) {
case "Specifications":
switch(tabBox.currentText) {
case "Pack configuration":
createEditorMc("cellMonitorICCount")
createEditorMc("cellMonitorICType")
createEditorMc("noOfParallelModules")
createEditorMc("noOfCellsSeries")
break;
case "SOC - Pack capacity":
createEditorMc("noOfCellsParallel")
createEditorMc("batteryCapacity")
break;
case "Cell specifications":
createEditorMc("cellTechnology")
createEditorMc("cellHardOverVoltage")
createEditorMc("cellHardUnderVoltage")
createEditorMc("cellLCSoftUnderVoltage")
createEditorMc("cellSoftOverVoltage")
createEditorMc("maxUnderAndOverVoltageErrorCount")
createEditorMc("hysteresisDischarge")
createEditorMc("hysteresisCharge")
break;
default:
break;
}
break;
case "Balancing":
switch(tabBox.currentText) {
case "Balancing configuration":
createEditorMc("cellBalanceStart")
createEditorMc("cellBalanceDifferenceThreshold")
createEditorMc("cellBalanceUpdateInterval")
createEditorMc("cellBalanceAllTime")
break;
default:
break;
}
break;
case "Throttling":
switch(tabBox.currentText) {
case "Discharge":
createEditorMc("cellThrottleLowerStart")
createEditorMc("cellThrottleLowerMargin")
createEditorMc("throttleDisChargeIncreaseRate")
break;
case "Charge":
createEditorMc("cellThrottleUpperStart")
createEditorMc("cellThrottleUpperMargin")
createEditorMc("throttleChargeIncreaseRate")
break;
default:
break;
}
break;
case "SoC":
switch(tabBox.currentText) {
case "SoC general":
createEditorMc("stateOfChargeMethod")
createEditorMc("stateOfChargeStoreInterval")
createEditorMc("timeoutChargeCompleted")
createEditorMc("timeoutChargingCompletedMinimalMismatch")
createEditorMc("maxMismatchThreshold")
break;
default:
break;
}
break;
default:
break;
}
}
ColumnLayout {
id: column
anchors.fill: parent
spacing: 0
GridLayout {
Layout.fillWidth: true
columns: isHorizontal ? 2 : 1
rowSpacing: -5
ComboBox {
id: pageBox
Layout.fillWidth: true
model: [
"Specifications",
"Balancing",
"Throttling",
"SoC"
]
onCurrentTextChanged: {
var tabTextOld = tabBox.currentText
switch(currentText) {
case "Specifications":
tabBox.model = [
"Pack configuration",
"SOC - Pack capacity",
"Cell specifications"
]
break;
case "Balancing":
tabBox.model = [
"Balancing configuration"
]
break;
case "Throttling":
tabBox.model = [
"Discharge",
"Charge"
]
break;
case "SoC":
tabBox.model = [
"SoC general"
]
break;
default:
break;
}
tabBox.visible = tabBox.currentText.length !== 0
if (tabTextOld === tabBox.currentText) {
updateEditors()
}
}
}
ComboBox {
id: tabBox
Layout.fillWidth: true
onCurrentTextChanged: {
updateEditors()
}
}
}
ScrollView {
id: scroll
Layout.fillWidth: true
Layout.fillHeight: true
contentWidth: column.width
clip: true
GridLayout {
id: scrollCol
anchors.fill: parent
columns: isHorizontal ? 2 : 1
}
}
RowLayout {
Layout.fillWidth: true
Button {
Layout.preferredWidth: 100
Layout.fillWidth: true
text: "Write"
onClicked: {
mCommands.setBMSconf(true)
}
}
Button {
Layout.preferredWidth: 100
Layout.fillWidth: true
text: "Read"
onClicked: {
mCommands.getBMSconf()
}
}
Button {
Layout.preferredWidth: 50
Layout.fillWidth: true
text: "..."
onClicked: menu.open()
Menu {
id: menu
width: 500
MenuItem {
text: "Read Default Settings"
onTriggered: {
mCommands.getBMSconfDefault()
}
}
}
}
}
}
Connections {
target: mCommands
// TODO: For some reason this does not work
onBmsConfigCheckResult: {
if (paramsNotSet.length > 0) {
var notUpdated = "The following parameters were truncated because " +
"they were beyond the hardware limits:\n"
for (var i = 0;i < paramsNotSet.length;i++) {
notUpdated += mbmsConf.getLongName(paramsNotSet[i]) + "\n"
}
VescIf.emitMessageDialog("Parameters truncated", notUpdated, false, false)
}
}
}
}

View File

@@ -0,0 +1,203 @@
/*
Copyright 2020 Kevin Dionne kevin.dionne@ennoid.me
This file is part of ENNOID-BMS Tool.
ENNOID-BMS Tool is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ENNOID-BMS Tool is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import Ennoid.bmsinterface 1.0
import Ennoid.commands 1.0
import Ennoid.configparams 1.0
Item {
property Commands mCommands: VescIf.commands()
property var editorsVisible: []
property bool isHorizontal: width > height
ParamEditors {
id: editors
}
onIsHorizontalChanged: {
//updateEditors()
}
function addSeparator(text) {
editorsVisible.push(editors.createSeparator(scrollCol, text))
editorsVisible[editorsVisible.length - 1].Layout.columnSpan = isHorizontal ? 2 : 1
}
function destroyEditors() {
for (var i = 0;i < editorsVisible.length;i++) {
editorsVisible[i].destroy();
}
editorsVisible = []
}
function createEditorMc(param) {
editorsVisible.push(editors.createEditorMc(scrollCol, param))
editorsVisible[editorsVisible.length - 1].Layout.preferredWidth = 500
editorsVisible[editorsVisible.length - 1].Layout.fillsWidth = true
}
function updateEditors() {
destroyEditors()
switch (pageBox.currentText) {
case "Duration":
createEditorMc("displayTimeoutBatteryDead")
createEditorMc("displayTimeoutBatteryError")
createEditorMc("displayTimeoutBatteryErrorPreCharge")
createEditorMc("displayTimeoutSplashScreen")
break;
case "Custom":
createEditorMc("displayStyle")
break;
default:
break;
}
}
ColumnLayout {
id: column
anchors.fill: parent
spacing: 0
GridLayout {
Layout.fillWidth: true
columns: isHorizontal ? 2 : 1
rowSpacing: -5
ComboBox {
id: pageBox
Layout.fillWidth: true
model: [
"Duration",
"Custom"
]
onCurrentTextChanged: {
var tabTextOld = tabBox.currentText
switch(currentText) {
case "Duration":
tabBox.model = [
""
]
break;
case "Custom":
tabBox.model = [
""
]
break;
default:
break;
}
tabBox.visible = tabBox.currentText.length !== 0
if (tabTextOld === tabBox.currentText) {
updateEditors()
}
}
}
ComboBox {
id: tabBox
Layout.fillWidth: true
onCurrentTextChanged: {
updateEditors()
}
}
}
ScrollView {
id: scroll
Layout.fillWidth: true
Layout.fillHeight: true
contentWidth: column.width
clip: true
GridLayout {
id: scrollCol
anchors.fill: parent
columns: isHorizontal ? 2 : 1
}
}
RowLayout {
Layout.fillWidth: true
Button {
Layout.preferredWidth: 100
Layout.fillWidth: true
text: "Write"
onClicked: {
mCommands.setBMSconf(true)
}
}
Button {
Layout.preferredWidth: 100
Layout.fillWidth: true
text: "Read"
onClicked: {
mCommands.getBMSconf()
}
}
Button {
Layout.preferredWidth: 50
Layout.fillWidth: true
text: "..."
onClicked: menu.open()
Menu {
id: menu
width: 500
MenuItem {
text: "Read Default Settings"
onTriggered: {
mCommands.getBMSconfDefault()
}
}
}
}
}
}
Connections {
target: mCommands
// TODO: For some reason this does not work
onBmsConfigCheckResult: {
if (paramsNotSet.length > 0) {
var notUpdated = "The following parameters were truncated because " +
"they were beyond the hardware limits:\n"
for (var i = 0;i < paramsNotSet.length;i++) {
notUpdated += mbmsConf.getLongName(paramsNotSet[i]) + "\n"
}
VescIf.emitMessageDialog("Parameters truncated", notUpdated, false, false)
}
}
}
}

View File

@@ -0,0 +1,308 @@
/*
Copyright 2020 Kevin Dionne kevin.dionne@ennoid.me
This file is part of ENNOID-BMS Tool.
ENNOID-BMS Tool is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ENNOID-BMS Tool is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import Ennoid.bmsinterface 1.0
import Ennoid.commands 1.0
import Ennoid.configparams 1.0
Item {
property Commands mCommands: VescIf.commands()
property var editorsVisible: []
property bool isHorizontal: width > height
ParamEditors {
id: editors
}
onIsHorizontalChanged: {
//updateEditors()
}
function addSeparator(text) {
editorsVisible.push(editors.createSeparator(scrollCol, text))
editorsVisible[editorsVisible.length - 1].Layout.columnSpan = isHorizontal ? 2 : 1
}
function destroyEditors() {
for (var i = 0;i < editorsVisible.length;i++) {
editorsVisible[i].destroy();
}
editorsVisible = []
}
function createEditorMc(param) {
editorsVisible.push(editors.createEditorMc(scrollCol, param))
editorsVisible[editorsVisible.length - 1].Layout.preferredWidth = 500
editorsVisible[editorsVisible.length - 1].Layout.fillsWidth = true
}
function updateEditors() {
destroyEditors()
switch (pageBox.currentText) {
case "Power State":
switch(tabBox.currentText) {
case "Onstate":
createEditorMc("pulseToggleButton")
createEditorMc("notUsedCurrentThreshold")
createEditorMc("notUsedTimeout")
createEditorMc("powerDownDelay")
createEditorMc("allowForceOn")
break;
case "Jump to":
createEditorMc("extEnableState")
createEditorMc("chargeEnableState")
break;
default:
break;
}
break;
case "Limits":
switch(tabBox.currentText) {
case "Current":
createEditorMc("maxAllowedCurrent")
break;
case "Temperature discharging":
createEditorMc("allowedTempBattDischargingMax")
createEditorMc("allowedTempBattDischargingMin")
break;
case "Temperature charging":
createEditorMc("allowedTempBattChargingMax")
createEditorMc("allowedTempBattChargingMin")
break;
case "Temperature cooling/heating":
createEditorMc("allowedTempBattCoolingMax")
createEditorMc("allowedTempBattCoolingMin")
break;
case "Temperature Master board":
createEditorMc("allowedTempBMSMax")
createEditorMc("allowedTempBMSMin")
break;
default:
break;
}
break;
case "CAN":
switch(tabBox.currentText) {
case "CAN configuration":
createEditorMc("CANID")
createEditorMc("CANIDStyle")
createEditorMc("CANBaudRate")
break;
case "CAN messaging":
createEditorMc("emitStatusOverCAN")
createEditorMc("emitStatusProtocolType")
createEditorMc("useCANSafetyInput")
createEditorMc("useCANDelayedPowerDown")
break;
default:
break;
}
break;
case "Sensors":
switch(tabBox.currentText) {
case "NTC specifications battery":
createEditorMc("tempEnableMaskBattery")
createEditorMc("noOfTempSensorPerModule")
createEditorMc("NTCLTC25Deg")
createEditorMc("NTCLTCBeta")
break;
case "NTC specifications expansion Board":
createEditorMc("tempEnableMaskExpansion")
createEditorMc("noOfExpansionBoard")
createEditorMc("noOfTempSensorPerExpansionBoard")
createEditorMc("NTCEXP25Deg")
createEditorMc("NTCEXPBeta")
break;
case "NTC advanced settings":
createEditorMc("maxUnderAndOverTemperatureErrorCount")
createEditorMc("humidityICType")
break;
default:
break;
}
break;
default:
break;
}
}
ColumnLayout {
id: column
anchors.fill: parent
spacing: 0
GridLayout {
Layout.fillWidth: true
columns: isHorizontal ? 2 : 1
rowSpacing: -5
ComboBox {
id: pageBox
Layout.fillWidth: true
model: [
"Power State",
"Limits",
"CAN",
"Sensors"
]
onCurrentTextChanged: {
var tabTextOld = tabBox.currentText
switch(currentText) {
case "Power State":
tabBox.model = [
"Onstate",
"Jump to"
]
break;
case "Limits":
tabBox.model = [
"Current",
"Temperature discharging",
"Temperature charging",
"Temperature cooling/heating",
"Temperature Master board"
]
break;
case "CAN":
tabBox.model = [
"CAN configuration",
"CAN messaging"
]
break;
case "Sensors":
tabBox.model = [
"NTC specifications battery",
"NTC enable",
"NTC specifications expansion Board"
]
break;
default:
break;
}
tabBox.visible = tabBox.currentText.length !== 0
if (tabTextOld === tabBox.currentText) {
updateEditors()
}
}
}
ComboBox {
id: tabBox
Layout.fillWidth: true
onCurrentTextChanged: {
updateEditors()
}
}
}
ScrollView {
id: scroll
Layout.fillWidth: true
Layout.fillHeight: true
contentWidth: column.width
clip: true
GridLayout {
id: scrollCol
anchors.fill: parent
columns: isHorizontal ? 2 : 1
}
}
RowLayout {
Layout.fillWidth: true
Button {
Layout.preferredWidth: 100
Layout.fillWidth: true
text: "Write"
onClicked: {
mCommands.setBMSconf(true)
}
}
Button {
Layout.preferredWidth: 100
Layout.fillWidth: true
text: "Read"
onClicked: {
mCommands.getBMSconf()
}
}
Button {
Layout.preferredWidth: 50
Layout.fillWidth: true
text: "..."
onClicked: menu.open()
Menu {
id: menu
width: 500
MenuItem {
text: "Read Default Settings"
onTriggered: {
mCommands.getBMSconfDefault()
}
}
}
}
}
}
Connections {
target: mCommands
// TODO: For some reason this does not work
onBmsConfigCheckResult: {
if (paramsNotSet.length > 0) {
var notUpdated = "The following parameters were truncated because " +
"they were beyond the hardware limits:\n"
for (var i = 0;i < paramsNotSet.length;i++) {
notUpdated += mbmsConf.getLongName(paramsNotSet[i]) + "\n"
}
VescIf.emitMessageDialog("Parameters truncated", notUpdated, false, false)
}
}
}
}

244
mobile/ConfigPageSignal.qml Normal file
View File

@@ -0,0 +1,244 @@
/*
Copyright 2020 Kevin Dionne kevin.dionne@ennoid.me
This file is part of ENNOID-BMS Tool.
ENNOID-BMS Tool is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ENNOID-BMS Tool is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import Ennoid.bmsinterface 1.0
import Ennoid.commands 1.0
import Ennoid.configparams 1.0
Item {
property Commands mCommands: VescIf.commands()
property var editorsVisible: []
property bool isHorizontal: width > height
ParamEditors {
id: editors
}
onIsHorizontalChanged: {
// updateEditors()
}
function addSeparator(text) {
editorsVisible.push(editors.createSeparator(scrollCol, text))
editorsVisible[editorsVisible.length - 1].Layout.columnSpan = isHorizontal ? 2 : 1
}
function destroyEditors() {
for (var i = 0;i < editorsVisible.length;i++) {
editorsVisible[i].destroy();
}
editorsVisible = []
}
function createEditorMc(param) {
editorsVisible.push(editors.createEditorMc(scrollCol, param))
editorsVisible[editorsVisible.length - 1].Layout.preferredWidth = 500
editorsVisible[editorsVisible.length - 1].Layout.fillsWidth = true
}
function updateEditors() {
destroyEditors()
switch (pageBox.currentText) {
case "Discharge Current":
createEditorMc("shuntLCFactor")
break;
case "Pack Voltage":
createEditorMc("voltageLCFactor")
createEditorMc("voltageLCOffset")
break;
case "Load Voltage":
createEditorMc("loadVoltageFactor")
createEditorMc("loadVoltageOffset")
break;
case "Charger Voltage":
createEditorMc("chargerVoltageFactor")
createEditorMc("chargerVoltageOffset")
break;
case "Data Source":
createEditorMc("packVoltageDataSource")
createEditorMc("packCurrentDataSource")
break;
case "Buzzer Control":
createEditorMc("buzzerSignalSource")
createEditorMc("buzzerPersistent")
break;
default:
break;
}
}
ColumnLayout {
id: column
anchors.fill: parent
spacing: 0
GridLayout {
Layout.fillWidth: true
columns: isHorizontal ? 2 : 1
rowSpacing: -5
ComboBox {
id: pageBox
Layout.fillWidth: true
model: [
"Discharge Current",
"Pack Voltage",
"Load Voltage",
"Charger Voltage",
"Data Source",
"Buzzer Control"
]
onCurrentTextChanged: {
var tabTextOld = tabBox.currentText
switch(currentText) {
case "Discharge Current":
tabBox.model = [
""
]
break;
case "Pack Voltage":
tabBox.model = [
""
]
break;
case "Load Voltage":
tabBox.model = [
""
]
break;
case "Charger Voltage":
tabBox.model = [
""
]
break;
case "Data Source":
tabBox.model = [
""
]
break;
case "Buzzer Control":
tabBox.model = [
""
]
break;
default:
break;
}
tabBox.visible = tabBox.currentText.length !== 0
if (tabTextOld === tabBox.currentText) {
updateEditors()
}
}
}
ComboBox {
id: tabBox
Layout.fillWidth: true
onCurrentTextChanged: {
updateEditors()
}
}
}
ScrollView {
id: scroll
Layout.fillWidth: true
Layout.fillHeight: true
contentWidth: column.width
clip: true
GridLayout {
id: scrollCol
anchors.fill: parent
columns: isHorizontal ? 2 : 1
}
}
RowLayout {
Layout.fillWidth: true
Button {
Layout.preferredWidth: 100
Layout.fillWidth: true
text: "Write"
onClicked: {
mCommands.setBMSconf(true)
}
}
Button {
Layout.preferredWidth: 100
Layout.fillWidth: true
text: "Read"
onClicked: {
mCommands.getBMSconf()
}
}
Button {
Layout.preferredWidth: 50
Layout.fillWidth: true
text: "..."
onClicked: menu.open()
Menu {
id: menu
width: 500
MenuItem {
text: "Read Default Settings"
onTriggered: {
mCommands.getBMSconfDefault()
}
}
}
}
}
}
Connections {
target: mCommands
// TODO: For some reason this does not work
onBmsConfigCheckResult: {
if (paramsNotSet.length > 0) {
var notUpdated = "The following parameters were truncated because " +
"they were beyond the hardware limits:\n"
for (var i = 0;i < paramsNotSet.length;i++) {
notUpdated += mbmsConf.getLongName(paramsNotSet[i]) + "\n"
}
VescIf.emitMessageDialog("Parameters truncated", notUpdated, false, false)
}
}
}
}

209
mobile/ConfigPageSwitch.qml Normal file
View File

@@ -0,0 +1,209 @@
/*
Copyright 2020 Kevin Dionne kevin.dionne@ennoid.me
This file is part of ENNOID-BMS Tool.
ENNOID-BMS Tool is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ENNOID-BMS Tool is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import Ennoid.bmsinterface 1.0
import Ennoid.commands 1.0
import Ennoid.configparams 1.0
Item {
property Commands mCommands: VescIf.commands()
property var editorsVisible: []
property bool isHorizontal: width > height
ParamEditors {
id: editors
}
onIsHorizontalChanged: {
// updateEditors()
}
function addSeparator(text) {
editorsVisible.push(editors.createSeparator(scrollCol, text))
editorsVisible[editorsVisible.length - 1].Layout.columnSpan = isHorizontal ? 2 : 1
}
function destroyEditors() {
for (var i = 0;i < editorsVisible.length;i++) {
editorsVisible[i].destroy();
}
editorsVisible = []
}
function createEditorMc(param) {
editorsVisible.push(editors.createEditorMc(scrollCol, param))
editorsVisible[editorsVisible.length - 1].Layout.preferredWidth = 500
editorsVisible[editorsVisible.length - 1].Layout.fillsWidth = true
}
function updateEditors() {
destroyEditors()
switch (pageBox.currentText) {
case "Discharge":
createEditorMc("LCUseDischarge")
createEditorMc("LCUsePrecharge")
createEditorMc("minimalPrechargePercentage")
createEditorMc("timeoutLCPreCharge")
createEditorMc("timeoutDischargeRetry")
break;
case "Charge":
createEditorMc("chargerEnabledThreshold")
createEditorMc("timeoutChargerDisconnected")
createEditorMc("allowChargingDuringDischarge")
createEditorMc("timeoutChargeRetry")
break;
default:
break;
}
}
ColumnLayout {
id: column
anchors.fill: parent
spacing: 0
GridLayout {
Layout.fillWidth: true
columns: isHorizontal ? 2 : 1
rowSpacing: -5
ComboBox {
id: pageBox
Layout.fillWidth: true
model: [
"Discharge",
"Charge"
]
onCurrentTextChanged: {
var tabTextOld = tabBox.currentText
switch(currentText) {
case "Discharge":
tabBox.model = [
""
]
break;
case "Charge":
tabBox.model = [
""
]
break;
default:
break;
}
tabBox.visible = tabBox.currentText.length !== 0
if (tabTextOld === tabBox.currentText) {
updateEditors()
}
}
}
ComboBox {
id: tabBox
Layout.fillWidth: true
onCurrentTextChanged: {
updateEditors()
}
}
}
ScrollView {
id: scroll
Layout.fillWidth: true
Layout.fillHeight: true
contentWidth: column.width
clip: true
GridLayout {
id: scrollCol
anchors.fill: parent
columns: isHorizontal ? 2 : 1
}
}
RowLayout {
Layout.fillWidth: true
Button {
Layout.preferredWidth: 100
Layout.fillWidth: true
text: "Write"
onClicked: {
mCommands.setBMSconf(true)
}
}
Button {
Layout.preferredWidth: 100
Layout.fillWidth: true
text: "Read"
onClicked: {
mCommands.getBMSconf()
}
}
Button {
Layout.preferredWidth: 50
Layout.fillWidth: true
text: "..."
onClicked: menu.open()
Menu {
id: menu
width: 500
MenuItem {
text: "Read Default Settings"
onTriggered: {
mCommands.getBMSconfDefault()
}
}
}
}
}
}
Connections {
target: mCommands
// TODO: For some reason this does not work
onBmsConfigCheckResult: {
if (paramsNotSet.length > 0) {
var notUpdated = "The following parameters were truncated because " +
"they were beyond the hardware limits:\n"
for (var i = 0;i < paramsNotSet.length;i++) {
notUpdated += mbmsConf.getLongName(paramsNotSet[i]) + "\n"
}
VescIf.emitMessageDialog("Parameters truncated", notUpdated, false, false)
}
}
}
}

393
mobile/ConnectBle.qml Normal file
View File

@@ -0,0 +1,393 @@
/*
Copyright 2017 - 2019 Benjamin Vedder benjamin@vedder.se
This file is part of VESC Tool.
VESC Tool is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
VESC Tool is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import Ennoid.bmsinterface 1.0
import Ennoid.bleuart 1.0
import Ennoid.commands 1.0
Item {
id: topItem
property BleUart mBle: VescIf.bleDevice()
property Commands mCommands: VescIf.commands()
property alias disconnectButton: disconnectButton
property bool isHorizontal: width > height
signal requestOpenControls()
ScrollView {
anchors.fill: parent
contentWidth: parent.width
clip: true
GridLayout {
id: grid
anchors.fill: parent
columns: isHorizontal ? 2 : 1
columnSpacing: 5
anchors.topMargin: 10
rowSpacing: 30
Image {
id: image
Layout.columnSpan: isHorizontal ? 2 : 1
Layout.preferredWidth: Math.min(topItem.width, topItem.height)
Layout.preferredHeight: (sourceSize.height * Layout.preferredWidth) / sourceSize.width
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
source: "qrc:/res/logo_white.png"
}
GroupBox {
id: bleConnBox
title: qsTr("BLE Connection")
Layout.fillWidth: true
Layout.columnSpan: 1
GridLayout {
anchors.topMargin: -5
anchors.bottomMargin: -5
anchors.fill: parent
clip: false
visible: true
rowSpacing: -10
columnSpacing: 5
rows: 5
columns: 6
Button {
id: setNameButton
text: qsTr("Name")
Layout.columnSpan: 2
Layout.preferredWidth: 500
Layout.fillWidth: true
enabled: bleBox.count > 0
onClicked: {
if (bleItems.rowCount() > 0) {
bleNameDialog.open()
} else {
VescIf.emitMessageDialog("Set BLE Device Name",
"No device selected.",
false, false);
}
}
}
Button {
text: "Pair"
Layout.fillWidth: true
Layout.preferredWidth: 500
Layout.columnSpan: 2
onClicked: {
pairDialog.openDialog()
}
}
Button {
id: scanButton
text: qsTr("Scan")
Layout.columnSpan: 2
Layout.preferredWidth: 500
Layout.fillWidth: true
onClicked: {
scanButton.enabled = false
mBle.startScan()
}
}
ComboBox {
id: bleBox
Layout.columnSpan: 6
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
transformOrigin: Item.Center
textRole: "key"
model: ListModel {
id: bleItems
}
}
Button {
id: disconnectButton
text: qsTr("Disconnect")
enabled: false
Layout.preferredWidth: 500
Layout.fillWidth: true
Layout.columnSpan: 3
onClicked: {
VescIf.disconnectPort()
}
}
Button {
id: connectButton
text: qsTr("Connect")
enabled: false
Layout.preferredWidth: 500
Layout.fillWidth: true
Layout.columnSpan: 3
onClicked: {
if (bleItems.rowCount() > 0) {
connectButton.enabled = false
VescIf.connectBle(bleItems.get(bleBox.currentIndex).value)
}
}
}
}
}
GroupBox {
id: canFwdBox
title: qsTr("CAN Forwarding")
Layout.fillWidth: true
ColumnLayout {
anchors.topMargin: -5
anchors.bottomMargin: -5
anchors.fill: parent
spacing: -10
RowLayout {
Layout.fillWidth: true
ComboBox {
id: canIdBox
Layout.fillWidth: true
textRole: "key"
model: ListModel {
id: canItems
}
onCurrentIndexChanged: {
if (fwdCanBox.checked && canItems.rowCount() > 0) {
mCommands.setCanSendId(canItems.get(canIdBox.currentIndex).value)
}
}
}
CheckBox {
id: fwdCanBox
text: qsTr("Activate")
enabled: canIdBox.currentIndex >= 0 && canIdBox.count > 0
onClicked: {
mCommands.setSendCan(fwdCanBox.checked, canItems.get(canIdBox.currentIndex).value)
canScanButton.enabled = !checked
canAllButton.enabled = !checked
}
}
}
ProgressBar {
id: canScanBar
visible: false
Layout.fillWidth: true
indeterminate: true
Layout.preferredHeight: canAllButton.height
}
RowLayout {
id: canButtonLayout
Layout.fillWidth: true
Button {
id: canAllButton
text: "List All (no Scan)"
Layout.fillWidth: true
Layout.preferredWidth: 500
onClicked: {
canItems.clear()
for (var i = 0;i < 255;i++) {
var name = "ENNOID-BMS " + i
canItems.append({ key: name, value: i })
}
canIdBox.currentIndex = 0
}
}
Button {
id: canScanButton
text: "Scan CAN Bus"
Layout.fillWidth: true
Layout.preferredWidth: 500
onClicked: {
canScanBar.indeterminate = true
canButtonLayout.visible = false
canScanBar.visible = true
canItems.clear()
enabled = false
canAllButton.enabled = false
mCommands.pingCan()
}
}
}
}
}
}
}
PairingDialog {
id: pairDialog
}
Timer {
interval: 500
running: !scanButton.enabled
repeat: true
property int dots: 0
onTriggered: {
var text = "S"
for (var i = 0;i < dots;i++) {
text = "-" + text + "-"
}
dots++;
if (dots > 3) {
dots = 0;
}
scanButton.text = text
}
}
Timer {
interval: 100
running: true
repeat: true
onTriggered: {
connectButton.enabled = (bleItems.rowCount() > 0) && !VescIf.isPortConnected() && !mBle.isConnecting()
disconnectButton.enabled = VescIf.isPortConnected()
}
}
Connections {
target: mBle
onScanDone: {
if (done) {
scanButton.enabled = true
scanButton.text = qsTr("Scan")
}
bleItems.clear()
for (var addr in devs) {
var name = devs[addr]
var name2 = name + " [" + addr + "]"
var setName = VescIf.getBleName(addr)
if (setName.length > 0) {
setName += " [" + addr + "]"
bleItems.insert(0, { key: setName, value: addr })
} else if (name.indexOf("ENNOID-BMS") !== -1) {
bleItems.insert(0, { key: name2, value: addr })
} else {
bleItems.append({ key: name2, value: addr })
}
}
connectButton.enabled = (bleItems.rowCount() > 0) && !VescIf.isPortConnected()
bleBox.currentIndex = 0
}
onBleError: {
VescIf.emitMessageDialog("BLE Error", info, false, false)
}
}
Connections {
target: mCommands
onPingCanRx: {
canItems.clear()
for (var i = 0;i < devs.length;i++) {
var name = "ENNOID-BMS " + devs[i]
canItems.append({ key: name, value: devs[i] })
}
canScanButton.enabled = true
canAllButton.enabled = true
canIdBox.currentIndex = 0
canButtonLayout.visible = true
canScanBar.visible = false
canScanBar.indeterminate = false
}
}
Dialog {
id: bleNameDialog
standardButtons: Dialog.Ok | Dialog.Cancel
modal: true
focus: true
title: "Set BLE Device Name"
width: parent.width - 20
height: 200
closePolicy: Popup.CloseOnEscape
x: 10
y: Math.max(parent.height / 4 - height / 2, 20)
parent: ApplicationWindow.overlay
Rectangle {
anchors.fill: parent
height: stringInput.implicitHeight + 14
border.width: 2
border.color: "#8d8d8d"
color: "#33a8a8a8"
radius: 3
TextInput {
id: stringInput
color: "#ffffff"
anchors.fill: parent
anchors.margins: 7
font.pointSize: 12
focus: true
}
}
onAccepted: {
if (stringInput.text.length > 0) {
var addr = bleItems.get(bleBox.currentIndex).value
var setName = stringInput.text + " [" + addr + "]"
VescIf.storeBleName(addr, stringInput.text)
VescIf.storeSettings()
bleItems.set(bleBox.currentIndex, { key: setName, value: addr })
bleBox.currentText
}
}
}
}

110
mobile/ConnectBle2.qml Normal file
View File

@@ -0,0 +1,110 @@
/*
Copyright 2017 Benjamin Vedder benjamin@vedder.se
This file is part of VESC Tool.
VESC Tool is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
VESC Tool is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.4
import Ennoid.bmsinterface 1.0
import Ennoid.bleuart 1.0
import Ennoid.commands 1.0
ConnectBleForm {
property BleUart mBle: VescIf.bleDevice()
property Commands mCommands: VescIf.commands()
scanButton.onClicked: {
mBle.startScan()
scanButton.enabled = false
scanButton.text = "Scanning"
}
connectButton.onClicked: {
if (bleItems.rowCount() > 0) {
connectButton.enabled = false
VescIf.connectBle(bleItems.get(bleBox.currentIndex).value)
}
}
disconnectButton.onClicked: {
VescIf.disconnectPort()
}
fwdCanBox.onClicked: {
mCommands.setSendCan(fwdCanBox.checked, canIdBox.value)
}
canIdBox.onValueChanged: {
mCommands.setCanSendId(canIdBox.value)
}
Timer {
interval: 500
running: !scanButton.enabled
repeat: true
property int dots: 0
onTriggered: {
var text = "Scanning"
for (var i = 0;i < dots;i++) {
text = "-" + text + "-"
}
dots++;
if (dots > 3) {
dots = 0;
}
scanButton.text = text
}
}
Timer {
interval: 100
running: true
repeat: true
onTriggered: {
connectButton.enabled = (bleItems.rowCount() > 0) && !VescIf.isPortConnected() && !mBle.isConnecting()
disconnectButton.enabled = VescIf.isPortConnected()
}
}
Connections {
target: mBle
onScanDone: {
if (done) {
scanButton.enabled = true
scanButton.text = qsTr("Scan")
}
bleItems.clear()
for (var name in devs) {
var name2 = name + " [" + devs[name] + "]"
if (name.indexOf("VESC") !== -1) {
bleItems.insert(0, { key: name2, value: devs[name] })
} else {
bleItems.append({ key: name2, value: devs[name] })
}
}
connectButton.enabled = (bleItems.rowCount() > 0) && !VescIf.isPortConnected()
bleBox.currentIndex = 0
}
}
}

View File

@@ -0,0 +1,159 @@
/*
Copyright 2017 Benjamin Vedder benjamin@vedder.se
This file is part of VESC Tool.
VESC Tool is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
VESC Tool is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
Item {
property alias scanButton: scanButton
property alias connectButton: connectButton
property alias disconnectButton: disconnectButton
property alias bleItems: bleItems
property alias canIdBox: canIdBox
property alias fwdCanBox: fwdCanBox
id: item1
width: 400
height: 400
property alias bleBox: bleBox
ColumnLayout {
anchors.fill: parent
Image {
id: image
Layout.preferredWidth: Math.min(parent.width, parent.height)
Layout.preferredHeight: (300 * Layout.preferredWidth) / 1549
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
source: "../res/logo_white.png"
}
Item {
// Spacer
Layout.fillHeight: true
Layout.fillWidth: true
}
GridLayout {
clip: false
visible: true
rowSpacing: 0
columnSpacing: 5
rows: 4
columns: 2
Label {
id: connectionName
text: "BLE Connection"
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
Layout.bottomMargin: 5
Layout.columnSpan: 2
}
ComboBox {
id: bleBox
Layout.columnSpan: 2
Layout.preferredHeight: 48
Layout.fillHeight: false
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
transformOrigin: Item.Center
textRole: "key"
model: ListModel {
id: bleItems
}
}
Button {
id: scanButton
text: qsTr("Scan")
Layout.columnSpan: 2
Layout.preferredHeight: 48
Layout.fillWidth: true
}
Button {
id: connectButton
text: qsTr("Connect")
enabled: false
Layout.preferredHeight: 48
Layout.preferredWidth: 100
Layout.fillWidth: true
}
Button {
id: disconnectButton
text: qsTr("Disconnect")
enabled: false
Layout.preferredHeight: 48
Layout.preferredWidth: 100
Layout.fillWidth: true
}
}
RowLayout {
Layout.fillWidth: true
CheckBox {
id: fwdCanBox
text: qsTr("CAN Forward")
}
SpinBox {
id: canIdBox
Layout.fillWidth: true
}
}
Item {
id: element
// Spacer
Layout.fillHeight: true
Layout.fillWidth: true
}
Item {
scale: 1.2
// Spacer
Layout.fillHeight: true
Layout.fillWidth: true
}
Item {
scale: 1.2
// Spacer
Layout.fillHeight: true
Layout.fillWidth: true
}
}
}

456
mobile/Controls.qml Normal file
View File

@@ -0,0 +1,456 @@
/*
Copyright 2018 Benjamin Vedder benjamin@vedder.se
This file is part of VESC Tool.
VESC Tool is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
VESC Tool is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import Ennoid.bmsinterface 1.0
import Ennoid.commands 1.0
import Ennoid.configparams 1.0
Item {
property int parentWidth: 10
property int parentHeight: 10
property Commands mCommands: VescIf.commands()
property ConfigParams mbmsConfig: VescIf.bmsConfig()
property ConfigParams mInfoConf: VescIf.infoConfig()
function openDialog() {
dialog.open()
opacitySlider.value = 1
}
function testConnected() {
if (VescIf.isPortConnected()) {
return true
} else {
VescIf.emitMessageDialog(
"Connection Error",
"The BMS is not connected. Please connect it to run detection.",
false, false)
return false
}
}
Dialog {
id: dialog
standardButtons: Dialog.Close
modal: false
focus: true
width: parentWidth - 40
height: parentHeight - 40
closePolicy: Popup.CloseOnEscape
x: 20
y: 10
background.opacity: opacitySlider.value
ColumnLayout {
anchors.fill: parent
SwipeView {
id: swipeView
currentIndex: tabBar.currentIndex
Layout.fillHeight: true
Layout.fillWidth: true
clip: true
Page {
background: Rectangle {
opacity: 0.0
}
ColumnLayout {
anchors.fill: parent
spacing: 0
DoubleSpinBox {
id: currentBox
Layout.fillWidth: true
decimals: 1
prefix: "I: "
suffix: " A"
realFrom: 0.0
realTo: 500.0
realValue: 3.0
realStepSize: 1.0
}
Rectangle {
Layout.fillHeight: true
}
CheckBox {
id: maintainCurrentBox
text: "Continue after release"
}
RowLayout {
Layout.fillWidth: true
Button {
Layout.fillWidth: true
Layout.preferredWidth: 100
text: "Set REV"
onPressedChanged: {
if (pressed) {
mCommands.setCurrent(-currentBox.realValue)
} else if (!maintainCurrentBox.checked) {
mCommands.setCurrent(0.0)
}
}
}
Button {
Layout.fillWidth: true
Layout.preferredWidth: 100
text: "Set FWD"
onPressedChanged: {
if (pressed) {
mCommands.setCurrent(currentBox.realValue)
} else if (!maintainCurrentBox.checked) {
mCommands.setCurrent(0.0)
}
}
}
}
Button {
Layout.fillWidth: true
text: "Brake"
onPressedChanged: {
if (pressed) {
mCommands.setCurrentBrake(currentBox.realValue)
} else if (!maintainCurrentBox.checked) {
mCommands.setCurrent(0.0)
}
}
}
Button {
Layout.fillWidth: true
text: "Release"
onClicked: {
mCommands.setCurrent(0.0)
}
}
}
}
Page {
background: Rectangle {
opacity: 0.0
}
ColumnLayout {
anchors.fill: parent
spacing: 0
DoubleSpinBox {
id: dutyBox
Layout.fillWidth: true
prefix: "D: "
decimals: 3
realFrom: 0.0
realTo: 1.0
realValue: 0.1
realStepSize: 0.01
}
Rectangle {
Layout.fillHeight: true
}
CheckBox {
id: maintainDutyBox
text: "Continue after release"
}
RowLayout {
Layout.fillWidth: true
Button {
Layout.fillWidth: true
Layout.preferredWidth: 100
text: "Set REV"
onPressedChanged: {
if (pressed) {
mCommands.setDutyCycle(-dutyBox.realValue)
} else if (!maintainDutyBox.checked) {
mCommands.setCurrent(0.0)
}
}
}
Button {
Layout.fillWidth: true
Layout.preferredWidth: 100
text: "Set FWD"
onPressedChanged: {
if (pressed) {
mCommands.setDutyCycle(dutyBox.realValue)
} else if (!maintainDutyBox.checked) {
mCommands.setCurrent(0.0)
}
}
}
}
Button {
Layout.fillWidth: true
text: "0 Duty (brake)"
onPressedChanged: {
if (pressed) {
mCommands.setDutyCycle(0.0)
} else if (!maintainDutyBox.checked) {
mCommands.setCurrent(0.0)
}
}
}
Button {
Layout.fillWidth: true
text: "Release"
onClicked: {
mCommands.setCurrent(0.0)
}
}
}
}
Page {
background: Rectangle {
opacity: 0.0
}
ColumnLayout {
anchors.fill: parent
spacing: 0
DoubleSpinBox {
id: speedBox
Layout.fillWidth: true
prefix: "\u03C9: "
suffix: " ERPM"
decimals: 1
realFrom: 0.0
realTo: 500000
realValue: 3000
realStepSize: 500
}
Rectangle {
Layout.fillHeight: true
}
CheckBox {
id: maintainSpeedBox
text: "Continue after release"
}
RowLayout {
Layout.fillWidth: true
Button {
Layout.fillWidth: true
Layout.preferredWidth: 100
text: "Set REV"
onPressedChanged: {
if (pressed) {
mCommands.setRpm(-speedBox.realValue)
} else if (!maintainSpeedBox.checked) {
mCommands.setCurrent(0.0)
}
}
}
Button {
Layout.fillWidth: true
Layout.preferredWidth: 100
text: "Set FWD"
onPressedChanged: {
if (pressed) {
mCommands.setRpm(speedBox.realValue)
} else if (!maintainSpeedBox.checked) {
mCommands.setCurrent(0.0)
}
}
}
}
Button {
Layout.fillWidth: true
text: "0 ERPM (brake)"
onPressedChanged: {
if (pressed) {
mCommands.setRpm(0.0)
} else if (!maintainSpeedBox.checked) {
mCommands.setCurrent(0.0)
}
}
}
Button {
Layout.fillWidth: true
text: "Release"
onClicked: {
mCommands.setCurrent(0.0)
}
}
}
}
Page {
background: Rectangle {
opacity: 0.0
}
ColumnLayout {
anchors.fill: parent
spacing: 0
DoubleSpinBox {
id: posBox
Layout.fillWidth: true
prefix: "P: "
suffix: " \u00B0"
decimals: 3
realFrom: 0
realTo: 360
realValue: 20
realStepSize: 0.1
}
Rectangle {
Layout.fillHeight: true
}
CheckBox {
id: maintainPosBox
text: "Continue after release"
}
Button {
Layout.fillWidth: true
text: "Set"
onPressedChanged: {
if (pressed) {
mCommands.setPos(posBox.realValue)
} else if (!maintainPosBox.checked) {
mCommands.setCurrent(0.0)
}
}
}
Button {
Layout.fillWidth: true
text: "Release"
onClicked: {
mCommands.setCurrent(0.0)
}
}
}
}
}
RowLayout {
Layout.fillWidth: true
Text {
color: "white"
text: qsTr("Opacity")
}
Slider {
id: opacitySlider
Layout.fillWidth: true
from: 0.1
to: 1.0
value: 1.0
}
}
}
header: Rectangle {
color: "#dbdbdb"
height: tabBar.height
TabBar {
id: tabBar
currentIndex: swipeView.currentIndex
anchors.fill: parent
implicitWidth: 0
clip: true
background: Rectangle {
opacity: 1
color: "#4f4f4f"
}
property int buttons: 4
property int buttonWidth: 120
TabButton {
text: qsTr("Current")
width: Math.max(tabBar.buttonWidth, tabBar.width / tabBar.buttons)
}
TabButton {
text: qsTr("Duty")
width: Math.max(tabBar.buttonWidth, tabBar.width / tabBar.buttons)
}
TabButton {
text: qsTr("RPM")
width: Math.max(tabBar.buttonWidth, tabBar.width / tabBar.buttons)
}
TabButton {
text: qsTr("Position")
width: Math.max(tabBar.buttonWidth, tabBar.width / tabBar.buttons)
}
}
}
}
Timer {
id: aliveTimer
interval: 200
running: true
repeat: true
onTriggered: {
if (VescIf.isPortConnected() && dialog.visible) {
mCommands.sendAlive()
}
}
}
}

217
mobile/CustomGauge.qml Normal file
View File

@@ -0,0 +1,217 @@
/*
Copyright 2018 - 2019 Benjamin Vedder benjamin@vedder.se
This file is part of VESC Tool.
VESC Tool is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
VESC Tool is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.0
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import QtQuick.Extras 1.4
import QtGraphicalEffects 1.0
import QtQuick.Controls.Material 2.2
Item {
property alias minimumValue: gauge.minimumValue
property alias maximumValue: gauge.maximumValue
property alias value: gauge.value
property string unitText: ""
property string typeText: ""
property string tickmarkSuffix: ""
property double labelStep: 10
property double tickmarkScale: 1
property color traceColor: "#606060"
property double maxAngle: 144
property double minAngle: -144
CircularGauge {
id: gauge
anchors.fill: parent
Behavior on value {
NumberAnimation {
easing.type: Easing.OutCirc
duration: 100
}
}
style: CircularGaugeStyle {
id: style
labelStepSize: labelStep
tickmarkStepSize: labelStep
labelInset: outerRadius * 0.28
tickmarkInset: 2
minorTickmarkInset: 2
minimumValueAngle: minAngle
maximumValueAngle: maxAngle
background:
Canvas {
property double value: gauge.value
anchors.fill: parent
onValueChanged: requestPaint()
function d2r(degrees) {
return degrees * (Math.PI / 180.0);
}
onPaint: {
var ctx = getContext("2d");
ctx.reset();
ctx.beginPath();
var gradient = ctx.createRadialGradient(outerRadius, outerRadius, 0,
outerRadius, outerRadius, outerRadius)
gradient.addColorStop(0.7, Material.background)
gradient.addColorStop(1, traceColor)
ctx.fillStyle = gradient
ctx.arc(outerRadius, outerRadius, outerRadius, 0, Math.PI * 2)
ctx.fill()
ctx.beginPath();
ctx.strokeStyle = Material.background
ctx.lineWidth = outerRadius
ctx.arc(outerRadius,
outerRadius,
outerRadius / 2,
d2r(valueToAngle(Math.max(gauge.value, 0)) - 90),
d2r(valueToAngle(gauge.maximumValue + 1) - 90));
ctx.arc(outerRadius,
outerRadius,
outerRadius / 2,
d2r(valueToAngle(gauge.minimumValue) - 90),
d2r(valueToAngle(Math.min(gauge.value, 0)) - 90));
ctx.stroke();
ctx.beginPath();
ctx.arc(outerRadius,
outerRadius,
outerRadius / 2,
d2r(valueToAngle(gauge.maximumValue) - 90),
d2r(valueToAngle(gauge.minimumValue) - 90));
ctx.stroke();
ctx.beginPath();
ctx.strokeStyle = "darkGray"
ctx.lineWidth = 1
ctx.arc(outerRadius,
outerRadius,
outerRadius - 0.5,
0, 2 * Math.PI);
ctx.stroke();
}
}
needle: Item {
y: -outerRadius * 0.82
height: outerRadius * 0.18
Rectangle {
id: needle
height: parent.height
color: "red"
width: height * 0.13
antialiasing: true
radius: 10
}
Glow {
anchors.fill: needle
radius: 5
samples: 10
spread: 0.6
color: "darkred"
source: needle
}
}
foreground: Item {
Text {
id: speedLabel
anchors.centerIn: parent
text: gauge.value.toFixed(0)
horizontalAlignment: Text.AlignHCenter
font.pixelSize: outerRadius * 0.3
color: "white"
antialiasing: true
}
Text {
id: speedLabelUnit
text: unitText
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: speedLabel.bottom
horizontalAlignment: Text.AlignHCenter
font.pixelSize: outerRadius * 0.15
color: "white"
antialiasing: true
}
Text {
id: typeLabel
text: typeText
verticalAlignment: Text.AlignVCenter
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: speedLabel.top
anchors.bottomMargin: outerRadius * 0.1
horizontalAlignment: Text.AlignHCenter
font.pixelSize: outerRadius * 0.15
color: "white"
antialiasing: true
}
}
function isCovered(value) {
var res = false
if (gauge.value > 0) {
if (value <= gauge.value && value >= 0) {
res = true
}
} else {
if (value >= gauge.value && value <= 0) {
res = true
}
}
return res
}
tickmarkLabel: Text {
font.pixelSize: outerRadius * 0.15
text: parseFloat(styleData.value * tickmarkScale).toFixed(0) + tickmarkSuffix
color: isCovered(styleData.value) ? "white" : "darkGray"
antialiasing: true
}
tickmark: Rectangle {
implicitWidth: 2
implicitHeight: outerRadius * 0.09
antialiasing: true
smooth: true
color: isCovered(styleData.value) ? "white" : "darkGray"
}
minorTickmark: Rectangle {
implicitWidth: 1.5
implicitHeight: outerRadius * 0.05
antialiasing: true
smooth: true
color: isCovered(styleData.value) ? "white" : "darkGray"
}
}
}
}

262
mobile/DetectBldc.qml Normal file
View File

@@ -0,0 +1,262 @@
/*
Copyright 2018 Benjamin Vedder benjamin@vedder.se
This file is part of VESC Tool.
VESC Tool is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
VESC Tool is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import Vedder.vesc.bmsinterface 1.0
import Vedder.vesc.commands 1.0
import Vedder.vesc.configparams 1.0
Item {
property int parentWidth: 10
property real intLim: 0.0
property real coupling: 0.0
property var hallTable: []
property int hallRes: -4
property bool resultReceived: false
property Commands mCommands: VescIf.commands()
property ConfigParams mbmsConfig: VescIf.bmsConfig()
property ConfigParams mInfoConf: VescIf.infoConfig()
function openDialog() {
dialog.open()
}
function updateDisplay() {
var txt = ""
txt +=
"Integrator limit : " + parseFloat(intLim).toFixed(2) + "\n" +
"BEMF Coupling : " + parseFloat(coupling).toFixed(2) + "\n"
if (hallRes == 0) {
txt += "Detected hall sensor table:\n"
for (var i = 0;i < hallTable.length;i++) {
txt += "" + hallTable[i]
if (i != hallTable.length - 1) {
txt += ", "
}
}
} else if (hallRes == -1) {
txt += "Hall sensor detection failed:\n"
for (var i = 0;i < hallTable.length;i++) {
txt += "" + hallTable[i]
if (i != hallTable.length - 1) {
txt += ", "
}
}
} else if (hallRes == -2) {
txt += "WS2811 enabled. Hall sensors cannot be used."
} else if (hallRes == -3) {
txt += "Encoder enabled. Hall sensors cannot be used."
} else if (hallRes == -4) {
txt += "Detected hall sensor table:"
} else {
txt += "Unknown hall error: " + hallRes
}
resultArea.text = txt
}
function testConnected() {
if (VescIf.isPortConnected()) {
return true
} else {
VescIf.emitMessageDialog(
"Connection Error",
"The VESC is not connected. Please connect it to run detection.",
false, false)
return false
}
}
Component.onCompleted: {
updateDisplay()
}
Dialog {
id: dialog
standardButtons: Dialog.Close
modal: true
focus: true
width: parentWidth - 20
height: Math.min(implicitHeight, column.height - 40)
closePolicy: Popup.CloseOnEscape
x: 10
y: 10
ScrollView {
anchors.fill: parent
clip: true
contentWidth: parent.width
ColumnLayout {
anchors.fill: parent
spacing: 0
DoubleSpinBox {
id: currentBox
Layout.fillWidth: true
decimals: 2
realValue: 5.0
realFrom: 0.0
realTo: 200.0
prefix: "I: "
suffix: " A"
}
DoubleSpinBox {
id: dutyBox
Layout.fillWidth: true
decimals: 2
realValue: 0.05
realFrom: 0.0
realTo: 1.0
realStepSize: 0.01
prefix: "D: "
}
DoubleSpinBox {
id: erpmBox
Layout.fillWidth: true
decimals: 1
realValue: 450.0
realFrom: 0.0
realTo: 20000.0
realStepSize: 10.0
prefix: "\u03C9: "
suffix: " ERPM"
}
RowLayout {
Layout.fillWidth: true
Button {
text: "Help"
Layout.fillWidth: true
Layout.preferredWidth: 50
onClicked: {
VescIf.emitMessageDialog(
mInfoConf.getLongName("help_bldc_detect"),
mInfoConf.getDescription("help_bldc_detect"),
true, true)
}
}
Button {
text: "Detect"
Layout.preferredWidth: 50
Layout.fillWidth: true
onClicked: {
if (!testConnected()) {
return
}
detectDialog.open()
}
}
}
TextArea {
id: resultArea
Layout.fillWidth: true
Layout.preferredHeight: 180
readOnly: true
font.family: "DejaVu Sans Mono"
}
Button {
text: "Apply & Close"
Layout.fillWidth: true
onClicked: {
if (!resultReceived) {
VescIf.emitMessageDialog("Apply Detection Result",
"Detection result not received. Make sure to run the detection first.",
false, false)
return
}
mbmsConfig.updateParamDouble("sl_bemf_coupling_k", coupling)
mbmsConfig.updateParamDouble("sl_cycle_int_limit", intLim)
if (hallRes == 0) {
for(var i = 0;i < 7;i++) {
mbmsConfig.updateParamInt("hall_table_" + i, hallTable[i])
}
}
dialog.close()
}
}
}
}
}
Dialog {
id: detectDialog
standardButtons: Dialog.Ok | Dialog.Cancel
modal: true
focus: true
width: parentWidth - 20
closePolicy: Popup.CloseOnEscape
title: "Detect BLDC Parameters"
x: 10
y: dialog.y + dialog.height / 2 - height / 2
Text {
id: detectLabel
verticalAlignment: Text.AlignVCenter
anchors.fill: parent
wrapMode: Text.WordWrap
text:
"This is going to spin up the motor. Make " +
"sure that nothing is in the way."
}
onAccepted: {
mCommands.detectMotorParam(currentBox.realValue, erpmBox.realValue, dutyBox.realValue)
}
}
Connections {
target: mCommands
onBldcDetectReceived: {
if (param.cycle_int_limit < 0.01 && param.bemf_coupling_k < 0.01) {
VescIf.emitStatusMessage("Bad Detection Result Received", false)
VescIf.emitMessageDialog("BLDC Detection",
"Bad Detection Result Received",
false, false)
} else {
VescIf.emitStatusMessage("Detection Result Received", true)
intLim = param.cycle_int_limit
coupling = param.bemf_coupling_k
hallTable = param.hall_table
hallRes = param.hall_res
resultReceived = true
updateDisplay()
}
}
}
}

194
mobile/DetectFocEncoder.qml Normal file
View File

@@ -0,0 +1,194 @@
/*
Copyright 2018 Benjamin Vedder benjamin@vedder.se
This file is part of VESC Tool.
VESC Tool is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
VESC Tool is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import Vedder.vesc.bmsinterface 1.0
import Vedder.vesc.commands 1.0
import Vedder.vesc.configparams 1.0
Item {
property int parentWidth: 10
property real mOffset: 0.0
property real mRatio: 0.0
property bool mInverted: false
property Commands mCommands: VescIf.commands()
property ConfigParams mbmsConfig: VescIf.bmsConfig()
property ConfigParams mInfoConf: VescIf.infoConfig()
function openDialog() {
dialog.open()
}
function updateDisplay() {
resultArea.text =
"Offset : " + parseFloat(mOffset).toFixed(1) + "\n" +
"Ratio : " + parseFloat(mRatio).toFixed(1) + "\n" +
"Inverted : " + mInverted
}
function testConnected() {
if (VescIf.isPortConnected()) {
return true
} else {
VescIf.emitMessageDialog(
"Connection Error",
"The VESC is not connected. Please connect it to run detection.",
false, false)
return false
}
}
Component.onCompleted: {
updateDisplay()
}
Dialog {
id: dialog
standardButtons: Dialog.Close
modal: true
focus: true
width: parentWidth - 20
height: Math.min(implicitHeight, column.height - 40)
closePolicy: Popup.CloseOnEscape
x: 10
y: 10
ScrollView {
anchors.fill: parent
clip: true
contentWidth: parent.width
ColumnLayout {
anchors.fill: parent
spacing: 0
DoubleSpinBox {
id: currentBox
Layout.fillWidth: true
decimals: 2
realValue: 10.0
realFrom: 0.0
realTo: 200.0
prefix: "I: "
suffix: " A"
}
RowLayout {
Layout.fillWidth: true
Button {
text: "Help"
Layout.preferredWidth: 50
Layout.fillWidth: true
onClicked: {
VescIf.emitMessageDialog(
mInfoConf.getLongName("help_foc_encoder_detect"),
mInfoConf.getDescription("help_foc_encoder_detect"),
true, true)
}
}
Button {
text: "Detect"
Layout.preferredWidth: 50
Layout.fillWidth: true
onClicked: {
if (!testConnected()) {
return
}
detectDialog.open()
}
}
}
TextArea {
id: resultArea
Layout.fillWidth: true
Layout.preferredHeight: 200
readOnly: true
wrapMode: TextEdit.WordWrap
font.family: "DejaVuSansMono"
}
Button {
text: "Apply & Close"
Layout.fillWidth: true
onClicked: {
mbmsConfig.updateParamDouble("foc_encoder_offset", mOffset)
mbmsConfig.updateParamDouble("foc_encoder_ratio", mRatio)
mbmsConfig.updateParamBool("foc_encoder_inverted", mInverted)
VescIf.emitStatusMessage("Encoder Parameters Applied", true)
dialog.close()
}
}
}
}
}
Dialog {
id: detectDialog
standardButtons: Dialog.Ok | Dialog.Cancel
modal: true
focus: true
width: parentWidth - 20
closePolicy: Popup.CloseOnEscape
title: "Detect FOC Encoder Parameters"
x: 10
y: dialog.y + dialog.height / 2 - height / 2
Text {
id: detectLambdaLabel
verticalAlignment: Text.AlignVCenter
anchors.fill: parent
wrapMode: Text.WordWrap
text:
"This is going to turn the motor slowly. Make " +
"sure that nothing is in the way."
}
onAccepted: {
mCommands.measureEncoder(currentBox.realValue)
}
}
Connections {
target: mCommands
onEncoderParamReceived: {
if (offset > 1000.0) {
VescIf.emitStatusMessage("Encoder not enabled in firmware", false)
VescIf.emitMessageDialog("Error",
"Encoder support is not enabled. Enable it in the general settings.",
false, false)
} else {
VescIf.emitStatusMessage("Encoder Result Received", true)
mOffset = offset
mRatio = ratio
mInverted = inverted
updateDisplay()
}
}
}
}

204
mobile/DetectFocHall.qml Normal file
View File

@@ -0,0 +1,204 @@
/*
Copyright 2018 Benjamin Vedder benjamin@vedder.se
This file is part of VESC Tool.
VESC Tool is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
VESC Tool is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import Vedder.vesc.bmsinterface 1.0
import Vedder.vesc.commands 1.0
import Vedder.vesc.configparams 1.0
Item {
property int parentWidth: 10
property var table: []
property Commands mCommands: VescIf.commands()
property ConfigParams mbmsConfig: VescIf.bmsConfig()
property ConfigParams mInfoConf: VescIf.infoConfig()
function openDialog() {
dialog.open()
}
function updateDisplay() {
var txt = "Hall Table:\n"
for (var i = 0;i < table.length;i++) {
txt += "" + table[i]
if (i != table.length - 1) {
txt += ", "
}
}
resultArea.text = txt
}
function testConnected() {
if (VescIf.isPortConnected()) {
return true
} else {
VescIf.emitMessageDialog(
"Connection Error",
"The VESC is not connected. Please connect it to run detection.",
false, false)
return false
}
}
Component.onCompleted: {
updateDisplay()
}
Dialog {
id: dialog
standardButtons: Dialog.Close
modal: true
focus: true
width: parentWidth - 20
height: Math.min(implicitHeight, column.height - 40)
closePolicy: Popup.CloseOnEscape
x: 10
y: 10
ScrollView {
anchors.fill: parent
clip: true
contentWidth: parent.width
ColumnLayout {
anchors.fill: parent
spacing: 0
DoubleSpinBox {
id: currentBox
Layout.fillWidth: true
decimals: 2
realValue: 10.0
realFrom: 0.0
realTo: 200.0
prefix: "I: "
suffix: " A"
}
RowLayout {
Layout.fillWidth: true
Button {
text: "Help"
Layout.preferredWidth: 50
Layout.fillWidth: true
onClicked: {
VescIf.emitMessageDialog(
mInfoConf.getLongName("help_foc_hall_detect"),
mInfoConf.getDescription("help_foc_hall_detect"),
true, true)
}
}
Button {
text: "Detect"
Layout.preferredWidth: 50
Layout.fillWidth: true
onClicked: {
if (!testConnected()) {
return
}
detectDialog.open()
}
}
}
TextArea {
id: resultArea
Layout.fillWidth: true
Layout.preferredHeight: 200
readOnly: true
wrapMode: TextEdit.WordWrap
font.family: "DejaVuSansMono"
}
Button {
text: "Apply & Close"
Layout.fillWidth: true
onClicked: {
if (table.length != 8) {
VescIf.emitMessageDialog("Apply Error",
"Hall table is empty.",
false, false)
return
}
for(var i = 0;i < 7;i++) {
mbmsConfig.updateParamInt("foc_hall_table_" + i, table[i])
}
VescIf.emitStatusMessage("Hall Sensor Parameters Applied", true)
dialog.close()
}
}
}
}
}
Dialog {
id: detectDialog
standardButtons: Dialog.Ok | Dialog.Cancel
modal: true
focus: true
width: parentWidth - 20
closePolicy: Popup.CloseOnEscape
title: "Detect FOC Hall Sensor Parameters"
x: 10
y: dialog.y + dialog.height / 2 - height / 2
Text {
id: detectLambdaLabel
verticalAlignment: Text.AlignVCenter
anchors.fill: parent
wrapMode: Text.WordWrap
text:
"This is going to turn the motor slowly. Make " +
"sure that nothing is in the way."
}
onAccepted: {
mCommands.measureHallFoc(currentBox.realValue)
}
}
Connections {
target: mCommands
onFocHallTableReceived: {
if (res !== 0) {
VescIf.emitStatusMessage("Bad FOC Hall Detection Result Received", false)
VescIf.emitMessageDialog("Bad FOC Hall Detection Result Received",
"Could not detect hall sensors. Make sure that everything " +
"is connected properly.",
false, false)
} else {
table = hall_table
updateDisplay()
}
}
}
}

367
mobile/DetectFocParam.qml Normal file
View File

@@ -0,0 +1,367 @@
/*
Copyright 2018 Benjamin Vedder benjamin@vedder.se
This file is part of VESC Tool.
VESC Tool is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
VESC Tool is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import Vedder.vesc.bmsinterface 1.0
import Vedder.vesc.commands 1.0
import Vedder.vesc.configparams 1.0
Item {
property int parentWidth: 10
property real res: 0.0
property real ind: 0.0
property real lambda: 0.0
property real kp: 0.0
property real ki: 0.0
property real gain: 0.0
property Commands mCommands: VescIf.commands()
property ConfigParams mbmsConfig: VescIf.bmsConfig()
property ConfigParams mInfoConf: VescIf.infoConfig()
function openDialog() {
dialog.open()
}
function updateDisplay() {
resultArea.text =
"R : " + parseFloat(res * 1e3).toFixed(2) + " m\u03A9\n" +
"L : " + parseFloat(ind * 1e6).toFixed(2) + " µH\n" +
"\u03BB : " + parseFloat(lambda * 1e3).toFixed(3) + " mWb\n" +
"KP : " + parseFloat(kp).toFixed(4) + "\n" +
"KI : " + parseFloat(ki).toFixed(2) + "\n" +
"Gain : " + parseFloat(gain).toFixed(2)
}
function calcKpKi() {
if (res < 1e-10) {
VescIf.emitMessageDialog("Calculate Error",
"R is 0. Please measure it first.",
false, false)
return;
}
if (ind < 1e-10) {
VescIf.emitMessageDialog("Calculate Error",
"L is 0. Please measure it first.",
false, false)
return;
}
// https://e2e.ti.com/blogs_/b/motordrivecontrol/archive/2015/07/20/teaching-your-pi-controller-to-behave-part-ii
var tc = tcBox.realValue * 1e-6
var bw = 1.0 / tc
kp = ind * bw;
ki = res * bw;
updateDisplay()
}
function calcGain() {
if (lambda < 1e-10) {
VescIf.emitMessageDialog("Calculate Error",
"\u03BB is 0. Please measure it first.",
false, false)
return;
}
gain = 0.001 / (lambda * lambda)
updateDisplay()
}
function testConnected() {
if (VescIf.isPortConnected()) {
return true
} else {
VescIf.emitMessageDialog(
"Connection Error",
"The VESC is not connected. Please connect it to run detection.",
false, false)
return false
}
}
Component.onCompleted: {
updateDisplay()
}
Dialog {
id: dialog
standardButtons: Dialog.Close
modal: true
focus: true
width: parentWidth - 20
height: Math.min(implicitHeight, column.height - 40)
closePolicy: Popup.CloseOnEscape
x: 10
y: 10
ScrollView {
anchors.fill: parent
clip: true
contentWidth: parent.width
ColumnLayout {
anchors.fill: parent
spacing: 0
DoubleSpinBox {
id: currentBox
Layout.fillWidth: true
decimals: 2
realValue: 5.0
realFrom: 0.0
realTo: 200.0
prefix: "I: "
suffix: " A"
}
DoubleSpinBox {
id: dutyBox
Layout.fillWidth: true
decimals: 2
realValue: 0.5
realFrom: 0.0
realTo: 1.0
realStepSize: 0.1
prefix: "D: "
}
DoubleSpinBox {
id: erpmBox
Layout.fillWidth: true
decimals: 1
realValue: 450.0
realFrom: 0.0
realTo: 20000.0
realStepSize: 10.0
prefix: "\u03C9: "
suffix: " ERPM"
}
DoubleSpinBox {
id: tcBox
Layout.fillWidth: true
decimals: 1
realValue: 1000.0
realFrom: 0.0
realTo: 1000000.0
realStepSize: 100.0
prefix: "T: "
suffix: " µS"
}
Button {
text: "Help"
Layout.fillWidth: true
onClicked: {
VescIf.emitMessageDialog(
mInfoConf.getLongName("help_foc_detect"),
mInfoConf.getDescription("help_foc_detect"),
true, true)
}
}
RowLayout {
Layout.fillWidth: true
Button {
text: "Detect R&L"
Layout.preferredWidth: 50
Layout.fillWidth: true
onClicked: {
if (!testConnected()) {
return
}
detectRlDialog.open()
}
}
Button {
text: "DETECT \u03BB"
font.capitalization: Font.MixedCase
Layout.preferredWidth: 50
Layout.fillWidth: true
onClicked: {
if (!testConnected()) {
return
}
if (res < 1e-9) {
VescIf.emitMessageDialog("Detect",
"R is 0. Please measure it first.",
false, false)
} else {
detectLambdaDialog.open()
}
}
}
}
Button {
text: "CALCULATE KP, KI AND \u03BB"
font.capitalization: Font.MixedCase
Layout.fillWidth: true
onClicked: {
calcKpKi()
calcGain()
}
}
TextArea {
id: resultArea
Layout.fillWidth: true
Layout.preferredHeight: 200
readOnly: true
font.family: "DejaVu Sans Mono"
}
Button {
text: "Apply & Close"
Layout.fillWidth: true
onClicked: {
if (res < 1e-10) {
VescIf.emitMessageDialog("Apply Error",
"R is 0. Please measure it first.",
false, false)
return
}
if (ind < 1e-10) {
VescIf.emitMessageDialog("Apply Error",
"L is 0. Please measure it first.",
false, false)
return
}
if (lambda < 1e-10) {
VescIf.emitMessageDialog("Apply Error",
"\u03BB is 0. Please measure it first.",
false, false)
return
}
calcKpKi()
calcGain()
mbmsConfig.updateParamDouble("foc_motor_r", res)
mbmsConfig.updateParamDouble("foc_motor_l", ind)
mbmsConfig.updateParamDouble("foc_motor_flux_linkage", lambda)
mbmsConfig.updateParamDouble("foc_current_kp", kp)
mbmsConfig.updateParamDouble("foc_current_ki", ki)
mbmsConfig.updateParamDouble("foc_observer_gain", gain * 1e6)
dialog.close()
}
}
}
}
}
Dialog {
id: detectRlDialog
standardButtons: Dialog.Ok
modal: true
focus: true
width: parentWidth - 20
closePolicy: Popup.CloseOnEscape
title: "Measure R & L"
x: 10
y: dialog.y + dialog.height / 2 - height / 2
Text {
id: detectRlLabel
verticalAlignment: Text.AlignVCenter
anchors.fill: parent
wrapMode: Text.WordWrap
text:
"When measuring R & L the motor is going to make some noises, but " +
"not rotate. These noises are completely normal, so don't unplug " +
"anything unless you see smoke."
}
onAccepted: {
mCommands.measureRL()
}
}
Dialog {
id: detectLambdaDialog
standardButtons: Dialog.Ok | Dialog.Cancel
modal: true
focus: true
width: parentWidth - 20
closePolicy: Popup.CloseOnEscape
title: "Warning"
x: 10
y: dialog.y + dialog.height / 2 - height / 2
Text {
id: detectLambdaLabel
verticalAlignment: Text.AlignVCenter
anchors.fill: parent
wrapMode: Text.WordWrap
text:
"<font color=\"red\">Warning: </font>" +
"This is going to spin up the motor. Make " +
"sure that nothing is in the way."
}
onAccepted: {
mCommands.measureLinkage(currentBox.realValue, erpmBox.realValue, dutyBox.realValue, res)
}
}
Connections {
target: mCommands
onMotorRLReceived: {
if (r < 1e-9 && l < 1e-9) {
VescIf.emitStatusMessage("Bad FOC Detection Result Received", false)
VescIf.emitMessageDialog("Bad Detection Result",
"Could not measure the motor resistance and inductance.",
false, false)
} else {
VescIf.emitStatusMessage("FOC Detection Result Received", true)
res = r
ind = l * 1e-6
calcKpKi()
}
}
onMotorLinkageReceived: {
if (flux_linkage < 1e-9) {
VescIf.emitStatusMessage("Bad FOC Detection Result Received", false)
VescIf.emitMessageDialog("Bad Detection Result",
"Could not measure the flux linkage properly. Adjust " +
"the start parameters according to the help text and try again.",
false, false)
} else {
VescIf.emitStatusMessage("FOC Detection Result Received", true)
lambda = flux_linkage
calcGain()
}
}
}
}

69
mobile/DoubleSpinBox.qml Normal file
View File

@@ -0,0 +1,69 @@
/*
Copyright 2018 Benjamin Vedder benjamin@vedder.se
This file is part of VESC Tool.
VESC Tool is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
VESC Tool is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// Based on https://stackoverflow.com/questions/43406830/how-to-use-float-in-a-qml-spinbox
import QtQuick 2.0
import QtQuick.Controls 2.2
Item {
height: spinbox.implicitHeight
property int decimals: 2
property real realValue: 0.0
property real realFrom: 0.0
property real realTo: 100.0
property real realStepSize: 1.0
property string suffix: ""
property string prefix: ""
SpinBox{
id: spinbox
anchors.fill: parent
editable: true
// wheelEnabled: true
property real factor: Math.pow(10, decimals)
stepSize: realStepSize * factor
value: Math.round(realValue * factor)
to : realTo * factor
from : realFrom * factor
validator: DoubleValidator {
bottom: Math.min(spinbox.from, spinbox.to)*spinbox.factor
top: Math.max(spinbox.from, spinbox.to)*spinbox.factor
}
textFromValue: function(value, locale) {
return prefix + parseFloat(value * 1.0 / factor).toFixed(decimals) + suffix;
}
valueFromText: function(text, locale) {
return Math.round(parseFloat(text.replace(",", ".").
replace(suffix, "").
replace(prefix, "")) * factor)
}
onValueChanged: {
if (Math.round(realValue * factor) !== value) {
realValue = value * 1.0 / factor
}
}
}
}

214
mobile/FilePicker.qml Normal file
View File

@@ -0,0 +1,214 @@
/**
MIT License
Copyright (c) 2017 Andrey Semenov
Copyright (c) 2017 Benjamin Vedder: Added cancel button, embedded configuration colors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
import QtQuick 2.0
import QtQuick.Controls 1.4 as OldControls
import QtQuick.Controls 2.2
import Qt.labs.folderlistmodel 2.1
import QtQuick.Layouts 1.3
import QtQuick.Window 2.0
Item {
id:picker
signal fileSelected(string fileName)
readonly property real textmargin: 8
readonly property real textSize: 10
readonly property real headerTextSize: 12
readonly property real buttonHeight: 24
readonly property real rowHeight: 36
readonly property real toolbarHeight: 48
property bool showDotAndDotDot: false
property bool showHidden: true
property bool showDirsFirst: true
property string folder: "file:///sdcard"
property string nameFilters: "*.*"
function currentFolder() {
return folderListModel.folder;
}
function isFolder(fileName) {
return folderListModel.isFolder(folderListModel.indexOf(folderListModel.folder + "/" + fileName));
}
function canMoveUp() {
return folderListModel.folder.toString() !== "file:///"
}
function onItemClick(fileName) {
if(!isFolder(fileName)) {
fileSelected(fileName)
return;
}
if(fileName === ".." && canMoveUp()) {
folderListModel.folder = folderListModel.parentFolder
} else if(fileName !== "." && fileName !== "..") {
if(folderListModel.folder.toString() === "file:///") {
folderListModel.folder += fileName
} else {
folderListModel.folder += "/" + fileName
}
}
}
Rectangle {
id: toolbar
anchors.right: parent.right
anchors.left: parent.left
anchors.top: parent.top
height: toolbarHeight
color: "#3c3c3c"
Button {
id: button
text: ".."
anchors.right: parent.right
anchors.rightMargin: buttonHeight
anchors.bottom: parent.bottom
anchors.top: parent.top
enabled: canMoveUp()
flat: true
onClicked: {
if(canMoveUp) {
folderListModel.folder = folderListModel.parentFolder
}
}
}
Text {
id: filePath
color: "white"
text: folderListModel.folder.toString().replace("file:///", "►").replace("/", "►").replace("/", "►").replace("/", "►").replace("/", "►")
renderType: Text.NativeRendering
elide: Text.ElideMiddle
anchors.right: button.left
font.italic: true
font.bold: true
verticalAlignment: Text.AlignVCenter
anchors.left: parent.left
anchors.leftMargin: buttonHeight
anchors.bottom: parent.bottom
anchors.top: parent.top
font.pixelSize: textSize
}
}
FolderListModel {
id: folderListModel
showDotAndDotDot: picker.showDotAndDotDot
showHidden: picker.showHidden
showDirsFirst: picker.showDirsFirst
folder: picker.folder
nameFilters: picker.nameFilters
}
ColumnLayout {
anchors.top: toolbar.bottom
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.left: parent.left
OldControls.TableView {
id: view
Layout.fillHeight: true
Layout.fillWidth: true
model: folderListModel
headerDelegate:headerDelegate
rowDelegate: Rectangle {
height: rowHeight
color: "#6f6f6f"
}
OldControls.TableViewColumn {
title: qsTr("FileName")
role: "fileName"
resizable: true
delegate: fileDelegate
}
Component {
id: fileDelegate
Item {
height: rowHeight
Rectangle {
color: "#6f6f6f"
anchors.fill: parent
MouseArea {
anchors.fill: parent
onClicked: {
onItemClick(fileNameText.text)
}
}
Text {
id: fileNameText
color: "white"
height: width
anchors.left: image.right
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: parent.right
text: styleData.value !== undefined ? styleData.value : ""
verticalAlignment: Text.AlignVCenter
}
Image {
id: image
height: buttonHeight
width: height
anchors.left: parent.left
anchors.leftMargin: textmargin
anchors.verticalCenter: parent.verticalCenter
source: isFolder(fileNameText.text) ? "qrc:/res/icons/ic_folder_open_black_48dp.png" :
"qrc:/res/icons/ic_insert_drive_file_black_48dp.png"
}
}
}
}
Component {
id: headerDelegate
Rectangle {
height: rowHeight
color: "#535353"
Text {
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
height: headerTextSize
font.bold: true
elide: Text.ElideMiddle
color: "white"
text: styleData.value !== undefined ? styleData.value : ""
}
}
}
}
Button {
Layout.fillWidth: true
text: "Cancel"
Layout.margins: 10
Layout.topMargin: -5
Layout.bottomMargin: 0
onClicked: {
picker.enabled = false
picker.visible = false
}
}
}
}

601
mobile/FwUpdate.qml Normal file
View File

@@ -0,0 +1,601 @@
/*
Copyright 2017 Benjamin Vedder benjamin@vedder.se
This file is part of VESC Tool.
VESC Tool is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
VESC Tool is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import Ennoid.bmsinterface 1.0
import Ennoid.commands 1.0
import Ennoid.configparams 1.0
import Ennoid.fwhelper 1.0
import Ennoid.utility 1.0
Item {
property Commands mCommands: VescIf.commands()
property ConfigParams mInfoConf: VescIf.infoConfig()
FwHelper {
id: fwHelper
}
ColumnLayout {
anchors.fill: parent
spacing: 0
RowLayout {
Layout.fillHeight: true
Layout.fillWidth: true
spacing: 0
Rectangle {
color: "#4f4f4f"
width: 16
Layout.fillHeight: true
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
PageIndicator {
id: indicator
count: swipeView.count
currentIndex: swipeView.currentIndex
Layout.preferredWidth: 15
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
rotation: 90
}
}
SwipeView {
id: swipeView
enabled: true
clip: true
Layout.fillWidth: true
Layout.fillHeight: true
Layout.rightMargin: 15
orientation: Qt.Vertical
Page {
ColumnLayout {
anchors.fill: parent
anchors.leftMargin: 10
anchors.rightMargin: 10
Rectangle {
Layout.fillWidth: true
height: 30;
border.width: 0
gradient: Gradient {
GradientStop {
position: 0.00;
color: "#002dcbff";
}
GradientStop {
position: 0.3;
color: "#80014cb2";
}
GradientStop {
position: 0.7;
color: "#80014cb2";
}
GradientStop {
position: 1.00;
color: "#000dc3ff";
}
}
border.color: "#00000000"
Text {
anchors.centerIn: parent
color: "white"
text: "Included Files"
font.bold: true
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
}
}
Item {
// Spacer
Layout.fillWidth: true
Layout.fillHeight: true
}
Text {
color: "white"
Layout.fillWidth: true
height: 30;
text: "Hardware"
horizontalAlignment: Text.AlignHCenter
}
ComboBox {
id: hwBox
Layout.preferredHeight: 48
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
textRole: "key"
model: ListModel {
id: hwItems
}
Component.onCompleted: {
updateHw("")
}
onCurrentIndexChanged: {
if (hwItems.rowCount() === 0) {
return
}
var fws = fwHelper.getFirmwares(hwItems.get(hwBox.currentIndex).value)
fwItems.clear()
for (var name in fws) {
if (name.toLowerCase().indexOf("ENNOID-BMS.bin") !== -1) {
fwItems.insert(0, { key: name, value: fws[name] })
} else {
fwItems.append({ key: name, value: fws[name] })
}
}
fwBox.currentIndex = 0
}
}
Text {
color: "white"
Layout.fillWidth: true
height: 30;
text: "Firmware"
horizontalAlignment: Text.AlignHCenter
}
ComboBox {
id: fwBox
Layout.preferredHeight: 48
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
textRole: "key"
model: ListModel {
id: fwItems
}
}
Button {
text: "Show Changelog"
Layout.fillWidth: true
onClicked: {
VescIf.emitMessageDialog(
"Firmware Changelog",
Utility.fwChangeLog(),
true)
}
}
Item {
// Spacer
Layout.fillWidth: true
Layout.fillHeight: true
}
}
}
Page {
ColumnLayout {
anchors.fill: parent
anchors.leftMargin: 10
anchors.rightMargin: 10
Rectangle {
Layout.fillWidth: true
height: 30;
border.width: 0
gradient: Gradient {
GradientStop {
position: 0.00;
color: "#002dcbff";
}
GradientStop {
position: 0.3;
color: "#80014cb2";
}
GradientStop {
position: 0.7;
color: "#80014cb2";
}
GradientStop {
position: 1;
color: "#000dc3ff";
}
}
border.color: "#00000000"
Text {
anchors.centerIn: parent
color: "white"
text: "Custom File"
font.bold: true
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
}
}
Item {
// Spacer
Layout.fillWidth: true
Layout.fillHeight: true
}
TextInput {
color: "white"
id: customFwText
Layout.fillWidth: true
}
Button {
text: "Choose File..."
Layout.fillWidth: true
onClicked: {
if (Utility.requestFilePermission()) {
filePicker.enabled = true
filePicker.visible = true
} else {
VescIf.emitMessageDialog(
"File Permissions",
"Unable to request file system permission.",
false, false)
}
}
}
Item {
// Spacer
Layout.fillWidth: true
Layout.fillHeight: true
}
}
FilePicker {
id: filePicker
anchors.fill: parent
showDotAndDotDot: true
nameFilters: "*.bin"
visible: false
enabled: false
onFileSelected: {
customFwText.text = currentFolder() + "/" + fileName
visible = false
enabled = false
}
}
}
Page {
ColumnLayout {
anchors.fill: parent
anchors.leftMargin: 10
anchors.rightMargin: 10
Rectangle {
Layout.fillWidth: true
height: 30;
border.width: 0
gradient: Gradient {
GradientStop {
position: 0.00;
color: "#002dcbff";
}
GradientStop {
position: 0.3;
color: "#80014cb2";
}
GradientStop {
position: 0.7;
color: "#80014cb2";
}
GradientStop {
position: 1.00;
color: "#000dc3ff"
}
}
border.color: "#00000000"
Text {
anchors.centerIn: parent
color: "white"
text: "Bootloader"
font.bold: true
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
}
}
Item {
// Spacer
Layout.fillWidth: true
Layout.fillHeight: true
}
Text {
color: "white"
Layout.fillWidth: true
height: 30;
text: "Hardware"
horizontalAlignment: Text.AlignHCenter
}
ComboBox {
id: blBox
Layout.preferredHeight: 48
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
textRole: "key"
model: ListModel {
id: blItems
}
Component.onCompleted: {
updateBl("")
}
}
Item {
// Spacer
Layout.fillWidth: true
Layout.fillHeight: true
}
}
}
}
}
Rectangle {
Layout.fillWidth: true
height: asd.implicitHeight + 20
color: "#414141"
ColumnLayout {
id: asd
anchors.fill: parent
anchors.margins: 10
Text {
Layout.fillWidth: true
color: "white"
id: uploadText
text: qsTr("Not Uploading")
horizontalAlignment: Text.AlignHCenter
}
ProgressBar {
id: uploadProgress
Layout.fillWidth: true
}
RowLayout {
Layout.fillWidth: true
Button {
id: uploadButton
text: qsTr("Upload")
Layout.fillWidth: true
onClicked: {
if (!VescIf.isPortConnected()) {
VescIf.emitMessageDialog(
"Connection Error",
"The BMS is not connected. Please open a connection.",
false)
return
}
if (swipeView.currentIndex == 0) {
if (fwItems.rowCount() === 0) {
VescIf.emitMessageDialog(
"Upload Error",
"This version of ENNOID-BMS Tool does not include any firmware " +
"for your hardware version. You can either " +
"upload a custom file or look for a later version of ENNOID-BMS " +
"Tool that might support your hardware.",
false)
return;
}
if (hwItems.rowCount() === 1) {
uploadDialog.title = "Warning"
uploadDialogLabel.text =
"Uploading new firmware will clear all settings on your ENNOID-BMS " +
"and you have to do the configuration again. Do you want to " +
"continue?"
uploadDialog.open()
} else {
uploadDialog.title = "Warning"
uploadDialogLabel.text =
"Uploading firmware for the wrong hardware version " +
"WILL damage the ENNOID-BMS for sure. Are you sure that you have " +
"chosen the correct hardware version?"
uploadDialog.open()
}
} else if (swipeView.currentIndex == 1) {
if (customFwText.text.length > 0) {
uploadDialog.title = "Warning"
uploadDialogLabel.text =
"Uploading firmware for the wrong hardware version " +
"WILL damage the ENNOID-BMS for sure. Are you sure that you have " +
"chosen the correct hardware version?"
uploadDialog.open()
} else {
VescIf.emitMessageDialog(
"Error",
"Please select a file",
false, false)
}
} else if (swipeView.currentIndex == 2) {
if (blItems.rowCount() === 0) {
VescIf.emitMessageDialog(
"Upload Error",
"This version of ENNOID-BMS Tool does not include any bootloader " +
"for your hardware version.",
false)
return;
}
uploadDialog.title = "Warning"
uploadDialogLabel.text =
"This will attempt to upload a bootloader to the connected VESC. " +
"If the connected ENNOID-BMS already has a bootloader this will destroy " +
"the bootloader and firmware updates cannot be done anymore. Do " +
"you want to continue?"
uploadDialog.open()
}
}
}
Button {
id: cancelButton
text: qsTr("Cancel")
Layout.fillWidth: true
enabled: false
onClicked: {
mCommands.cancelFirmwareUpload()
}
}
}
Text {
Layout.fillWidth: true
id: versionText
color: "#e0e0e0"
text:
"FW : \n" +
"HW : \n" +
"UUID : "
font.family: "DejaVu Sans Mono"
verticalAlignment: Text.AlignVCenter
}
}
}
}
Dialog {
id: uploadDialog
standardButtons: Dialog.Ok | Dialog.Cancel
modal: true
focus: true
width: parent.width - 20
closePolicy: Popup.CloseOnEscape
x: (parent.width - width) / 2
y: (parent.height - height) / 2
Text {
color: "#ffffff"
id: uploadDialogLabel
verticalAlignment: Text.AlignVCenter
anchors.fill: parent
wrapMode: Text.WordWrap
}
onAccepted: {
if (swipeView.currentIndex == 0) {
fwHelper.uploadFirmware(fwItems.get(fwBox.currentIndex).value, VescIf, false, false)
} else if (swipeView.currentIndex == 1) {
fwHelper.uploadFirmware(customFwText.text, VescIf, false, true)
} else if (swipeView.currentIndex == 2) {
fwHelper.uploadFirmware(blItems.get(blBox.currentIndex).value, VescIf, true, false)
}
}
}
function updateHw(hw) {
var hws = fwHelper.getHardwares(hw)
hwItems.clear()
for (var name in hws) {
if (name.indexOf("412") !== -1) {
hwItems.insert(0, { key: name, value: hws[name] })
} else {
hwItems.append({ key: name, value: hws[name] })
}
}
hwBox.currentIndex = 0
}
function updateBl(hw) {
var bls = fwHelper.getBootloaders(hw)
blItems.clear()
for (var name in bls) {
if (name.indexOf("412") !== -1) {
blItems.insert(0, { key: name, value: bls[name] })
} else {
blItems.append({ key: name, value: bls[name] })
}
}
blBox.currentIndex = 0
}
Connections {
target: VescIf
onFwUploadStatus: {
if (isOngoing) {
uploadText.text = status + " (" + parseFloat(progress * 100.0).toFixed(2) + " %)"
} else {
uploadText.text = status
}
uploadProgress.value = progress
uploadButton.enabled = !isOngoing
cancelButton.enabled = isOngoing
}
}
Connections {
target: mCommands
onFwVersionReceived: {
updateHw(hw)
updateBl(hw)
versionText.text =
"FW : " + major + "." + minor + "\n" +
"HW : " + hw + "\n" +
"UUID : " + Utility.uuid2Str(uuid, false)
}
}
}

173
mobile/NrfPair.qml Normal file
View File

@@ -0,0 +1,173 @@
/*
Copyright 2018 Benjamin Vedder benjamin@vedder.se
This file is part of VESC Tool.
VESC Tool is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
VESC Tool is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import Ennoid.bmsinterface 1.0
import Ennoid.commands 1.0
import Ennoid.configparams 1.0
Item {
property int parentWidth: 10
property real pairCnt: 0.0
property Commands mCommands: VescIf.commands()
property ConfigParams mInfoConf: VescIf.infoConfig()
function openDialog() {
dialog.open()
}
Dialog {
id: dialog
standardButtons: Dialog.Close
modal: true
focus: true
width: parentWidth - 20
height: Math.min(implicitHeight, column.height - 40)
closePolicy: Popup.CloseOnEscape
x: 10
y: 10
ScrollView {
anchors.fill: parent
clip: true
contentWidth: parent.width
ColumnLayout {
anchors.fill: parent
spacing: 0
DoubleSpinBox {
id: timeBox
Layout.fillWidth: true
realFrom: 1.0
realTo: 30.0
realValue: 10.0
decimals: 1
prefix: "Time: "
suffix: " s"
}
ProgressBar {
id: cntBar
Layout.fillWidth: true
Layout.bottomMargin: 5
from: 0.0
to: 1.0
value: 0.0
}
RowLayout {
Layout.fillWidth: true
Button {
text: "Help"
Layout.preferredWidth: 50
Layout.fillWidth: true
onClicked: {
VescIf.emitMessageDialog(
mInfoConf.getLongName("help_nrf_pair"),
mInfoConf.getDescription("help_nrf_pair"),
true, true)
}
}
Button {
id: startButton
text: "Start"
Layout.preferredWidth: 50
Layout.fillWidth: true
onClicked: {
mCommands.pairNrf(timeBox.realValue * 1000.0)
}
}
}
}
}
}
Timer {
id: cntTimer
interval: 100
running: true
repeat: true
onTriggered: {
if (pairCnt > 0.01) {
pairCnt -= 0.1
if (pairCnt <= 0.01) {
startButton.enabled = true
pairCnt = 0.0
}
cntBar.value = pairCnt / timeBox.realValue
}
}
}
Connections {
target: mCommands
onNrfPairingRes: {
if (!dialog.visible) {
return
}
switch (res) {
case 0:
pairCnt = timeBox.realValue
cntBar.value = 1
startButton.enabled = false
break;
case 1:
startButton.enabled = true
pairCnt = 0.0
cntBar.value = 0
VescIf.emitStatusMessage("Pairing NRF Sucessful", true)
VescIf.emitMessageDialog(
"NRF Pairing",
"Pairing was successful.",
true, false)
break;
case 2:
startButton.enabled = true
pairCnt = 0.0
cntBar.value = 0
VescIf.emitStatusMessage("Pairing NRF Timed Out", false)
VescIf.emitMessageDialog(
"NRF Pairing",
"Pairing timed out. Make sure to put your device (e.g. NRF nunchuk) " +
"in pairing mode before the time runs out." +
"<br><br>" +
"To put the NRF nunchuk in " +
"pairing mode, just switch it on using any of the buttons. Then it " +
"will enter pairing mode if it was switched off previously.",
false, false)
break;
default:
break;
}
}
}
}

358
mobile/PairingDialog.qml Normal file
View File

@@ -0,0 +1,358 @@
/*
Copyright 2019 Benjamin Vedder benjamin@vedder.se
This file is part of VESC Tool.
VESC Tool is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
VESC Tool is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import Ennoid.bmsinterface 1.0
import Ennoid.commands 1.0
import Ennoid.configparams 1.0
import Ennoid.utility 1.0
Item {
function openDialog() {
dialog.open()
loadUuids()
}
function loadUuids() {
pairModel.clear()
var uuids = VescIf.getPairedUuids()
for (var i = 0;i < uuids.length;i++) {
pairModel.append({"uuid": uuids[i]})
}
}
Dialog {
//property ConfigParams mAppConf: VescIf.appConfig()
property Commands mCommands: VescIf.commands()
id: dialog
modal: true
focus: true
width: parent.width - 20
height: parent.height - 60
closePolicy: Popup.CloseOnEscape
x: 10
y: 50
parent: ApplicationWindow.overlay
padding: 10
ColumnLayout {
anchors.fill: parent
Text {
id: text
Layout.fillWidth: true
color: "white"
text: qsTr("These are the BMS paired to this instance of BMS Tool.")
font.bold: true
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
}
ListModel {
id: pairModel
}
ListView {
id: pairList
Layout.fillWidth: true
Layout.fillHeight: true
focus: true
clip: true
spacing: 5
Component {
id: pairDelegate
Rectangle {
property variant modelData: model
width: pairList.width
height: 60
color: "#30000000"
radius: 5
RowLayout {
anchors.fill: parent
spacing: 10
Image {
id: image
fillMode: Image.PreserveAspectFit
Layout.preferredWidth: 40
Layout.preferredHeight: 40
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: 10
source: "qrc:/res/icon.png"
}
Text {
Layout.fillWidth: true
color: "white"
text: uuid
wrapMode: Text.Wrap
}
Button {
Layout.alignment: Qt.AlignVCenter
Layout.rightMargin: 10
text: "Delete"
onClicked: {
deleteDialog.open()
}
Dialog {
id: deleteDialog
property int indexNow: 0
standardButtons: Dialog.Ok | Dialog.Cancel
modal: true
focus: true
width: parent.width - 20
closePolicy: Popup.CloseOnEscape
title: "Delete paired BMS"
x: 10
y: 10 + parent.height / 2 - height / 2
parent: ApplicationWindow.overlay
Text {
color: "#ffffff"
verticalAlignment: Text.AlignVCenter
anchors.fill: parent
wrapMode: Text.WordWrap
text: "This is going to delete this BMS from the paired list. If that BMS " +
"has the pairing flag set you won't be able to connect to it over BLE " +
"any more. Are you sure?"
}
onAccepted: {
VescIf.deletePairedUuid(uuid)
VescIf.storeSettings()
}
}
}
}
}
}
model: pairModel
delegate: pairDelegate
}
RowLayout {
Layout.fillWidth: true
Button {
Layout.preferredWidth: 50
Layout.fillWidth: true
text: "..."
onClicked: menu.open()
Menu {
id: menu
width: 500
MenuItem {
text: "Add current without pairing"
onTriggered: {
if (VescIf.isPortConnected()) {
VescIf.addPairedUuid(VescIf.getConnectedUuid());
VescIf.storeSettings()
} else {
VescIf.emitMessageDialog("Add UUID",
"You are not connected to the BMS. Connect in order to add it.",
false, false)
}
}
}
MenuItem {
text: "Add from UUID"
onTriggered: {
uuidEnter.open()
}
}
MenuItem {
text: "Unpair connected"
onTriggered: {
if (VescIf.isPortConnected()) {
if (mCommands.isLimitedMode()) {
VescIf.emitMessageDialog("Unpair BMS",
"The fiwmare must be updated to unpair this BMS.",
false, false)
} else {
unpairConnectedDialog.open()
}
} else {
VescIf.emitMessageDialog("Unpair BMS",
"You are not connected to the BMS. Connect in order to unpair it.",
false, false)
}
}
}
}
}
Button {
id: pairConnectedButton
text: "Pair BMS"
Layout.fillWidth: true
onClicked: {
if (VescIf.isPortConnected()) {
if (mCommands.isLimitedMode()) {
VescIf.emitMessageDialog("Pair BMS",
"The fiwmare must be updated to pair this BMS.",
false, false)
} else {
pairConnectedDialog.open()
}
} else {
VescIf.emitMessageDialog("Pair BMS",
"You are not connected to the BMS. Connect in order to pair it.",
false, false)
}
}
}
Button {
text: "Close"
Layout.fillWidth: true
onClicked: {
dialog.close()
}
}
}
}
}
Dialog {
id: pairConnectedDialog
property int indexNow: 0
standardButtons: Dialog.Ok | Dialog.Cancel
modal: true
focus: true
width: parent.width - 20
closePolicy: Popup.CloseOnEscape
title: "Pair connected BMS"
x: 10
y: 10 + Math.max((parent.height - height) / 2, 10)
parent: ApplicationWindow.overlay
Text {
color: "#ffffff"
verticalAlignment: Text.AlignVCenter
anchors.fill: parent
wrapMode: Text.WordWrap
text: "This is going to pair the connected BMS with this instance of BMS Tool. BMS Tool instances " +
"that are not paired with this BMS will not be able to connect over bluetooth any more. Continue?"
}
onAccepted: {
VescIf.addPairedUuid(VescIf.getConnectedUuid());
VescIf.storeSettings()
mAppConf.updateParamBool("pairing_done", true, 0)
mCommands.setAppConf()
if (Utility.waitSignal(mCommands, "2ackReceived(QString)", 2000)) {
VescIf.emitMessageDialog("Pairing Successful!",
"Pairing is done! Please note the UUID if this BMS (or take a screenshot) in order " +
"to add it to BMS Tool instances that are not paired in the future. The UUID is:\n" +
VescIf.getConnectedUuid(),
true, false)
}
}
}
Dialog {
id: unpairConnectedDialog
property int indexNow: 0
standardButtons: Dialog.Ok | Dialog.Cancel
modal: true
focus: true
width: parent.width - 20
closePolicy: Popup.CloseOnEscape
title: "Unpair connected BMS"
x: 10
y: 10 + parent.height / 2 - height / 2
parent: ApplicationWindow.overlay
Text {
color: "#ffffff"
verticalAlignment: Text.AlignVCenter
anchors.fill: parent
wrapMode: Text.WordWrap
text: "This is going to unpair the connected BMS. Continue?"
}
onAccepted: {
VescIf.deletePairedUuid(VescIf.getConnectedUuid());
VescIf.storeSettings()
mAppConf.updateParamBool("pairing_done", false, 0)
mCommands.setAppConf()
}
}
Dialog {
id: uuidEnter
standardButtons: Dialog.Ok | Dialog.Cancel
modal: true
focus: true
title: "Add UUID"
width: parent.width - 20
height: 200
closePolicy: Popup.CloseOnEscape
x: 10
y: parent.height / 2 - height / 2
parent: ApplicationWindow.overlay
Rectangle {
anchors.fill: parent
height: 20
border.width: 2
border.color: "#8d8d8d"
color: "#33a8a8a8"
radius: 3
TextInput {
id: stringInput
color: "#ffffff"
anchors.fill: parent
anchors.margins: 7
font.pointSize: 12
focus: true
}
}
onAccepted: {
if (stringInput.text.length > 0) {
VescIf.addPairedUuid(stringInput.text)
}
}
}
Connections {
target: VescIf
onPairingListUpdated: {
loadUuids()
}
}
}

139
mobile/ParamEditBool.qml Normal file
View File

@@ -0,0 +1,139 @@
/*
Copyright 2018 Benjamin Vedder benjamin@vedder.se
This file is part of VESC Tool.
VESC Tool is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
VESC Tool is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.0
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import Ennoid.bmsinterface 1.0
import Ennoid.configparams 1.0
Item {
id: editor
property string paramName: ""
property ConfigParams params: null
height: 140
Layout.fillWidth: true
property real maxVal: 1.0
Component.onCompleted: {
if (params !== null) {
nameText.text = params.getLongName(paramName)
boolSwitch.checked = params.getParamBool(paramName)
if (params.getParamTransmittable(paramName)) {
nowButton.visible = true
defaultButton.visible = true
} else {
nowButton.visible = false
defaultButton.visible = false
}
}
}
Rectangle {
id: rect
anchors.fill: parent
color: "#4c5a5a5a"
radius: 5
border.color: "#919191"
border.width: 2
ColumnLayout {
id: column
anchors.fill: parent
anchors.topMargin: 10
anchors.margins: 5
Text {
id: nameText
color: "white"
text: paramName
horizontalAlignment: Text.AlignHCenter
Layout.fillWidth: true
font.pointSize: 12
}
Switch {
id: boolSwitch
Layout.fillWidth: true
onCheckedChanged: {
if (params !== null) {
if (params.getUpdateOnly() !== paramName) {
params.setUpdateOnly("")
}
params.updateParamBool(paramName, checked, editor);
}
}
}
RowLayout {
Layout.fillWidth: true
Button {
id: nowButton
Layout.fillWidth: true
Layout.preferredWidth: 500
flat: true
text: "Current"
onClicked: {
params.setUpdateOnly(paramName)
params.requestUpdate()
}
}
Button {
id: defaultButton
Layout.fillWidth: true
Layout.preferredWidth: 500
flat: true
text: "Default"
onClicked: {
params.setUpdateOnly(paramName)
params.requestUpdateDefault()
}
}
Button {
id: helpButton
Layout.fillWidth: true
Layout.preferredWidth: 500
flat: true
text: "Help"
onClicked: {
VescIf.emitMessageDialog(
params.getLongName(paramName),
params.getDescription(paramName),
true, true)
}
}
}
}
}
Connections {
target: params
onParamChangedBool: {
if (src !== editor && name == paramName) {
boolSwitch.checked = newParam
}
}
}
}

200
mobile/ParamEditDouble.qml Normal file
View File

@@ -0,0 +1,200 @@
/*
Copyright 2018 Benjamin Vedder benjamin@vedder.se
This file is part of VESC Tool.
VESC Tool is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
VESC Tool is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.0
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import Ennoid.bmsinterface 1.0
import Ennoid.configparams 1.0
Item {
id: editor
property string paramName: ""
property ConfigParams params: null
height: 140
Layout.fillWidth: true
property real maxVal: 1.0
property bool createReady: false
Component.onCompleted: {
if (params !== null) {
if (Math.abs(params.getParamMaxDouble(paramName)) > params.getParamMinDouble(paramName)) {
maxVal = Math.abs(params.getParamMaxDouble(paramName))
} else {
maxVal = Math.abs(params.getParamMinDouble(paramName))
}
nameText.text = params.getLongName(paramName)
valueBox.decimals = params.getParamDecimalsDouble(paramName)
valueBox.realFrom = params.getParamMinDouble(paramName) * params.getParamEditorScale(paramName)
valueBox.realTo = params.getParamMaxDouble(paramName) * params.getParamEditorScale(paramName)
valueBox.realValue = params.getParamDouble(paramName) * params.getParamEditorScale(paramName)
valueBox.realStepSize = params.getParamStepDouble(paramName)
valueBox.visible = !params.getParamEditAsPercentage(paramName)
valueBox.suffix = params.getParamSuffix(paramName)
var p = (params.getParamDouble(paramName) * 100.0) / maxVal
percentageBox.from = (100.0 * params.getParamMinDouble(paramName)) / maxVal
percentageBox.to = (100.0 * params.getParamMaxDouble(paramName)) / maxVal
percentageBox.value = p
percentageBox.visible = params.getParamEditAsPercentage(paramName)
if (params.getParamTransmittable(paramName)) {
nowButton.visible = true
defaultButton.visible = true
} else {
nowButton.visible = false
defaultButton.visible = false
}
createReady = true
}
}
function updateDisplay(value) {
// TODO: No display for now...
}
Rectangle {
id: rect
anchors.fill: parent
color: "#4c5a5a5a"
radius: 5
border.color: "#919191"
border.width: 2
ColumnLayout {
id: column
anchors.fill: parent
anchors.topMargin: 10
anchors.margins: 5
Text {
id: nameText
color: "white"
text: paramName
horizontalAlignment: Text.AlignHCenter
Layout.fillWidth: true
font.pointSize: 12
}
DoubleSpinBox {
id: valueBox
Layout.fillWidth: true
onRealValueChanged: {
if (!params.getParamEditAsPercentage(paramName)) {
var val = realValue / params.getParamEditorScale(paramName)
if (params !== null && createReady) {
if (params.getUpdateOnly() !== paramName) {
params.setUpdateOnly("")
}
params.updateParamDouble(paramName, val, editor);
}
updateDisplay(val);
}
}
}
SpinBox {
id: percentageBox
Layout.fillWidth: true
editable: true
onValueChanged: {
if (params.getParamEditAsPercentage(paramName)) {
var val = (value / 100.0) * maxVal
if (params !== null && createReady) {
if (params.getUpdateOnly() !== paramName) {
params.setUpdateOnly("")
}
params.updateParamDouble(paramName, val, editor);
}
updateDisplay(val);
}
}
textFromValue: function(value, locale) {
return Number(value).toLocaleString(locale, 'f', 0) + " %"
}
valueFromText: function(text, locale) {
return Number.fromLocaleString(locale, text.replace("%", ""))
}
}
RowLayout {
Layout.fillWidth: true
Button {
id: nowButton
Layout.fillWidth: true
Layout.preferredWidth: 500
flat: true
text: "Current"
onClicked: {
params.setUpdateOnly(paramName)
params.requestUpdate()
}
}
Button {
id: defaultButton
Layout.fillWidth: true
Layout.preferredWidth: 500
flat: true
text: "Default"
onClicked: {
params.setUpdateOnly(paramName)
params.requestUpdateDefault()
}
}
Button {
id: helpButton
Layout.fillWidth: true
Layout.preferredWidth: 500
flat: true
text: "Help"
onClicked: {
VescIf.emitMessageDialog(
params.getLongName(paramName),
params.getDescription(paramName),
true, true)
}
}
}
}
}
Connections {
target: params
onParamChangedDouble: {
if (src !== editor && name == paramName) {
valueBox.realValue = newParam * params.getParamEditorScale(paramName)
percentageBox.value = Math.round((100.0 * newParam) / maxVal)
}
}
}
}

151
mobile/ParamEditEnum.qml Normal file
View File

@@ -0,0 +1,151 @@
/*
Copyright 2018 Benjamin Vedder benjamin@vedder.se
This file is part of VESC Tool.
VESC Tool is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
VESC Tool is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.0
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import Ennoid.bmsinterface 1.0
import Ennoid.configparams 1.0
Item {
id: editor
property string paramName: ""
property ConfigParams params: null
height: 140
Layout.fillWidth: true
property real maxVal: 1.0
property bool createReady: false
Component.onCompleted: {
if (params !== null) {
nameText.text = params.getLongName(paramName)
enumBox.model = params.getParamEnumNames(paramName)
enumBox.currentIndex = params.getParamEnum(paramName)
if (params.getParamTransmittable(paramName)) {
nowButton.visible = true
defaultButton.visible = true
} else {
nowButton.visible = false
defaultButton.visible = false
}
createReady = true
}
}
Rectangle {
id: rect
anchors.fill: parent
color: "#4c5a5a5a"
radius: 5
border.color: "#919191"
border.width: 2
ColumnLayout {
id: column
anchors.fill: parent
anchors.bottomMargin: 2
anchors.margins: 10
Text {
id: nameText
color: "white"
text: paramName
horizontalAlignment: Text.AlignHCenter
Layout.fillWidth: true
font.pointSize: 12
}
ComboBox {
id: enumBox
Layout.fillWidth: true
background: Rectangle {
implicitHeight: 35
color: enumBox.pressed ? "#606060" : "#505050"
border.color: enumBox.hovered ? "#81D4FA" : "#000dc3ff"
border.width: enumBox.visualFocus ? 2 : 2
radius: 5
}
onCurrentIndexChanged: {
if (params !== null && createReady) {
if (params.getUpdateOnly() !== paramName) {
params.setUpdateOnly("")
}
params.updateParamEnum(paramName, currentIndex, editor);
}
}
}
RowLayout {
Layout.fillWidth: true
Button {
id: nowButton
Layout.fillWidth: true
Layout.preferredWidth: 500
flat: true
text: "Current"
onClicked: {
params.setUpdateOnly(paramName)
params.requestUpdate()
}
}
Button {
id: defaultButton
Layout.fillWidth: true
Layout.preferredWidth: 500
flat: true
text: "Default"
onClicked: {
params.setUpdateOnly(paramName)
params.requestUpdateDefault()
}
}
Button {
id: helpButton
Layout.fillWidth: true
Layout.preferredWidth: 500
flat: true
text: "Help"
onClicked: {
VescIf.emitMessageDialog(
params.getLongName(paramName),
params.getDescription(paramName),
true, true)
}
}
}
}
}
Connections {
target: params
onParamChangedEnum: {
if (src !== editor && name == paramName) {
enumBox.currentIndex = newParam
}
}
}
}

210
mobile/ParamEditInt.qml Normal file
View File

@@ -0,0 +1,210 @@
/*
Copyright 2018 Benjamin Vedder benjamin@vedder.se
This file is part of VESC Tool.
VESC Tool is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
VESC Tool is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.0
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import Ennoid.bmsinterface 1.0
import Ennoid.configparams 1.0
Item {
id: editor
property string paramName: ""
property ConfigParams params: null
height: 140
Layout.fillWidth: true
property real maxVal: 1.0
property bool createReady: false
Component.onCompleted: {
if (params !== null) {
if (Math.abs(params.getParamMaxInt(paramName)) > params.getParamMinInt(paramName)) {
maxVal = Math.abs(params.getParamMaxInt(paramName))
} else {
maxVal = Math.abs(params.getParamMinInt(paramName))
}
nameText.text = params.getLongName(paramName)
valueBox.from = params.getParamMinInt(paramName) * params.getParamEditorScale(paramName)
valueBox.to = params.getParamMaxInt(paramName) * params.getParamEditorScale(paramName)
valueBox.value = params.getParamInt(paramName) * params.getParamEditorScale(paramName)
valueBox.stepSize = params.getParamStepInt(paramName)
valueBox.visible = !params.getParamEditAsPercentage(paramName)
valueBox.suffix = params.getParamSuffix(paramName)
var p = (params.getParamInt(paramName) * 100.0) / maxVal
percentageBox.from = (100.0 * params.getParamMinInt(paramName)) / maxVal
percentageBox.to = (100.0 * params.getParamMaxInt(paramName)) / maxVal
percentageBox.value = p
percentageBox.visible = params.getParamEditAsPercentage(paramName)
if (params.getParamTransmittable(paramName)) {
nowButton.visible = true
defaultButton.visible = true
} else {
nowButton.visible = false
defaultButton.visible = false
}
createReady = true
}
}
function updateDisplay(value) {
// TODO: No display for now...
}
Rectangle {
id: rect
anchors.fill: parent
color: "#4c5a5a5a"
radius: 5
border.color: "#919191"
border.width: 2
ColumnLayout {
id: column
anchors.fill: parent
anchors.topMargin: 10
anchors.margins: 5
Text {
id: nameText
color: "white"
text: paramName
horizontalAlignment: Text.AlignHCenter
Layout.fillWidth: true
font.pointSize: 12
}
SpinBox {
id: valueBox
Layout.fillWidth: true
property string suffix: ""
editable: true
onValueChanged: {
if (!params.getParamEditAsPercentage(paramName)) {
var val = value / params.getParamEditorScale(paramName)
if (params !== null && createReady) {
if (params.getUpdateOnly() !== paramName) {
params.setUpdateOnly("")
}
params.updateParamInt(paramName, val, editor);
}
updateDisplay(val);
}
}
textFromValue: function(value, locale) {
return Number(value).toLocaleString(locale, 'f', 0) + suffix
}
valueFromText: function(text, locale) {
return Number.fromLocaleString(locale, text.replace(suffix, ""))
}
}
SpinBox {
id: percentageBox
Layout.fillWidth: true
editable: true
visible: false
onValueChanged: {
if (params.getParamEditAsPercentage(paramName)) {
var val = (value / 100.0) * maxVal
if (params !== null && createReady) {
if (params.getUpdateOnly() !== paramName) {
params.setUpdateOnly("")
}
params.updateParamInt(paramName, val, editor);
}
updateDisplay(val);
}
}
textFromValue: function(value, locale) {
return Number(value).toLocaleString(locale, 'f', 0) + " %"
}
valueFromText: function(text, locale) {
return Number.fromLocaleString(locale, text.replace("%", ""))
}
}
RowLayout {
Layout.fillWidth: true
Button {
id: nowButton
Layout.fillWidth: true
Layout.preferredWidth: 500
flat: true
text: "Current"
onClicked: {
params.setUpdateOnly(paramName)
params.requestUpdate()
}
}
Button {
id: defaultButton
Layout.fillWidth: true
Layout.preferredWidth: 500
flat: true
text: "Default"
onClicked: {
params.setUpdateOnly(paramName)
params.requestUpdateDefault()
}
}
Button {
id: helpButton
Layout.fillWidth: true
Layout.preferredWidth: 500
flat: true
text: "Help"
onClicked: {
VescIf.emitMessageDialog(
params.getLongName(paramName),
params.getDescription(paramName),
true, true)
}
}
}
}
}
Connections {
target: params
onParamChangedInt: {
if (src !== editor && name == paramName) {
valueBox.value = newParam * params.getParamEditorScale(paramName)
percentageBox.value = Math.round((100.0 * newParam) / maxVal)
}
}
}
}

View File

@@ -0,0 +1,48 @@
/*
Copyright 2018 Benjamin Vedder benjamin@vedder.se
This file is part of VESC Tool.
VESC Tool is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
VESC Tool is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.0
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
Item {
Layout.fillWidth: true
height: name.implicitHeight + 8
property string sepName: ""
Rectangle {
id: rect
anchors.fill: parent
color: "#9e0000"
radius: 5
Text {
anchors.centerIn: parent
color: "white"
id: name
text: sepName
font.bold: true
font.pointSize: 12
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
}
}

148
mobile/ParamEditString.qml Normal file
View File

@@ -0,0 +1,148 @@
/*
Copyright 2018 Benjamin Vedder benjamin@vedder.se
This file is part of VESC Tool.
VESC Tool is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
VESC Tool is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.0
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import Ennoid.bmsinterface 1.0
import Ennoid.configparams 1.0
Item {
id: editor
property string paramName: ""
property ConfigParams params: null
height: column.implicitHeight + 2 * column.anchors.margins
Layout.fillWidth: true
property real maxVal: 1.0
Component.onCompleted: {
if (params !== null) {
nameText.text = params.getLongName(paramName)
stringInput.text = params.getParamQString(paramName)
if (params.getParamTransmittable(paramName)) {
nowButton.visible = true
defaultButton.visible = true
} else {
nowButton.visible = false
defaultButton.visible = false
}
}
}
Rectangle {
id: rect
anchors.fill: parent
color: "#4cbfbfbf"
radius: 10
border.color: "#4c000000"
border.width: 3
ColumnLayout {
id: column
anchors.fill: parent
anchors.margins: 10
Text {
id: nameText
text: paramName
horizontalAlignment: Text.AlignHCenter
Layout.fillWidth: true
font.pointSize: 12
}
Rectangle {
Layout.fillWidth: true
height: stringInput.implicitHeight + 14
border.width: 2
border.color: "#8d8d8d"
color: "#33a8a8a8"
radius: 3
TextInput {
id: stringInput
anchors.fill: parent
anchors.margins: 7
font.pointSize: 12
focus: true
onTextChanged: {
if (params !== null) {
if (params.getUpdateOnly() !== paramName) {
params.setUpdateOnly("")
}
params.updateParamString(paramName, text, editor);
}
}
}
}
RowLayout {
Layout.fillWidth: true
Button {
id: nowButton
Layout.fillWidth: true
Layout.preferredWidth: 500
flat: true
text: "Current"
onClicked: {
params.setUpdateOnly(paramName)
params.requestUpdate()
}
}
Button {
id: defaultButton
Layout.fillWidth: true
Layout.preferredWidth: 500
flat: true
text: "Default"
onClicked: {
params.setUpdateOnly(paramName)
params.requestUpdateDefault()
}
}
Button {
id: helpButton
Layout.fillWidth: true
Layout.preferredWidth: 500
flat: true
text: "Help"
onClicked: {
VescIf.emitMessageDialog(
params.getLongName(paramName),
params.getDescription(paramName),
true, true)
}
}
}
}
}
Connections {
target: params
onParamChangedQString: {
if (src !== editor && name == paramName) {
stringInput.text = newParam
}
}
}
}

72
mobile/ParamEditors.qml Normal file
View File

@@ -0,0 +1,72 @@
/*
Copyright 2018 Benjamin Vedder benjamin@vedder.se
This file is part of VESC Tool.
VESC Tool is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
VESC Tool is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import Ennoid.bmsinterface 1.0
import Ennoid.commands 1.0
import Ennoid.configparams 1.0
Item {
property ConfigParams mbmsConfig: VescIf.bmsConfig()
function createEditor(parent, name, conf) {
if (conf.hasParam(name)) {
if (conf.isParamDouble(name)) {
var component = Qt.createComponent("ParamEditDouble.qml");
return component.createObject(parent, {"params": conf, "paramName": name});
} else if (conf.isParamInt(name)) {
var component2 = Qt.createComponent("ParamEditInt.qml");
return component2.createObject(parent, {"params": conf, "paramName": name});
} else if (conf.isParamEnum(name)) {
var component3 = Qt.createComponent("ParamEditEnum.qml");
return component3.createObject(parent, {"params": conf, "paramName": name});
} else if (conf.isParamBool(name)) {
var component4 = Qt.createComponent("ParamEditBool.qml");
return component4.createObject(parent, {"params": conf, "paramName": name});
} else if (conf.isParamQString(name)) {
var component5 = Qt.createComponent("ParamEditString.qml");
return component5.createObject(parent, {"params": conf, "paramName": name});
}
} else {
console.log("Parameter " + name + " not found.")
}
return null
}
function createEditorMc(parent, name) {
return createEditor(parent, name, mbmsConfig)
}
function createSeparator(parent, text) {
var component = Qt.createComponent("ParamEditSeparator.qml");
return component.createObject(parent, {"sepName": text});
}
function createSpacer(parent) {
return Qt.createQmlObject(
'import QtQuick 2.7; import QtQuick.Layouts 1.3; Rectangle {Layout.fillHeight: true}',
parent,
"spacer1")
}
}

193
mobile/PpmMap.qml Normal file
View File

@@ -0,0 +1,193 @@
/*
Copyright 2018 Benjamin Vedder benjamin@vedder.se
This file is part of VESC Tool.
VESC Tool is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
VESC Tool is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import Ennoid.bmsinterface 1.0
import Ennoid.commands 1.0
import Ennoid.configparams 1.0
Item {
implicitHeight: column.implicitHeight
property real msMin: 0.0
property real msMax: 0.0
property real msCenter: 0.0
property real msNow: 0.0
property real valueNow: 0.5
property bool resetDone: true
property Commands mCommands: VescIf.commands()
property ConfigParams mInfoConf: VescIf.infoConfig()
function openDialog() {
dialog.open()
}
function updateDisplay() {
resultArea.text =
"Value : " + parseFloat(valueNow).toFixed(2) + "\n\n" +
"Now : " + parseFloat(msNow).toFixed(4) + " ms\n" +
"Min : " + parseFloat(msMin).toFixed(4) + " ms\n" +
"Max : " + parseFloat(msMax).toFixed(4) + " ms\n" +
"Center : " + parseFloat(msCenter).toFixed(4) + " ms"
valueBar.value = valueNow
}
function isValid() {
return (msMax - msMin) > 0.4
}
function applyMapping() {
if (isValid()) {
mAppConf.updateParamDouble("app_ppm_conf.pulse_start", msMin)
mAppConf.updateParamDouble("app_ppm_conf.pulse_end", msMax)
mAppConf.updateParamDouble("app_ppm_conf.pulse_center", msCenter)
VescIf.emitStatusMessage("Start, End and Center Pulselengths Applied", true)
mCommands.setAppConf()
} else {
VescIf.emitMessageDialog("Apply Mapping",
"Mapped values are not valid. Move the throttle to min, " +
"then to max and then leave it in the center.",
false,
false)
}
}
function reset() {
msMin = 0.0
msMax = 0.0
msCenter = 0.0
resetDone = true
updateDisplay()
}
Component.onCompleted: {
updateDisplay()
}
ColumnLayout {
id: column
anchors.fill: parent
spacing: 0
TextArea {
id: resultArea
Layout.fillWidth: true
Layout.preferredHeight: 200
readOnly: true
wrapMode: TextEdit.WordWrap
font.family: "DejaVu Sans Mono"
}
ProgressBar {
id: valueBar
Layout.fillWidth: true
from: -1.0
to: 1.0
value: 0.0
}
RowLayout {
Layout.fillWidth: true
Button {
text: "Help"
Layout.preferredWidth: 50
Layout.fillWidth: true
flat: true
onClicked: {
VescIf.emitMessageDialog(
mInfoConf.getLongName("app_ppm_mapping_help"),
mInfoConf.getDescription("app_ppm_mapping_help"),
true, true)
}
}
Button {
text: "Reset"
Layout.preferredWidth: 50
Layout.fillWidth: true
flat: true
onClicked: {
reset()
}
}
}
Button {
text: "Apply & Write"
Layout.fillWidth: true
onClicked: {
applyMapping()
}
}
}
Timer {
id: rtTimer
interval: 50
running: true
repeat: true
onTriggered: {
if (VescIf.isPortConnected() && visible) {
mCommands.getDecodedPpm()
}
}
}
Connections {
target: mCommands
onDecodedPpmReceived: {
valueNow = value
msNow = last_len
if (resetDone) {
resetDone = false
msMin = msNow
msMax = msNow
}
if (msNow < msMin) {
msMin = msNow
}
if (msNow > msMax) {
msMax = msNow
}
var range = msMax - msMin
var pos = msNow - msMin
if (pos > (range / 4.0) && pos < ((3.0 * range) / 4.0)) {
msCenter = msNow
} else {
msCenter = range / 2.0 + msMin
}
updateDisplay()
}
}
}

70
mobile/ProgressBar.qml Normal file
View File

@@ -0,0 +1,70 @@
/*
Copyright 2018 Benjamin Vedder benjamin@vedder.se
This file is part of VESC Tool.
VESC Tool is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
VESC Tool is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.0
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import Ennoid.commands 1.0
Item {
id:progressBar
height: 20
Layout.fillWidth: true
property string prefix: ""
property string unit: ""
property double barValue: 0
property double barFrom: 0
property double barTo: 0
ColumnLayout {
id: column
anchors.fill: parent
anchors.topMargin: 0
anchors.margins: 5
ProgressBar {
id: bar
Layout.leftMargin: 35
implicitHeight: 10
Layout.preferredWidth: 300
scale:1
Layout.fillHeight: false
from: barFrom
value: barValue
to: barTo
Label {
id: label
color: "#ffffff"
text: bar.value + unit
anchors.leftMargin: 10
anchors.left: parent.right
anchors.verticalCenter: parent.verticalCenter
}
Label {
id: label2
color: "#ffffff"
text: prefix
anchors.right: parent.left
anchors.verticalCenter: parent.verticalCenter
}
}
}
}

270
mobile/RtData.qml Normal file
View File

@@ -0,0 +1,270 @@
/*
Copyright 2020 Kevin Dionne kevin.dionne@ennoid.me
This file is part of ENNOID-BMS Tool.
ENNOID-BMS Tool is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ENNOID-BMS Tool is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import Ennoid.bmsinterface 1.0
import Ennoid.commands 1.0
import Ennoid.configparams 1.0
Item {
property Commands mCommands: VescIf.commands()
property ConfigParams mbmsConfig: VescIf.bmsConfig()
property var progressBarsVisible: []
property var cellValues: [6,6,6,6,6,6,6,6,6,6,6,6]
property var tempValues: [100,100,100,100,100]
property var tempValuesExp: [100,100,100,100,100]
property bool isHorizontal: width > height
ProgressBar {
id: progressBar
}
Component.onCompleted: {
mCommands.emitEmptyValues()
}
function addProgressBar(prefix, unit, barValue, barFrom, barTo) {
var component = Qt.createComponent("ProgressBar.qml");
return component.createObject(scrollCol, {"prefix": prefix, "unit": unit, "barValue": barValue, "barFrom": barFrom, "barTo": barTo});
}
function destroyProgressBars() {
for (var i = 0;i < progressBarsVisible.length;i++) {
progressBarsVisible[i].destroy();
}
progressBarsVisible = []
}
function createProgressBar(prefix, unit, barValue, barFrom, barTo) {
progressBarsVisible.push(addProgressBar(prefix, unit, barValue, barFrom, barTo))
}
function updateProgressBars(type, prefix, unit, barFrom, barTo) {
destroyProgressBars()
switch(type) {
case "Cells":
for (var i = 0 ; i < cellValues.length ; i++) {
createProgressBar(prefix+(i+1), unit, cellValues[i], barFrom, barTo)
}
break;
case "Temps":
for (var j = 0 ; j < tempValues.length ; j++) {
createProgressBar(prefix+(j+1), unit, tempValues[j], barFrom, barTo)
}
break;
case "TempsExp":
for (var k = 0 ; k < tempValuesExp.length ; k++) {
createProgressBar(prefix+(k+1), unit, tempValuesExp[k], barFrom, barTo)
}
break;
default:
break;
}
}
ColumnLayout {
id: column
anchors.fill: parent
spacing: 0
ComboBox {
id: tabBox
Layout.fillWidth: true
model: [
"Cells voltages",
"Temperatures",
"Temperatures expansion"
]
onCurrentTextChanged: {
switch(currentText) {
case "Cells voltages":
updateProgressBars("Cells","C","V", 2.5, 4.2)
break;
case "Temperatures":
updateProgressBars("Temps","T","\u00B0C", -50 ,100)
break;
case "Temperatures expansion":
updateProgressBars("TempsExp","T","\u00B0C", -50 ,100)
break;
default:
break;
}
}
}
ScrollView {
id: scroll
Layout.fillWidth: true
Layout.fillHeight: true
contentWidth: column.width
clip: true
GridLayout {
id: scrollCol
anchors.fill: parent
columns: isHorizontal ? 2 : 1
}
}
Rectangle {
id: textRect
color: "#272727"
Layout.fillWidth: true
Layout.preferredHeight: valMetrics.height * 8 + 20
Layout.alignment: Qt.AlignBottom
Rectangle {
anchors.bottom: valText.top
width: parent.width
height: 2
color: "#81D4FA"
}
Text {
id: valText
color: "white"
text: VescIf.getConnectedPortName()
font.family: "DejaVu Sans Mono"
verticalAlignment: Text.AlignVCenter
anchors.left: parent.left
anchors.verticalCenter: parent.verticalTop
anchors.margins: 10
}
Text {
id: valText2
color: "white"
text: VescIf.getConnectedPortName()
font.family: "DejaVu Sans Mono"
verticalAlignment: Text.AlignVCenter
anchors.left: parent.horizontalCenter
anchors.verticalCenter: parent.verticalTop
anchors.margins: 10
}
TextMetrics {
id: valMetrics
font: valText.font
text: valText.text
}
}
}
Connections {
target: mCommands
// TODO: For some reason this does not work
onBmsConfigCheckResult: {
if (paramsNotSet.length > 0) {
var notUpdated = "The following parameters were truncated because " +
"they were beyond the hardware limits:\n"
for (var i = 0;i < paramsNotSet.length;i++) {
notUpdated += mbmsConf.getLongName(paramsNotSet[i]) + "\n"
}
VescIf.emitMessageDialog("Parameters truncated", notUpdated, false, false)
}
}
}
Connections {
target: mCommands
onValuesReceived: {
valText.text =
"V Pack : " + parseFloat(values.packVoltage).toFixed(2) + " V\n" +
"I Pack : " + parseFloat(values.packCurrent).toFixed(2) + " A\n" +
"CVHigh : " + parseFloat(values.cVHigh).toFixed(2) + " V\n" +
"CVAverage : " + parseFloat(values.cVAverage).toFixed(2) + " V\n" +
"CVLow : " + parseFloat(values.cVLow).toFixed(2) + " V\n" +
"CVMismatch : " + parseFloat(values.cVMisMatch).toFixed(2) + " V\n" +
"OpState : " + values.opState + "\n" +
"FaultState : " + values.faultState + "\n" +
"SoC : " + parseFloat(values.soC).toFixed(1) + " %\n"
valText2.text =
"T Batt High: " + parseFloat(values.tempBattHigh).toFixed(1) + " \u00B0C\n" +
"T Batt Avrg: " + parseFloat(values.tempBattAverage).toFixed(1) + " \u00B0C\n" +
"T Batt Low : " + parseFloat(values.tempBattLow).toFixed(1) + " \u00B0C\n" +
"T BMS High : " + parseFloat(values.tempBMSHigh).toFixed(1) + " \u00B0C\n" +
"T BMS Avrg : " + parseFloat(values.tempBMSAverage).toFixed(1) + " \u00B0C\n" +
"T BMS Low : " + parseFloat(values.tempBMSLow).toFixed(1) + " \u00B0C\n" +
"Humidity : " + parseFloat(values.humidity).toFixed(1) + " %\n" +
"V Load : " + parseFloat(values.loadLCVoltage).toFixed(1) + " V\n" +
"V Charger : " + parseFloat(values.chargerVoltage).toFixed(1) + " V\n"
}
}
Connections {
target: mCommands
onCellsReceived: {
cellValues = cellVoltageArray
if(tabBox.currentText==="Cells voltages"){
updateProgressBars("Cells","C","V", 2.5, 4.2)
}
}
}
Connections {
target: mCommands
onAuxReceived: {
tempValues = auxVoltageArray
if(tabBox.currentText==="Temperatures"){
updateProgressBars("Temps","T","\u00B0C", -50 ,100)
}
}
}
Connections {
target: mCommands
onExpTempReceived: {
tempValuesExp = expTempVoltageArray
if(tabBox.currentText==="Temperatures expansion"){
updateProgressBars("TempsExp","T","\u00B0C", -50 ,100)
}
}
}
}

224
mobile/RtData2.qml Normal file
View File

@@ -0,0 +1,224 @@
/*
Copyright 2017 Benjamin Vedder benjamin@vedder.se
This file is part of VESC Tool.
VESC Tool is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
VESC Tool is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import Ennoid.bmsinterface 1.0
import Ennoid.commands 1.0
import Ennoid.configparams 1.0
Item {
id: rtData
property Commands mCommands: VescIf.commands()
property ConfigParams mbmsConfig: VescIf.bmsConfig()
property var progressBarsVisible: []
property bool isHorizontal: rtData.width > rtData.height
property int gaugeSize: Math.min(width / 2 - 10,
(height - valMetrics.height * 10) /
(isHorizontal ? 1 : 2) - (isHorizontal ? 30 : 20))
//RtProgressBar {
// id: progressBars
//}
Component.onCompleted: {
//currentGauge.minimumValue = -mbmsConfig.getParamDouble("maxAllowedCurrent")
//currentGauge.maximumValue = mbmsConfig.getParamDouble("maxAllowedCurrent")
mCommands.emitEmptyValues()
}
ColumnLayout {
id: column
anchors.fill: parent
GridLayout {
Layout.fillWidth: true
columns: 1
/*CustomGauge {
id: currentGauge
Layout.fillWidth: true
maximumValue: 100
minimumValue: -100
labelStep: maximumValue > 60 ? 20 : 10
value: 0
unitText: "A"
typeText: "Current"
Layout.preferredWidth: gaugeSize
Layout.preferredHeight: gaugeSize
}
CustomGauge {
id: dutyGauge
Layout.fillWidth: true
maximumValue: 100
minimumValue: -100
labelStep: 20
value: 0
unitText: "%"
typeText: "Duty"
Layout.preferredWidth: gaugeSize
Layout.preferredHeight: gaugeSize
}
CustomGauge {
id: rpmGauge
Layout.fillWidth: true
maximumValue: 100
minimumValue: -100
labelStep: 20
value: -0
unitText: "x1000"
typeText: "ERPM"
Layout.preferredWidth: gaugeSize
Layout.preferredHeight: gaugeSize
}
CustomGauge {
id: powerGauge
Layout.fillWidth: true
maximumValue: 10000
minimumValue: -10000
tickmarkScale: 0.001
tickmarkSuffix: "k"
labelStep: 1000
value: 0
unitText: "W"
typeText: "Power"
Layout.preferredWidth: gaugeSize
Layout.preferredHeight: gaugeSize
}*/
}
Rectangle {
id: textRect
color: "#272727"
Rectangle {
anchors.bottom: valText.top
width: parent.width
height: 2
color: "#81D4FA"
}
Layout.fillWidth: true
Layout.preferredHeight: valMetrics.height * 6 + 20
Layout.alignment: Qt.AlignBottom
Text {
id: valText
color: "white"
text: VescIf.getConnectedPortName()
font.family: "DejaVu Sans Mono"
verticalAlignment: Text.AlignVCenter
anchors.left: parent.left
anchors.verticalCenter: parent.verticalTop
anchors.margins: 10
}
Text {
id: valText2
color: "white"
text: VescIf.getConnectedPortName()
font.family: "DejaVu Sans Mono"
verticalAlignment: Text.AlignVCenter
anchors.left: parent.horizontalCenter
anchors.verticalCenter: parent.verticalTop
anchors.margins: 10
}
TextMetrics {
id: valMetrics
font: valText.font
text: valText.text
}
}
}
Connections {
target: mbmsConfig
onUpdated: {
currentGauge.maximumValue = Math.ceil(mbmsConfig.getParamDouble("maxAllowedCurrent") / 5) * 5
currentGauge.minimumValue = -currentGauge.maximumValue
}
}
Connections {
target: mCommands
onCellsReceived:{
updateProgressBars()
}
}
Connections {
target: mCommands
onValuesReceived: {
/* currentGauge.value = values.packCurrent
dutyGauge.value = values.duty_now * 100.0
var fl = mbmsConfig.getParamDouble("maxAllowedCurrent")
var rpmMax = (values.v_in * 60.0) / (Math.sqrt(3.0) * 2.0 * Math.PI * fl)
var rpmMaxRound = (Math.ceil(rpmMax / 5000.0) * 5000.0) / 1000
if (Math.abs(rpmGauge.maximumValue - rpmMaxRound) > 6) {
rpmGauge.maximumValue = rpmMaxRound
rpmGauge.minimumValue = -rpmMaxRound
}
rpmGauge.value = values.packVoltage
var powerMax = Math.min(values.packVoltage * Math.min(mbmsConfig.getParamDouble("packVoltage"),
mbmsConfig.getParamDouble("packVoltage")),
mbmsConfig.getParamDouble("packVoltage"))
var powerMaxRound = (Math.ceil(powerMax / 1000.0) * 1000.0)
if (Math.abs(powerGauge.maximumValue - powerMaxRound) > 1.2) {
powerGauge.maximumValue = powerMaxRound
powerGauge.minimumValue = -powerMaxRound
}
powerGauge.value = (values.packCurrent * values.packVoltage)
*/
valText.text =
"Battery : " + parseFloat(values.packVoltage).toFixed(2) + " V\n" +
"I Battery : " + parseFloat(values.packCurrent).toFixed(2) + " A\n" +
"CVHigh : " + parseFloat(values.cVHigh).toFixed(2) + " V\n" +
"CVAverage : " + parseFloat(values.cVAverage).toFixed(2) + " V\n" +
"CVLow : " + parseFloat(values.cVLow).toFixed(2) + " V\n" +
"CVMismatch : " + parseFloat(values.cVMisMatch).toFixed(2) + " V\n"
valText2.text =
"T Batt High: " + parseFloat(values.tempBattHigh).toFixed(1) + " \u00B0C\n" +
"T Batt Avrg: " + parseFloat(values.tempBattAverage).toFixed(1) + " \u00B0C\n" +
"T BMS High : " + parseFloat(values.tempBMSHigh).toFixed(2) + " \u00B0C\n" +
"T BMS Avrg : " + parseFloat(values.tempBMSAverage).toFixed(2) + " \u00B0C\n" +
"SoC : " + parseFloat(values.soC).toFixed(1) + " %\n" +
"OpState : " + values.opState
}
}
}

240
mobile/RtDataSetup.qml Normal file
View File

@@ -0,0 +1,240 @@
/*
Copyright 2018 - 2019 Benjamin Vedder benjamin@vedder.se
This file is part of VESC Tool.
VESC Tool is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
VESC Tool is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.5
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import QtQuick.Controls.Styles 1.4
import QtGraphicalEffects 1.0
import Ennoid.bmsinterface 1.0
import Ennoid.commands 1.0
import Ennoid.configparams 1.0
Item {
id: rtData
property Commands mCommands: VescIf.commands()
property ConfigParams mbmsConfig: VescIf.bmsConfig()
property bool isHorizontal: rtData.width > rtData.height
property int gaugeSize: isHorizontal ? Math.min((height - valMetrics.height * 4) - 30, width / 3.5 - 10) :
Math.min(width / 1.4,
(height - valMetrics.height * 4) / 2.3 - 10)
property int gaugeSize2: gaugeSize * 0.65
property int gaugeVMargin: isHorizontal ? 0 : -gaugeSize * 0.05
property int gaugeHMargin: isHorizontal ? -gaugeSize * 0.1 : gaugeSize * 0.02
Component.onCompleted: {
mCommands.emitEmptySetupValues()
}
GridLayout {
anchors.fill: parent
anchors.topMargin: 5
columns: isHorizontal ? 3 : 1
columnSpacing: 0
RowLayout {
Layout.alignment: Qt.AlignHCenter
Layout.bottomMargin: gaugeVMargin
Layout.preferredHeight: isHorizontal ? gaugeSize : gaugeSize2
spacing: 0
CustomGauge {
id: powerGauge
Layout.preferredWidth: gaugeSize2
Layout.preferredHeight: gaugeSize2
Layout.alignment: (isHorizontal ? Qt.AlignTop : Qt.AlignVCenter)
Layout.rightMargin: gaugeHMargin
maximumValue: 10000
minimumValue: -10000
tickmarkScale: 0.001
tickmarkSuffix: "k"
labelStep: 1000
value: 1000
unitText: "W"
typeText: "Power"
}
CustomGauge {
id: currentGauge
Layout.preferredWidth: gaugeSize2
Layout.preferredHeight: gaugeSize2
Layout.alignment: isHorizontal ? Qt.AlignBottom : Qt.AlignVCenter
Layout.rightMargin: gaugeHMargin
minimumValue: 0
maximumValue: 60
labelStep: maximumValue > 60 ? 20 : 10
unitText: "A"
typeText: "Current"
}
}
CustomGauge {
id: speedGauge
Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: gaugeSize
Layout.preferredHeight: gaugeSize
minimumValue: 0
maximumValue: 60
minAngle: -250
maxAngle: 70
labelStep: maximumValue > 60 ? 20 : 10
value: 20
unitText: "km/h"
typeText: "Speed"
}
RowLayout {
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: gaugeVMargin
Layout.preferredHeight: isHorizontal ? gaugeSize : gaugeSize2
spacing: 0
CustomGauge {
id: batteryGauge
Layout.preferredWidth: gaugeSize2
Layout.preferredHeight: gaugeSize2
Layout.alignment: isHorizontal ? Qt.AlignBottom : Qt.AlignVCenter
Layout.leftMargin: gaugeHMargin
minimumValue: 0
maximumValue: 100
value: 95
unitText: "%"
typeText: "Battery"
traceColor: "green"
}
CustomGauge {
id: dutyGauge
Layout.preferredWidth: gaugeSize2
Layout.preferredHeight: gaugeSize2
Layout.alignment: isHorizontal ? Qt.AlignTop : Qt.AlignVCenter
Layout.leftMargin: gaugeHMargin
maximumValue: 100
minimumValue: -100
labelStep: 20
value: 0
unitText: "%"
typeText: "Duty"
}
}
Rectangle {
id: textRect
color: "#272727"
Layout.fillWidth: true
Layout.preferredHeight: valMetrics.height * 4 + 20
Layout.alignment: Qt.AlignBottom
Layout.columnSpan: isHorizontal ? 3 : 1
Rectangle {
anchors.top: parent.top
width: parent.width
height: 2
color: "#81D4FA"
}
Text {
id: valText
color: "white"
text: VescIf.getConnectedPortName()
font.family: "DejaVu Sans Mono"
verticalAlignment: Text.AlignVCenter
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
anchors.margins: 10
}
Text {
id: valText2
color: "white"
text: VescIf.getConnectedPortName()
font.family: "DejaVu Sans Mono"
verticalAlignment: Text.AlignVCenter
anchors.left: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
anchors.margins: 10
}
TextMetrics {
id: valMetrics
font: valText.font
text: valText.text
}
}
}
Connections {
target: mCommands
onValuesSetupReceived: {
currentGauge.maximumValue = Math.ceil(mbmsConfig.getParamDouble("maxAllowedCurrent") / 5) * 5 * values.num_vescs
currentGauge.minimumValue = -currentGauge.maximumValue
currentGauge.value = values.packCurrent
dutyGauge.value = values.duty_now * 100.0
batteryGauge.value = values.battery_level * 100.0
var fl = mbmsConfig.getParamDouble("foc_motor_flux_linkage")
var rpmMax = (values.v_in * 60.0) / (Math.sqrt(3.0) * 2.0 * Math.PI * fl)
var speedFact = ((mbmsConfig.getParamInt("si_motor_poles") / 2.0) * 60.0 *
mbmsConfig.getParamDouble("si_gear_ratio")) /
(mbmsConfig.getParamDouble("si_wheel_diameter") * Math.PI)
var speedMax = 3.6 * rpmMax / speedFact
var speedMaxRound = (Math.ceil(speedMax / 10.0) * 10.0)
if (Math.abs(speedGauge.maximumValue - speedMaxRound) > 6.0) {
speedGauge.maximumValue = speedMaxRound
speedGauge.minimumValue = -speedMaxRound
}
speedGauge.value = values.speed * 3.6
var powerMax = Math.min(values.v_in * Math.min(mbmsConfig.getParamDouble("l_in_current_max"),
mbmsConfig.getParamDouble("l_current_max")),
mbmsConfig.getParamDouble("l_watt_max")) * values.num_vescs
var powerMaxRound = (Math.ceil(powerMax / 1000.0) * 1000.0)
if (Math.abs(powerGauge.maximumValue - powerMaxRound) > 1.2) {
powerGauge.maximumValue = powerMaxRound
powerGauge.minimumValue = -powerMaxRound
}
powerGauge.value = (values.current_in * values.v_in)
valText.text =
"mAh Out: " + parseFloat(values.amp_hours * 1000.0).toFixed(1) + "\n" +
"mAh In : " + parseFloat(values.amp_hours_charged * 1000.0).toFixed(1) + "\n" +
"Wh Out : " + parseFloat(values.watt_hours).toFixed(2) + "\n" +
"Wh In : " + parseFloat(values.watt_hours_charged).toFixed(2)
var wh_km = (values.watt_hours - values.watt_hours_charged) / (values.tachometer_abs / 1000.0)
valText2.text =
"Km Trip : " + parseFloat(values.tachometer_abs / 1000.0).toFixed(3) + "\n" +
"Wh/Km : " + parseFloat(wh_km).toFixed(1) + "\n" +
"Km Range: " + parseFloat(values.battery_wh / wh_km).toFixed(2) + "\n" +
"VESCs : " + values.num_vescs
}
}
}

60
mobile/RtProgressBar.qml Normal file
View File

@@ -0,0 +1,60 @@
/*
Copyright 2018 Benjamin Vedder benjamin@vedder.se
This file is part of VESC Tool.
VESC Tool is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
VESC Tool is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import Ennoid.bmsinterface 1.0
import Ennoid.commands 1.0
import Ennoid.configparams 1.0
Item {
property ConfigParams mbmsConfig: VescIf.bmsConfig()
function createProgressBar(parent, name, conf) {
if (conf.hasParam(name)) {
var component = Qt.createComponent("RtProgressBarDouble.qml");
return component.createObject(parent, {"params": conf, "paramName": name});
} else {
console.log("Parameter " + name + " not found.")
}
return null
}
function createProgressBar(parent, name) {
return createProgressBar(parent, name, mbmsConfig)
}
function createSeparator(parent, text) {
var component = Qt.createComponent("ParamEditSeparator.qml");
return component.createObject(parent, {"sepName": text});
}
function createSpacer(parent) {
return Qt.createQmlObject(
'import QtQuick 2.7; import QtQuick.Layouts 1.3; Rectangle {Layout.fillHeight: true}',
parent,
"spacer1")
}
}

124
mobile/Terminal.qml Normal file
View File

@@ -0,0 +1,124 @@
/*
Copyright 2018 Benjamin Vedder benjamin@vedder.se
This file is part of VESC Tool.
VESC Tool is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
VESC Tool is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import Ennoid.bmsinterface 1.0
import Ennoid.commands 1.0
import Ennoid.configparams 1.0
Item {
property Commands mCommands: VescIf.commands()
ColumnLayout {
id: column
anchors.fill: parent
spacing: 0
ScrollView {
id: scroll
Layout.fillWidth: true
Layout.fillHeight: true
contentWidth: terminalText.width
clip: true
TextArea {
id: terminalText
anchors.fill: parent
readOnly: true
font.family: "DejaVu Sans Mono"
}
}
Rectangle {
Layout.fillWidth: true
height: stringInput.implicitHeight + 14
border.width: 2
border.color: "#8d8d8d"
color: "#33a8a8a8"
radius: 3
TextInput {
id: stringInput
anchors.fill: parent
anchors.margins: 7
font.pointSize: 12
focus: true
}
}
RowLayout {
Layout.fillWidth: true
Button {
Layout.preferredWidth: 100
Layout.fillWidth: true
text: "Clear"
onClicked: {
terminalText.clear()
}
}
Button {
Layout.preferredWidth: 100
Layout.fillWidth: true
text: "Send"
onClicked: {
mCommands.sendTerminalCmd(stringInput.text)
stringInput.clear()
}
}
Button {
Layout.preferredWidth: 50
Layout.fillWidth: true
text: "..."
onClicked: menu.open()
Menu {
id: menu
width: 500
MenuItem {
text: "Print Faults"
onTriggered: {
mCommands.sendTerminalCmd("faults")
}
}
MenuItem {
text: "Print Threads"
onTriggered: {
mCommands.sendTerminalCmd("threads")
}
}
}
}
}
}
Connections {
target: mCommands
onPrintReceived: {
terminalText.text += "\n" + str
}
}
}

135
mobile/fwhelper.cpp Normal file
View File

@@ -0,0 +1,135 @@
/*
Original copyright 2018 Benjamin Vedder benjamin@vedder.se and the VESC Tool project ( https://github.com/vedderb/vesc_tool )
Forked to:
Copyright 2018 Danny Bokma github@diebie.nl (https://github.com/DieBieEngineering/DieBieMS-Tool)
Now forked to:
Copyright 2019 - 2020 Kevin Dionne kevin.dionne@ennoid.me (https://github.com/EnnoidMe/ENNOID-BMS-Tool)
This file is part of ENNOID-BMS Tool.
ENNOID-BMS Tool is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ENNOID-BMS Tool is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "fwhelper.h"
#include <QDirIterator>
FwHelper::FwHelper(QObject *parent) : QObject(parent)
{
}
QVariantMap FwHelper::getHardwares(QString hw)
{
QVariantMap hws;
QDirIterator it("://res/firmwares");
while (it.hasNext()) {
QFileInfo fi(it.next());
QStringList names = fi.fileName().split("_o_");
if (fi.isDir() && (hw.isEmpty() || names.contains(hw, Qt::CaseInsensitive))) {
QString name = names.at(0);
for(int i = 1;i < names.size();i++) {
name += " & " + names.at(i);
}
hws.insert(name, fi.absoluteFilePath());
}
}
return hws;
}
QVariantMap FwHelper::getFirmwares(QString hw)
{
QVariantMap fws;
QDirIterator it(hw);
while (it.hasNext()) {
QFileInfo fi(it.next());
fws.insert(fi.fileName(), fi.absoluteFilePath());
}
return fws;
}
QVariantMap FwHelper::getBootloaders(QString hw)
{
QVariantMap bls;
QDirIterator it("://res/bootloaders");
while (it.hasNext()) {
QFileInfo fi(it.next());
QStringList names = fi.fileName().replace(".bin", "").split("_o_");
if (!fi.isDir() && (hw.isEmpty() || names.contains(hw, Qt::CaseInsensitive))) {
QString name = names.at(0);
for(int i = 1;i < names.size();i++) {
name += " & " + names.at(i);
}
bls.insert(name, fi.absoluteFilePath());
}
}
if (bls.isEmpty()) {
QFileInfo generic("://res/bootloaders/generic.bin");
if (generic.exists()) {
bls.insert("generic", generic.absoluteFilePath());
}
}
return bls;
}
bool FwHelper::uploadFirmware(QString filename, BMSInterface *dieBieMS, bool isBootloader, bool checkName)
{
// TODO: Should this be removed on android?
if (filename.startsWith("file:/")) {
filename.remove(0, 6);
}
QFile file;
file.setFileName(filename);
QFileInfo fileInfo(filename);
if (checkName) {
if (!(fileInfo.fileName().startsWith("ENNOID")) || !fileInfo.fileName().endsWith(".bin")) {
dieBieMS->emitMessageDialog(tr("Upload Error"),tr("The selected file name seems to be invalid."),false, false);
return false;
}
}
if (!file.open(QIODevice::ReadOnly)) {
dieBieMS->emitMessageDialog(tr("Upload Error"),
tr("Could not open file. Make sure that the path is valid."),
false);
qDebug() << fileInfo.fileName() << fileInfo.absolutePath();
return false;
}
if (file.size() > 400000) {
dieBieMS->emitMessageDialog(tr("Upload Error"),
tr("The selected file is too large to be a firmware."),
false);
return false;
}
QByteArray data = file.readAll();
dieBieMS->commands()->startFirmwareUpload(data, isBootloader);
return true;
}

48
mobile/fwhelper.h Normal file
View File

@@ -0,0 +1,48 @@
/*
Original copyright 2018 Benjamin Vedder benjamin@vedder.se and the VESC Tool project ( https://github.com/vedderb/vesc_tool )
Forked to:
Copyright 2018 Danny Bokma github@diebie.nl (https://github.com/DieBieEngineering/DieBieMS-Tool)
Now forked to:
Copyright 2019 - 2020 Kevin Dionne kevin.dionne@ennoid.me (https://github.com/EnnoidMe/ENNOID-BMS-Tool)
This file is part of ENNOID-BMS Tool.
ENNOID-BMS Tool is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ENNOID-BMS Tool is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef FWHELPER_H
#define FWHELPER_H
#include <QObject>
#include <QVariantMap>
#include "bmsinterface.h"
class FwHelper : public QObject
{
Q_OBJECT
public:
explicit FwHelper(QObject *parent = nullptr);
Q_INVOKABLE QVariantMap getHardwares(QString hw = "");
Q_INVOKABLE QVariantMap getFirmwares(QString hw);
Q_INVOKABLE QVariantMap getBootloaders(QString hw);
Q_INVOKABLE bool uploadFirmware(QString filename, BMSInterface *dieBieMS, bool isBootloader, bool isIncluded);
signals:
public slots:
};
#endif // FWHELPER_H

498
mobile/main.qml Normal file
View File

@@ -0,0 +1,498 @@
/*
Copyright 2017 - 2018 Benjamin Vedder benjamin@vedder.se
This file is part of VESC Tool.
VESC Tool is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
VESC Tool is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import Qt.labs.settings 1.0 as QSettings
import Ennoid.bmsinterface 1.0
import Ennoid.commands 1.0
import Ennoid.configparams 1.0
import Ennoid.utility 1.0
ApplicationWindow {
id: appWindow
property Commands mCommands: VescIf.commands()
property ConfigParams mbmsConfig: VescIf.bmsConfig()
property ConfigParams mInfoConf: VescIf.infoConfig()
visible: true
width: 500
height: 850
title: qsTr("ENNOID-BMS Tool")
Component.onCompleted: {
// Utility.checkVersion(VescIf)
}
Controls {
id: controls
parentWidth: appWindow.width
parentHeight: appWindow.height - footer.height - tabBar.height
}
Drawer {
id: drawer
width: 0.5 * appWindow.width
height: appWindow.height - footer.height - tabBar.height
y: tabBar.height
dragMargin: 20
ColumnLayout {
anchors.fill: parent
anchors.margins: 10
spacing: 0
Image {
id: image
Layout.preferredWidth: Math.min(parent.width, parent.height)
Layout.preferredHeight: (300 * Layout.preferredWidth) / 1549
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
source: "qrc:/res/logo_white.png"
}
Button {
id: reconnectButton
Layout.fillWidth: true
text: "Reconnect"
enabled: false
flat: true
onClicked: {
VescIf.reconnectLastPort()
}
}
Button {
Layout.fillWidth: true
text: "Disconnect"
enabled: connBle.disconnectButton.enabled
flat: true
onClicked: {
VescIf.disconnectPort()
}
}
Item {
// Spacer
Layout.fillWidth: true
Layout.fillHeight: true
}
Button {
Layout.fillWidth: true
text: "About"
flat: true
onClicked: {
VescIf.emitMessageDialog(
"About",
Utility.aboutText(),
true, true)
}
}
Button {
Layout.fillWidth: true
text: "Changelog"
flat: true
onClicked: {
VescIf.emitMessageDialog(
"EBMS Tool Changelog",
Utility.vescToolChangeLog(),
true, false)
}
}
Button {
Layout.fillWidth: true
text: "License"
flat: true
onClicked: {
VescIf.emitMessageDialog(
mInfoConf.getLongName("gpl_text"),
mInfoConf.getDescription("gpl_text"),
true, true)
}
}
}
}
SwipeView {
id: swipeView
currentIndex: tabBar.currentIndex
anchors.fill: parent
Page {
ConnectBle {
id: connBle
anchors.fill: parent
anchors.margins: 10
onRequestOpenControls: {
controls.openDialog()
}
}
}
Page {
RowLayout {
anchors.fill: parent
spacing: 0
Rectangle {
color: "#4f4f4f"
width: 16
Layout.fillHeight: true
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
PageIndicator {
count: rtSwipeView.count
currentIndex: rtSwipeView.currentIndex
anchors.centerIn: parent
rotation: 90
}
}
SwipeView {
id: rtSwipeView
enabled: true
clip: true
Layout.fillWidth: true
Layout.fillHeight: true
orientation: Qt.Vertical
Page {
RtData {
anchors.fill: parent
}
}
/*Page {
RtDataSetup {
anchors.fill: parent
}
}*/
}
}
}
Page {
ConfigPageGeneral {
id: confPageGeneral
anchors.fill: parent
anchors.leftMargin: 10
anchors.rightMargin: 10
}
}
Page {
ConfigPageCell {
id: confPageCell
anchors.fill: parent
anchors.leftMargin: 10
anchors.rightMargin: 10
}
}
Page {
ConfigPageSwitch {
id: confPageSwitch
anchors.fill: parent
anchors.leftMargin: 10
anchors.rightMargin: 10
}
}
Page {
ConfigPageSignal {
id: confPageSignal
anchors.fill: parent
anchors.leftMargin: 10
anchors.rightMargin: 10
}
}
Page {
ConfigPageDisplay {
id: confPageDisplay
anchors.fill: parent
anchors.leftMargin: 10
anchors.rightMargin: 10
}
}
Page {
FwUpdate {
anchors.fill: parent
}
}
Page {
Terminal {
anchors.fill: parent
anchors.leftMargin: 10
anchors.rightMargin: 10
anchors.topMargin: 10
}
}
}
header: Rectangle {
color: "#5f5f5f"
height: tabBar.height
RowLayout {
anchors.fill: parent
spacing: 0
ToolButton {
Layout.preferredHeight: tabBar.height
Layout.preferredWidth: tabBar.height - 10
Image {
id: manuButton
anchors.centerIn: parent
width: tabBar.height * 0.5
height: tabBar.height * 0.5
opacity: 0.5
source: "qrc:/res/icons/Settings-96.png"
}
onClicked: {
if (drawer.visible) {
drawer.close()
} else {
drawer.open()
}
}
}
TabBar {
id: tabBar
currentIndex: swipeView.currentIndex
Layout.fillWidth: true
implicitWidth: 0
clip: true
background: Rectangle {
opacity: 1
color: "#4f4f4f"
}
property int buttons: 9
property int buttonWidth: 120
TabButton {
text: qsTr("Connection")
width: Math.max(tabBar.buttonWidth, tabBar.width / tabBar.buttons)
}
TabButton {
text: qsTr("RT")
width: Math.max(tabBar.buttonWidth, tabBar.width / tabBar.buttons)
}
TabButton {
text: qsTr("General")
width: Math.max(tabBar.buttonWidth, tabBar.width / tabBar.buttons)
}
TabButton {
text: qsTr("Cell cfg")
width: Math.max(tabBar.buttonWidth, tabBar.width / tabBar.buttons)
}
TabButton {
text: qsTr("Switch")
width: Math.max(tabBar.buttonWidth, tabBar.width / tabBar.buttons)
}
TabButton {
text: qsTr("Signal")
width: Math.max(tabBar.buttonWidth, tabBar.width / tabBar.buttons)
}
TabButton {
text: qsTr("Display")
width: Math.max(tabBar.buttonWidth, tabBar.width / tabBar.buttons)
}
TabButton {
text: qsTr("Firmware")
width: Math.max(tabBar.buttonWidth, tabBar.width / tabBar.buttons)
}
TabButton {
text: qsTr("Terminal")
width: Math.max(tabBar.buttonWidth, tabBar.width / tabBar.buttons)
}
}
}
}
footer: Rectangle {
id: connectedRect
color: "#4f4f4f"
Text {
id: connectedText
color: "white"
text: VescIf.getConnectedPortName()
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
anchors.fill: parent
}
width: parent.width
height: 20
}
Timer {
id: statusTimer
interval: 1600
running: false
repeat: false
onTriggered: {
connectedText.text = VescIf.getConnectedPortName()
connectedRect.color = "4f4f4f"
}
}
Timer {
id: uiTimer
interval: 1000
running: true
repeat: true
onTriggered: {
if (!statusTimer.running && connectedText.text !== VescIf.getConnectedPortName()) {
connectedText.text = VescIf.getConnectedPortName()
}
}
}
Timer {
id: confTimer
interval: 1000
running: true
repeat: true
property bool mcConfRx: false
onTriggered: {
if (VescIf.isPortConnected()) {
if (!mcConfRx) {
mCommands.getBMSconf()
}
}
}
}
Timer {
id: rtTimer
interval: 50
running: true
repeat: true
onTriggered: {
if (VescIf.isPortConnected() && tabBar.currentIndex == 1) {
// Sample RT data when the RT page is selected
interval = 50
mCommands.getValues()
}
}
}
Dialog {
id: vescDialog
standardButtons: Dialog.Ok
modal: true
focus: true
closePolicy: Popup.CloseOnEscape
width: parent.width - 20
height: Math.min(implicitHeight, parent.height - 40)
x: (parent.width - width) / 2
y: (parent.height - height) / 2
parent: ApplicationWindow.overlay
ScrollView {
anchors.fill: parent
clip: true
contentWidth: parent.width - 20
Text {
id: vescDialogLabel
color: "#ffffff"
linkColor: "lightblue"
verticalAlignment: Text.AlignVCenter
anchors.fill: parent
wrapMode: Text.WordWrap
textFormat: Text.RichText
onLinkActivated: {
Qt.openUrlExternally(link)
}
}
}
}
Connections {
target: VescIf
onPortConnectedChanged: {
connectedText.text = VescIf.getConnectedPortName()
if (VescIf.isPortConnected()) {
reconnectButton.enabled = true
}
}
onStatusMessage: {
connectedText.text = msg
connectedRect.color = isGood ? "green" : "red"
statusTimer.restart()
}
onMessageDialog: {
vescDialog.title = title
vescDialogLabel.text =(richText ? "<style>a:link { color: lightblue; }</style>" : "") + msg
vescDialogLabel.textFormat = richText ? Text.RichText : Text.AutoText
vescDialog.open()
}
onFwRxChanged: {
if (rx) {
if(limited){
swipeView.setCurrentIndex(5)
}else {
mCommands.getBMSconf()
}
}
}
}
Connections {
target: mbmsConfig
onUpdated: {
confTimer.mcConfRx = true
}
}
}

13
mobile/mobile.pri Normal file
View File

@@ -0,0 +1,13 @@
HEADERS += \
$$PWD/qmlui.h \
$$PWD/fwhelper.h
SOURCES += \
$$PWD/qmlui.cpp \
$$PWD/fwhelper.cpp
RESOURCES += \
$$PWD/qml.qrc
DISTFILES +=

31
mobile/qml.qrc Normal file
View File

@@ -0,0 +1,31 @@
<RCC>
<qresource prefix="/">
<file>qtquickcontrols2.conf</file>
</qresource>
<qresource prefix="/mobile">
<file>main.qml</file>
<file>ConnectBle.qml</file>
<file>RtData.qml</file>
<file>FwUpdate.qml</file>
<file>FilePicker.qml</file>
<file>ParamEditDouble.qml</file>
<file>ParamEditors.qml</file>
<file>DoubleSpinBox.qml</file>
<file>ParamEditInt.qml</file>
<file>ParamEditEnum.qml</file>
<file>ParamEditBool.qml</file>
<file>ParamEditString.qml</file>
<file>ParamEditSeparator.qml</file>
<file>PpmMap.qml</file>
<file>NrfPair.qml</file>
<file>Controls.qml</file>
<file>Terminal.qml</file>
<file>ConfigPageGeneral.qml</file>
<file>ConfigPageCell.qml</file>
<file>ConfigPageSwitch.qml</file>
<file>ConfigPageDisplay.qml</file>
<file>ProgressBar.qml</file>
<file>PairingDialog.qml</file>
<file>ConfigPageSignal.qml</file>
</qresource>
</RCC>

70
mobile/qmlui.cpp Normal file
View File

@@ -0,0 +1,70 @@
/*
Original copyright 2018 Benjamin Vedder benjamin@vedder.se and the VESC Tool project ( https://github.com/vedderb/vesc_tool )
Forked to:
Copyright 2018 Danny Bokma github@diebie.nl (https://github.com/DieBieEngineering/DieBieMS-Tool)
Now forked to:
Copyright 2019 - 2020 Kevin Dionne kevin.dionne@ennoid.me (https://github.com/EnnoidMe/ENNOID-BMS-Tool)
This file is part of ENNOID-BMS Tool.
ENNOID-BMS Tool is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ENNOID-BMS Tool is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "qmlui.h"
#include "fwhelper.h"
#include <QQuickStyle>
QmlUi::QmlUi(QObject *parent) : QObject(parent)
{
mEngine = new QQmlApplicationEngine(this);
}
bool QmlUi::startQmlUi()
{
qmlRegisterSingletonType<BMSInterface>("Ennoid.bmsinterface", 1, 0, "VescIf", BMSinterface_singletontype_provider);
qmlRegisterSingletonType<Utility>("Ennoid.utility", 1, 0, "Utility", utility_singletontype_provider);
qmlRegisterType<BleUart>("Ennoid.bleuart", 1, 0, "BleUart");
qmlRegisterType<Commands>("Ennoid.commands", 1, 0, "Commands");
qmlRegisterType<ConfigParams>("Ennoid.configparams", 1, 0, "ConfigParams");
qmlRegisterType<FwHelper>("Ennoid.fwhelper", 1, 0, "FwHelper");
mEngine->load(QUrl(QLatin1String("qrc:/mobile/main.qml")));
return !mEngine->rootObjects().isEmpty();
}
QObject *QmlUi::BMSinterface_singletontype_provider(QQmlEngine *engine, QJSEngine *scriptEngine)
{
(void)engine;
(void)scriptEngine;
BMSInterface *vesc = new BMSInterface();
vesc->bmsConfig()->loadParamsXml("://res/config.xml");
vesc->infoConfig()->loadParamsXml("://res/info.xml");
return vesc;
}
QObject *QmlUi::utility_singletontype_provider(QQmlEngine *engine, QJSEngine *scriptEngine)
{
(void)engine;
(void)scriptEngine;
Utility *util = new Utility();
return util;
}

54
mobile/qmlui.h Normal file
View File

@@ -0,0 +1,54 @@
/*
Original copyright 2018 Benjamin Vedder benjamin@vedder.se and the VESC Tool project ( https://github.com/vedderb/vesc_tool )
Forked to:
Copyright 2018 Danny Bokma github@diebie.nl (https://github.com/DieBieEngineering/DieBieMS-Tool)
Now forked to:
Copyright 2019 - 2020 Kevin Dionne kevin.dionne@ennoid.me (https://github.com/EnnoidMe/ENNOID-BMS-Tool)
This file is part of ENNOID-BMS Tool.
ENNOID-BMS Tool is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ENNOID-BMS Tool is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef QMLUI_H
#define QMLUI_H
#include <QObject>
#include <QQmlApplicationEngine>
#include "bmsinterface.h"
#include "utility.h"
class QmlUi : public QObject
{
Q_OBJECT
public:
explicit QmlUi(QObject *parent = nullptr);
bool startQmlUi();
signals:
public slots:
private:
QQmlApplicationEngine *mEngine;
static QObject *BMSinterface_singletontype_provider(QQmlEngine *engine, QJSEngine *scriptEngine);
static QObject *utility_singletontype_provider(QQmlEngine *engine, QJSEngine *scriptEngine);
};
#endif // QMLUI_H

View File

@@ -0,0 +1,17 @@
; This file can be edited to change the style of the application
; See Styling Qt Quick Controls 2 in the documentation for details:
; http://doc.qt.io/qt-5/qtquickcontrols2-styles.html
[Controls]
Style=Material
[Universal]
Theme=Light
;Accent=Steel
[Material]
Theme=Dark
Accent=LightBlue
;Primary=BlueGray
;Background="#1D1D1D"
;Foreground=White