From 80852c28a7f8f5ed8f32f98585e44ce1cd4cc8d7 Mon Sep 17 00:00:00 2001 From: Dado Mista Date: Sun, 12 Jul 2026 10:47:11 -0700 Subject: [PATCH 1/2] Add support for COMM_DISABLE (persistent motor disable) Add Disable/Enable Motor actions in the Terminal menu Add a MOTOR DISABLED indicator in the status bar Add a conditional Disabled line in the RT data text. For mobile and desktop. --- commands.cpp | 9 +++++++++ commands.h | 1 + datatypes.h | 5 +++++ mainwindow.cpp | 26 ++++++++++++++++++++++++- mainwindow.h | 3 +++ mainwindow.ui | 20 +++++++++++++++++++ mobile/RtData.qml | 3 ++- mobile/Terminal.qml | 44 ++++++++++++++++++++++++++++++++++++++++++ mobile/main.qml | 13 +++++++++++-- widgets/rtdatatext.cpp | 3 +++ 10 files changed, 123 insertions(+), 4 deletions(-) diff --git a/commands.cpp b/commands.cpp index 45d79c68b..f176f9f78 100755 --- a/commands.cpp +++ b/commands.cpp @@ -284,6 +284,7 @@ void Commands::processPacket(QByteArray data) quint8 status = vb.vbPopFrontUint8(); values.has_timeout = status & 1; values.kill_sw_active = (status >> 1) & 1; + values.motor_disabled = (status >> 2) & 1; } } @@ -1509,6 +1510,14 @@ void Commands::shutdown() emitData(vb); } +void Commands::disableMotor(bool disable) +{ + VByteArray vb; + vb.vbAppendInt8(COMM_DISABLE); + vb.vbAppendInt8(disable ? 1 : 0); + emitData(vb); +} + void Commands::sendAlive() { VByteArray vb; diff --git a/commands.h b/commands.h index ff21cb23a..6af9ec71c 100644 --- a/commands.h +++ b/commands.h @@ -204,6 +204,7 @@ public slots: void detectMotorParam(double current, double min_rpm, double low_duty); void reboot(); void shutdown(); + void disableMotor(bool disable); void sendAlive(); void getDecodedPpm(); void getDecodedAdc(); diff --git a/datatypes.h b/datatypes.h index 97064fcb0..62b4e1221 100644 --- a/datatypes.h +++ b/datatypes.h @@ -188,6 +188,7 @@ struct MC_VALUES { Q_PROPERTY(double vq MEMBER vq) Q_PROPERTY(bool has_timeout MEMBER has_timeout) Q_PROPERTY(bool kill_sw_active MEMBER kill_sw_active) + Q_PROPERTY(bool motor_disabled MEMBER motor_disabled) public: MC_VALUES() { @@ -216,6 +217,7 @@ struct MC_VALUES { vq = 0.0; has_timeout = false; kill_sw_active = false; + motor_disabled = false; } bool operator==(const MC_VALUES &other) const { @@ -254,6 +256,7 @@ struct MC_VALUES { double vq; bool has_timeout; bool kill_sw_active; + bool motor_disabled; }; Q_DECLARE_METATYPE(MC_VALUES) @@ -1038,6 +1041,8 @@ typedef enum { COMM_CAN_UPDATE_BAUD_ALL = 158, COMM_MOTOR_ESTOP = 159, + + COMM_DISABLE = 160, } COMM_PACKET_ID; // CAN commands diff --git a/mainwindow.cpp b/mainwindow.cpp index 6a9f762e7..1b00d0f25 100755 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -220,6 +220,7 @@ MainWindow::MainWindow(QWidget *parent) : mPreferences->setVesc(mVesc); mStatusInfoTime = 0; + mMotorDisabledNow = false; mStatusLabel = new QLabel(this); ui->statusBar->addPermanentWidget(mStatusLabel); mDebugTimer = new QTimer(this); @@ -796,8 +797,11 @@ void MainWindow::timerSlot() } } else { QString str = mVesc->getConnectedPortName(); + if (mMotorDisabledNow && mVesc->isPortConnected()) { + str += tr(" | MOTOR DISABLED"); + } if (str != mStatusLabel->text()) { - mStatusLabel->setText(mVesc->getConnectedPortName()); + mStatusLabel->setText(str); static QString statusLast = ""; if (str != statusLast) { mPageDebugPrint->printConsole("Status: " + str + "
"); @@ -1075,6 +1079,7 @@ void MainWindow::valuesReceived(MC_VALUES values, unsigned int mask) (void)mask; ui->dispCurrent->setVal(values.current_motor); ui->dispDuty->setVal(values.duty_now * 100.0); + mMotorDisabledNow = values.motor_disabled; } void MainWindow::paramChangedDouble(QObject *src, QString name, double newParam) @@ -1118,6 +1123,25 @@ void MainWindow::on_actionShutdown_triggered() mVesc->commands()->shutdown(); } +void MainWindow::on_actionMotorDisable_triggered() +{ + QMessageBox::StandardButton answer = QMessageBox::question( + this, + tr("Disable Motor"), + tr("This will disable the motor until it is enabled again. Continue?"), + QMessageBox::Yes | QMessageBox::Cancel + ); + + if (answer == QMessageBox::Yes) { + mVesc->commands()->disableMotor(true); + } +} + +void MainWindow::on_actionMotorEnable_triggered() +{ + mVesc->commands()->disableMotor(false); +} + void MainWindow::on_stopButton_clicked() { mVesc->commands()->setCurrent(0); diff --git a/mainwindow.h b/mainwindow.h index efd3c8e15..64f0f60a6 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -104,6 +104,8 @@ private slots: void on_actionDisconnect_triggered(); void on_actionReboot_triggered(); void on_actionShutdown_triggered(); + void on_actionMotorDisable_triggered(); + void on_actionMotorEnable_triggered(); void on_stopButton_clicked(); void on_fullBrakeButton_clicked(); void on_actionReadMcconf_triggered(); @@ -177,6 +179,7 @@ private slots: QTimer *mTimer; QLabel *mStatusLabel; int mStatusInfoTime; + bool mMotorDisabledNow; bool mKeyLeft; bool mKeyRight; bool mMcConfRead; diff --git a/mainwindow.ui b/mainwindow.ui index 3478322d9..dccc71b60 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -673,6 +673,10 @@ + + + + @@ -771,6 +775,22 @@ Shutdown (for controllers with auto-shutdown support only) + + + Disable Motor + + + Disable the motor persistently (survives reboots). Only works while the motor is stationary. + + + + + Enable Motor + + + Re-enable the motor after Disable Motor + + true diff --git a/mobile/RtData.qml b/mobile/RtData.qml index ae0ea93c3..3cf250cd1 100644 --- a/mobile/RtData.qml +++ b/mobile/RtData.qml @@ -226,7 +226,8 @@ Item { "Wh Draw : " + parseFloat(values.watt_hours).toFixed(2) + " Wh\n" + "Wh Charge : " + parseFloat(values.watt_hours_charged).toFixed(2) + " Wh\n" + "ABS Tacho : " + values.tachometer_abs + " Counts\n" + - "Fault : " + values.fault_str + "Fault : " + values.fault_str + + (values.motor_disabled ? "\nDisabled : TRUE" : "") } } } diff --git a/mobile/Terminal.qml b/mobile/Terminal.qml index fd8d1a665..342700d5f 100644 --- a/mobile/Terminal.qml +++ b/mobile/Terminal.qml @@ -118,6 +118,18 @@ Item { mCommands.shutdown() } } + MenuItem { + text: "Disable Motor" + onTriggered: { + motorDisableDialog.open() + } + } + MenuItem { + text: "Enable Motor" + onTriggered: { + mCommands.disableMotor(false) + } + } MenuItem { text: "Restart LispBM" onTriggered: { @@ -125,6 +137,38 @@ Item { } } } + + Dialog { + id: motorDisableDialog + standardButtons: Dialog.Yes | Dialog.Cancel + modal: true + focus: true + width: parent.width - 20 + height: 200 + closePolicy: Popup.CloseOnEscape + title: "Disable Motor" + x: 10 + y: 10 + parent.height / 2 - height / 2 + parent: terminalPageItem + + Overlay.modal: Rectangle { + color: "#AA000000" + } + + Text { + color: Utility.getAppHexColor("lightText") + verticalAlignment: Text.AlignVCenter + anchors.fill: parent + wrapMode: Text.WordWrap + text: "This is going to disable the motor until it is enabled again, " + + "even after reboot. The command is ignored while the motor is " + + "spinning. Are you sure?" + } + + onAccepted: { + mCommands.disableMotor(true) + } + } } } diff --git a/mobile/main.qml b/mobile/main.qml index a9e7afc62..244a6701a 100644 --- a/mobile/main.qml +++ b/mobile/main.qml @@ -32,6 +32,7 @@ import Vedder.vesc.vesc3ditem 1.0 ApplicationWindow { id: appWindow property Commands mCommands: VescIf.commands() + property bool motorDisabledNow: false property ConfigParams mMcConf: VescIf.mcConfig() property ConfigParams mAppConf: VescIf.appConfig() property ConfigParams mInfoConf: VescIf.infoConfig() @@ -820,8 +821,12 @@ ApplicationWindow { running: true repeat: true onTriggered: { - if (!statusTimer.running && connectedText.text !== VescIf.getConnectedPortName()) { - connectedText.text = VescIf.getConnectedPortName() + var statusStr = VescIf.getConnectedPortName() + if (appWindow.motorDisabledNow && VescIf.isPortConnected()) { + statusStr += " | MOTOR DISABLED" + } + if (!statusTimer.running && connectedText.text !== statusStr) { + connectedText.text = statusStr } } } @@ -1183,6 +1188,10 @@ ApplicationWindow { Connections { target: mCommands + function onValuesReceived(values, mask) { + appWindow.motorDisabledNow = values.motor_disabled + } + function onValuesImuReceived(values, mask) { if (tabBar.currentIndex == (1 + indexOffset()) && rtSwipeView.currentIndex == 2) { vesc3dLoader.item.setRotation(values.roll, values.pitch, diff --git a/widgets/rtdatatext.cpp b/widgets/rtdatatext.cpp index 3dbb69bfb..0d65f90f2 100644 --- a/widgets/rtdatatext.cpp +++ b/widgets/rtdatatext.cpp @@ -82,6 +82,7 @@ void RtDataText::paintEvent(QPaintEvent *event) "T\n" "T\n" "T\n" + "T\n" "T\n"); int boxh_new = br.height(); @@ -128,11 +129,13 @@ void RtDataText::paintEvent(QPaintEvent *event) str = QString::asprintf("T FET : %.2f \u00B0C\n" "T Motor : %.2f \u00B0C\n" "Fault : %s\n" + "%s" "Tac : %i\n" "Tac ABS : %i\n", mValues.temp_mos, mValues.temp_motor, mValues.fault_str.toLocal8Bit().data(), + mValues.motor_disabled ? "Disabled: TRUE\n" : "", mValues.tachometer, mValues.tachometer_abs); From 991ae428ba0717a323d01322798b28c35eb116e9 Mon Sep 17 00:00:00 2001 From: Dado Mista Date: Sun, 12 Jul 2026 13:49:28 -0700 Subject: [PATCH 2/2] Show active fault and motor-disabled state in red in RT data Draws the fault value (when a fault is present) and the TRUE of the Disabled row in the themed red color in the RT data text overlay. The colored values are omitted from the main text draw and painted separately at their line positions using the monospace font metrics. --- widgets/rtdatatext.cpp | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/widgets/rtdatatext.cpp b/widgets/rtdatatext.cpp index 0d65f90f2..338ef3aeb 100644 --- a/widgets/rtdatatext.cpp +++ b/widgets/rtdatatext.cpp @@ -126,6 +126,10 @@ void RtDataText::paintEvent(QPaintEvent *event) Qt::AlignLeft, str); // Middle info box + bool fault_active = mValues.fault_code != FAULT_CODE_NONE; + + // The fault value and the TRUE of the disabled row are left out here and + // drawn separately in red below. str = QString::asprintf("T FET : %.2f \u00B0C\n" "T Motor : %.2f \u00B0C\n" "Fault : %s\n" @@ -134,8 +138,8 @@ void RtDataText::paintEvent(QPaintEvent *event) "Tac ABS : %i\n", mValues.temp_mos, mValues.temp_motor, - mValues.fault_str.toLocal8Bit().data(), - mValues.motor_disabled ? "Disabled: TRUE\n" : "", + fault_active ? "" : mValues.fault_str.toLocal8Bit().data(), + mValues.motor_disabled ? "Disabled: \n" : "", mValues.tachometer, mValues.tachometer_abs); @@ -143,10 +147,31 @@ void RtDataText::paintEvent(QPaintEvent *event) painter.fillRect(vidw / 2.0 - bbox_w / 2.0, 0, bbox_w, bbow_h, Utility::getAppQColor("normalBackground")); painter.setOpacity(1.0); + const double xmid = vidw / 2.0 - bbox_w / 2.0 + mTxtOfs; + painter.setPen(Utility::getAppQColor("normalText")); - painter.drawText(QRectF(vidw / 2.0 - bbox_w / 2.0 + mTxtOfs, mTxtOfs, mBoxW, mBoxH), + painter.drawText(QRectF(xmid, mTxtOfs, mBoxW, mBoxH), Qt::AlignLeft, str); + if (fault_active || mValues.motor_disabled) { + QFontMetrics fm = painter.fontMetrics(); + painter.setPen(Utility::getAppQColor("red")); + + if (fault_active) { + painter.drawText(QRectF(xmid + fm.horizontalAdvance("Fault : "), + mTxtOfs + 2 * fm.lineSpacing(), mBoxW, mBoxH), + Qt::AlignLeft, mValues.fault_str); + } + + if (mValues.motor_disabled) { + painter.drawText(QRectF(xmid + fm.horizontalAdvance("Disabled: "), + mTxtOfs + 3 * fm.lineSpacing(), mBoxW, mBoxH), + Qt::AlignLeft, "TRUE"); + } + + painter.setPen(Utility::getAppQColor("normalText")); + } + // Right info box str = QString::asprintf("Ah Draw : %.1f mAh\n" "Ah Charge : %.1f mAh\n"