Skip to content
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
643a533
Added a tab for the Color LED with a color picker and information tex…
enyanil Oct 27, 2025
b8da6c7
Added dropdown for deck position
enyanil Nov 3, 2025
11d3428
Remove unused import of QtWidgets
gemenerik Nov 3, 2025
282f4d3
Add type ignore comment for color_led_tab_class UI loading
gemenerik Nov 3, 2025
38de4a4
Fix Pylance warnings
gemenerik Nov 3, 2025
6777ec6
Implement Color LED deck functionality and thermal monitoring
gemenerik Nov 3, 2025
ebbb35c
Use renamed Color LED param
gemenerik Nov 3, 2025
1047d56
Refactor color LED code into classes
gemenerik Nov 4, 2025
872ab0d
Handle not being connected (disable) and top/bottom deck
enyanil Nov 4, 2025
b51768c
Use updated param and log names
gemenerik Nov 4, 2025
102603a
Merge branch 'enya/hp-led-client-gui' into rik/hp-led-client-gui
gemenerik Nov 4, 2025
d7278f1
Fix Color LED deck detection and dynamic UI enabling
gemenerik Nov 4, 2025
3544204
Fix flake8
gemenerik Nov 4, 2025
79f38ea
Update parameter and log names for Color LED decks
gemenerik Nov 5, 2025
390424d
Switched default color in color led and moved + disable/enable intens…
enyanil Nov 7, 2025
5ef35a0
Added color tab documentation
ArisMorgens Nov 11, 2025
48bd3c8
Fix Color LED Tab to fetch actual colors and prevent unwanted writes
gemenerik Nov 11, 2025
dfcc6e8
Updated flight tab documentation (withtout the LED-ring part)
ArisMorgens Nov 12, 2025
3b41b01
Added documentation for the LED Ring tab
ArisMorgens Nov 13, 2025
5218d3f
Changes button-text-color based on color brightness in ring tab
enyanil Nov 19, 2025
8262ed3
Removed unused import
enyanil Nov 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added client-qt.pro
Empty file.
634 changes: 634 additions & 0 deletions src/cfclient/ui/tabs/ColorLEDTab.py

Large diffs are not rendered by default.

96 changes: 1 addition & 95 deletions src/cfclient/ui/tabs/FlightTab.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
# +------+ / /_/ / / /_/ /__/ / / /_/ / / /_/ __/
# || || /_____/_/\__/\___/_/ \__,_/ /___/\___/
#
# Copyright (C) 2011-2023 Bitcraze AB
# Copyright (C) 2011-2025 Bitcraze AB
#
# Crazyflie Nano Quadcopter Client
#
Expand Down Expand Up @@ -195,17 +195,6 @@ def __init__(self, helper):

self.uiSetupReady()

self._led_ring_headlight.clicked.connect(
lambda enabled: self._helper.cf.param.set_value("ring.headlightEnable", int(enabled)))

self._helper.cf.param.add_update_callback(
group="ring", name="headlightEnable",
cb=(lambda name, checked: self._led_ring_headlight.setChecked(bool(int(checked)))))

self._ledring_nbr_effects = 0

self._helper.cf.param.add_update_callback(group="ring", name="effect", cb=self._ring_effect_updated)

self._helper.cf.param.add_update_callback(group="imu_sensors", cb=self._set_available_sensors)

self._helper.cf.param.all_updated.add_callback(self._all_params_updated)
Expand All @@ -219,10 +208,7 @@ def __init__(self, helper):
self.targetCalPitch.setValue(Config().get("trim_pitch"))
self.targetCalRoll.setValue(Config().get("trim_roll"))

self._helper.inputDeviceReader.alt1_updated.add_callback(self.alt1_updated)
self._helper.inputDeviceReader.alt2_updated.add_callback(self.alt2_updated)
self._tf_state = 0
self._ring_effect = 0

# Connect callbacks for input device limiting of roll/pitch/yaw/thrust
self._helper.inputDeviceReader.limiting_updated.add_callback(self._limiting_updated.emit)
Expand Down Expand Up @@ -516,16 +502,6 @@ def disconnected(self, linkURI):
self._enable_estimators(False)

self.logAltHold = None
self._led_ring_effect.setEnabled(False)
self._led_ring_effect.clear()
try:
self._led_ring_effect.currentIndexChanged.disconnect(
self._ring_effect_changed)
except TypeError:
# Signal was not connected
pass
self._led_ring_effect.setCurrentIndex(-1)
self._led_ring_headlight.setEnabled(False)

