From dcc5b58fa9f4933c4470fcb648f421da4d81ff3b Mon Sep 17 00:00:00 2001 From: Andrew Tridgell Date: Mon, 13 Apr 2026 10:58:57 +1000 Subject: [PATCH 1/2] actuator_panel: per-channel command_type selector Adds a drop-down per channel to select the actuator Command type (UNITLESS, POSITION, FORCE, SPEED, PWM), defaulting to UNITLESS. When a non-unitless type is selected the -1..1 slider is greyed out, while the spinbox stays editable with a widened range so absolute values (e.g. PWM microseconds) can be entered directly. Co-Authored-By: Claude Opus 4.6 (1M context) --- dronecan_gui_tool/panels/actuator_panel.py | 32 ++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/dronecan_gui_tool/panels/actuator_panel.py b/dronecan_gui_tool/panels/actuator_panel.py index fb03941..5c83f59 100644 --- a/dronecan_gui_tool/panels/actuator_panel.py +++ b/dronecan_gui_tool/panels/actuator_panel.py @@ -9,7 +9,7 @@ import dronecan from functools import partial from PyQt6.QtWidgets import QVBoxLayout, QHBoxLayout, QWidget, QLabel, QDialog, QSlider, QSpinBox, QDoubleSpinBox, QCheckBox, \ - QPlainTextEdit + QPlainTextEdit, QComboBox from PyQt6.QtCore import QTimer, Qt from logging import getLogger from ..widgets import make_icon_button, get_icon, get_monospace_font @@ -36,6 +36,9 @@ def __init__(self, parent): self._slider.setTickInterval(1000) self._slider.setTickPosition(QSlider.TicksBothSides) self._slider.valueChanged.connect(lambda: self._spinbox.setValue(self._slider.value()/1000.0)) + sp = self._slider.sizePolicy() + sp.setRetainSizeWhenHidden(True) + self._slider.setSizePolicy(sp) self._spinbox = QDoubleSpinBox(self) self._spinbox.setMinimum(-1) @@ -56,6 +59,11 @@ def __init__(self, parent): self._enabled = QCheckBox('Enabled', self) self._enabled.setGeometry(0, 0, 10, 10) + self._command_type = QComboBox(self) + for label, value in (('UNITLESS', 0), ('POSITION', 1), ('FORCE', 2), ('SPEED', 3), ('PWM', 4)): + self._command_type.addItem(label, value) + self._command_type.currentIndexChanged.connect(self._on_command_type_changed) + layout = QVBoxLayout(self) sub_layout = QHBoxLayout(self) sub_layout.addStretch() @@ -65,6 +73,7 @@ def __init__(self, parent): layout.addWidget(self._spinbox) layout.addWidget(self._zero_button) layout.addWidget(self._actuator_id) + layout.addWidget(self._command_type) layout.addWidget(self._enabled) self.setLayout(layout) @@ -82,6 +91,25 @@ def get_actuator_id(self): def is_enabled(self): return self._enabled.isChecked() + def get_command_type(self): + return self._command_type.currentData() + + def _on_command_type_changed(self): + is_unitless = self._command_type.currentData() == 0 + self._slider.setEnabled(is_unitless) + self._slider.setVisible(is_unitless) + self._zero_button.setEnabled(is_unitless) + if is_unitless: + self._spinbox.setMinimum(-1) + self._spinbox.setMaximum(1) + self._spinbox.setDecimals(3) + self._spinbox.setSingleStep(0.001) + else: + self._spinbox.setDecimals(3) + self._spinbox.setMinimum(-1e6) + self._spinbox.setMaximum(1e6) + self._spinbox.setSingleStep(1) + class ActuatorPanel(QDialog): DEFAULT_INTERVAL = 0.1 @@ -161,7 +189,7 @@ def _do_broadcast(self): msg_cmd = dronecan.uavcan.equipment.actuator.Command() msg_cmd.actuator_id = sl.get_actuator_id() msg_cmd.command_value = sl.get_value() - msg_cmd.command_type = 0 + msg_cmd.command_type = sl.get_command_type() msg.commands.append(msg_cmd) self._node.broadcast(msg) From b01b16c969397014fe6117cef463e30fac6915fa Mon Sep 17 00:00:00 2001 From: Andrew Tridgell Date: Thu, 16 Apr 2026 08:34:28 +1000 Subject: [PATCH 2/2] actuator_panel: allow IDs up to 255 --- dronecan_gui_tool/panels/actuator_panel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dronecan_gui_tool/panels/actuator_panel.py b/dronecan_gui_tool/panels/actuator_panel.py index 5c83f59..4775946 100644 --- a/dronecan_gui_tool/panels/actuator_panel.py +++ b/dronecan_gui_tool/panels/actuator_panel.py @@ -52,7 +52,7 @@ def __init__(self, parent): self._actuator_id = QSpinBox(self) self._actuator_id.setMinimum(0) - self._actuator_id.setMaximum(15) + self._actuator_id.setMaximum(255) self._actuator_id.setValue(0) self._actuator_id.setPrefix('ID: ')