try:
self._assist_mode_combo.currentIndexChanged.disconnect(
Expand Down Expand Up @@ -714,81 +690,11 @@ def _assisted_control_updated(self, enabled):
else:
self._helper.cf.param.set_value("flightmode.althold", int(enabled))

def alt1_updated(self, state):
if state:
new_index = (self._ring_effect+1) % (self._ledring_nbr_effects+1)
self._helper.cf.param.set_value("ring.effect", str(new_index))

def alt2_updated(self, state):
self._helper.cf.param.set_value("ring.headlightEnable", str(state))

def _all_params_updated(self):
self._ring_populate_dropdown()
self._populate_assisted_mode_dropdown()
self._update_flight_commander(True)
self._update_supervisor_and_arming(True)

def _ring_populate_dropdown(self):
try:
nbr = int(self._helper.cf.param.values["ring"]["neffect"])
current = int(self._helper.cf.param.values["ring"]["effect"])
except KeyError:
return

# Used only in alt1_updated function
self._ring_effect = current
self._ledring_nbr_effects = nbr

hardcoded_names = {
0: "Off",
1: "White spinner",
2: "Color spinner",
3: "Tilt effect",
4: "Brightness effect",
5: "Color spinner 2",
6: "Double spinner",
7: "Solid color effect",
8: "Factory test",
9: "Battery status",
10: "Boat lights",
11: "Alert",
12: "Gravity",
13: "LED tab",
14: "Color fader",
15: "Link quality",
16: "Location server status",
17: "Sequencer",
18: "Lighthouse quality",
}

for i in range(nbr + 1):
name = "{}: ".format(i)
if i in hardcoded_names:
name += hardcoded_names[i]
else:
name += "N/A"
self._led_ring_effect.addItem(name, i)

self._led_ring_effect.currentIndexChanged.connect(
self._ring_effect_changed)

self._led_ring_effect.setCurrentIndex(current)
if int(self._helper.cf.param.values["deck"]["bcLedRing"]) == 1:
self._led_ring_effect.setEnabled(True)
self._led_ring_headlight.setEnabled(True)

def _ring_effect_changed(self, index):
self._ring_effect = index
if index > -1:
i = self._led_ring_effect.itemData(index)
logger.debug("Changed effect to {}".format(i))
if i != int(self._helper.cf.param.values["ring"]["effect"]):
self._helper.cf.param.set_value("ring.effect", str(i))

def _ring_effect_updated(self, name, value):
if self._helper.cf.param.is_updated:
self._led_ring_effect.setCurrentIndex(int(value))

def _populate_assisted_mode_dropdown(self):
self._assist_mode_combo.addItem("Altitude hold", 0)
self._assist_mode_combo.addItem("Position hold", 1)
Expand Down
114 changes: 109 additions & 5 deletions src/cfclient/ui/tabs/LEDTab.py → src/cfclient/ui/tabs/LEDRingTab.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
# +------+ / /_/ / / /_/ /__/ / / /_/ / / /_/ __/
# || || /_____/_/\__/\___/_/ \__,_/ /___/\___/
#
# Copyright (C) 2011-2023 Bitcraze AB
# Copyright (C) 2011-2025 Bitcraze AB
#
# Crazyflie Nano Quadcopter Client
#
Expand Down Expand Up @@ -43,23 +43,44 @@
from cflib.crazyflie.mem import MemoryElement

__author__ = 'Bitcraze AB'
__all__ = ['LEDTab']
__all__ = ['LEDRingTab']

logger = logging.getLogger(__name__)

led_tab_class = uic.loadUiType(cfclient.module_path + "/ui/tabs/ledTab.ui")[0]
led_ring_tab_class = uic.loadUiType(cfclient.module_path + "/ui/tabs/ledRingTab.ui")[0]


class LEDTab(TabToolbox, led_tab_class):
class LEDRingTab(TabToolbox, led_ring_tab_class):
"""Tab for plotting logging data"""

_connected_signal = pyqtSignal(str)
_disconnected_signal = pyqtSignal(str)

def __init__(self, helper):
super(LEDTab, self).__init__(helper, 'LED')
super(LEDRingTab, self).__init__(helper, 'LED Ring')
self.setupUi(self)

# LED-ring effect dropdown and headlight checkbox
self._ledring_nbr_effects = 0

# Connect the headlight checkbox
self._led_ring_headlight.clicked.connect(
lambda enabled: self._helper.cf.param.set_value("ring.headlightEnable", int(enabled)))

# Update headlight when param changes
self._helper.cf.param.add_update_callback(
group="ring", name="headlightEnable",
cb=lambda name, checked: self._led_ring_headlight.setChecked(bool(int(checked))))

# Update LED-ring effect when param changes
self._helper.cf.param.add_update_callback(
group="ring", name="effect",
cb=self._ring_effect_updated)

# Populate dropdown when all params are updated
self._helper.cf.param.all_updated.add_callback(self._ring_populate_dropdown)


# Always wrap callbacks from Crazyflie API though QT Signal/Slots
# to avoid manipulating the UI when rendering it
self._connected_signal.connect(self._connected)
Expand Down Expand Up @@ -107,6 +128,12 @@ def __init__(self, helper):
self._intensity_spin.setValue)
self._intensity_spin.valueChanged.connect(
self._intensity_slider.setValue)

self._helper.inputDeviceReader.alt1_updated.add_callback(self.alt1_updated)
self._helper.inputDeviceReader.alt2_updated.add_callback(self.alt2_updated)

self._led_ring_effect.setEnabled(False)
self._led_ring_headlight.setEnabled(False)

def _select(self, nbr):
col = QtGui.QColor() # default to invalid
Expand Down Expand Up @@ -152,6 +179,9 @@ def _connected(self, link_uri):
btn.setStyleSheet("background-color: black")
self._intensity_slider.setEnabled(True)
self._intensity_spin.setEnabled(True)

self._led_ring_effect.setEnabled(True)
self._led_ring_headlight.setEnabled(True)

def _disconnected(self, link_uri):
"""Callback for when the Crazyflie has been disconnected"""
Expand All @@ -161,3 +191,77 @@ def _disconnected(self, link_uri):
self._intensity_slider.setEnabled(False)
self._intensity_spin.setEnabled(False)
self._intensity_slider.setValue(100)

self._led_ring_effect.setEnabled(False)
self._led_ring_headlight.setEnabled(False)

def _ring_populate_dropdown(self):
try:
nbr = int(self._helper.cf.param.values["ring"]["neffect"])
current = int(self._helper.cf.param.values["ring"]["effect"])
except KeyError:
return

self._ring_effect = current
self._ledring_nbr_effects = nbr

hardcoded_names = {
0: "Off",
1: "White spinner",
2: "Color spinner",
3: "Tilt effect",
4: "Brightness effect",
5: "Color spinner 2",
6: "Double spinner",
7: "Solid color effect",
8: "Factory test",
9: "Battery status",
10: "Boat lights",
11: "Alert",
12: "Gravity",
13: "LED tab",
14: "Color fader",
15: "Link quality",
16: "Location server status",
17: "Sequencer",
18: "Lighthouse quality",
}

self._led_ring_effect.clear()
for i in range(nbr + 1):
name = "{}: ".format(i)
name += hardcoded_names.get(i, "N/A")
self._led_ring_effect.addItem(name, i)

self._led_ring_effect.currentIndexChanged.connect(self._ring_effect_changed)
self._led_ring_effect.setCurrentIndex(current)
self._led_ring_effect.setEnabled(int(self._helper.cf.param.values["deck"]["bcLedRing"]) == 1)
self._led_ring_headlight.setEnabled(int(self._helper.cf.param.values["deck"]["bcLedRing"]) == 1)

try:
self._helper.cf.param.set_value("ring.effect", "13")
self._led_ring_effect.setCurrentIndex(13)
self._ring_effect = 13
logger.info("Initialized LED ring to 'LED tab' mode (effect 13).")
except Exception as e:
logger.warning(f"Could not set LED tab effect on connect: {e}")

def _ring_effect_changed(self, index):
self._ring_effect = index
if index > -1:
i = self._led_ring_effect.itemData(index)
if i != int(self._helper.cf.param.values["ring"]["effect"]):
self._helper.cf.param.set_value("ring.effect", str(i))

def _ring_effect_updated(self, name, value):
if self._helper.cf.param.is_updated:
self._led_ring_effect.setCurrentIndex(int(value))

def alt1_updated(self, state):
if state:
new_index = (self._ring_effect+1) % (self._ledring_nbr_effects+1)
self._helper.cf.param.set_value("ring.effect", str(new_index))

def alt2_updated(self, state):
self._helper.cf.param.set_value("ring.headlightEnable", str(state))

6 changes: 4 additions & 2 deletions src/cfclient/ui/tabs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
# from .ExampleTab import ExampleTab
from .FlightTab import FlightTab
# from .GpsTab import GpsTab
from .LEDTab import LEDTab
from .LEDRingTab import LEDRingTab
from .LogBlockTab import LogBlockTab
from .LogTab import LogTab
from .ParamTab import ParamTab
Expand All @@ -43,6 +43,7 @@
from .LogClientTab import LogClientTab
from .lighthouse_tab import LighthouseTab
from .TuningTab import TuningTab
from .ColorLEDTab import ColorLEDTab

__author__ = 'Bitcraze AB'
__all__ = []
Expand All @@ -52,7 +53,8 @@
# ExampleTab,
FlightTab,
# GpsTab,
LEDTab,
LEDRingTab,
ColorLEDTab,
LogBlockTab,
LogTab,
ParamTab,
Expand Down
Loading
Loading