cfclient 2024.7.1__py3-none-any.whl → 2025.12.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cfclient/__init__.py +9 -7
- cfclient/configs/config.json +1 -1
- cfclient/configs/log/PID_tuning/Attitude.json +46 -0
- cfclient/configs/log/PID_tuning/Attitude_rate.json +46 -0
- cfclient/configs/log/PID_tuning/Position.json +46 -0
- cfclient/configs/log/PID_tuning/Velocity.json +46 -0
- cfclient/configs/log/PID_tuning_components/Pitch.json +22 -0
- cfclient/configs/log/PID_tuning_components/Pitch_rate.json +22 -0
- cfclient/configs/log/PID_tuning_components/Position_x.json +22 -0
- cfclient/configs/log/PID_tuning_components/Position_y.json +22 -0
- cfclient/configs/log/PID_tuning_components/Position_z.json +22 -0
- cfclient/configs/log/PID_tuning_components/Roll.json +22 -0
- cfclient/configs/log/PID_tuning_components/Roll_rate.json +22 -0
- cfclient/configs/log/PID_tuning_components/Velocity_x.json +22 -0
- cfclient/configs/log/PID_tuning_components/Velocity_y.json +22 -0
- cfclient/configs/log/PID_tuning_components/Velocity_z.json +22 -0
- cfclient/configs/log/PID_tuning_components/Yaw.json +22 -0
- cfclient/configs/log/PID_tuning_components/Yaw_rate.json +22 -0
- cfclient/resources/log_param_doc.json +1 -1
- cfclient/ui/dialogs/about.py +5 -1
- cfclient/ui/dialogs/basestation_mode_dialog.py +12 -1
- cfclient/ui/dialogs/bootloader.py +76 -6
- cfclient/ui/dialogs/bootloader.ui +110 -24
- cfclient/ui/dialogs/cf2config.ui +1 -1
- cfclient/ui/dialogs/lighthouse_bs_geometry_dialog.py +0 -12
- cfclient/ui/dialogs/lighthouse_bs_geometry_dialog.ui +0 -7
- cfclient/ui/icons/bl.webp +0 -0
- cfclient/ui/icons/bolt.webp +0 -0
- cfclient/ui/icons/cf21.webp +0 -0
- cfclient/ui/icons/flapper.webp +0 -0
- cfclient/ui/icons/tag.webp +0 -0
- cfclient/ui/main.py +3 -3
- cfclient/ui/main.ui +1 -1
- cfclient/ui/tabs/ColorLEDTab.py +752 -0
- cfclient/ui/tabs/FlightTab.py +11 -108
- cfclient/ui/tabs/{LEDTab.py → LEDRingTab.py} +126 -12
- cfclient/ui/tabs/LogTab.py +2 -2
- cfclient/ui/tabs/ParamTab.py +90 -4
- cfclient/ui/tabs/__init__.py +4 -2
- cfclient/ui/tabs/colorLEDTab.ui +624 -0
- cfclient/ui/tabs/consoleTab.ui +1 -1
- cfclient/ui/tabs/flightTab.ui +28 -71
- cfclient/ui/tabs/{ledTab.ui → ledRingTab.ui} +63 -46
- cfclient/utils/input/__init__.py +3 -3
- cfclient/utils/periodictimer.py +1 -1
- cfclient/version.py +34 -0
- cfclient-2025.12.1.dist-info/METADATA +70 -0
- {cfclient-2024.7.1.dist-info → cfclient-2025.12.1.dist-info}/RECORD +54 -28
- {cfclient-2024.7.1.dist-info → cfclient-2025.12.1.dist-info}/WHEEL +1 -1
- {cfclient-2024.7.1.dist-info → cfclient-2025.12.1.dist-info}/top_level.txt +1 -0
- cfconfig/Makefile +51 -0
- cfconfig/configblock.py +111 -0
- cfclient-2024.7.1.dist-info/METADATA +0 -53
- {cfclient-2024.7.1.dist-info → cfclient-2025.12.1.dist-info}/entry_points.txt +0 -0
- {cfclient-2024.7.1.dist-info → cfclient-2025.12.1.dist-info/licenses}/LICENSE.txt +0 -0
cfclient/ui/tabs/FlightTab.py
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
# +------+ / /_/ / / /_/ /__/ / / /_/ / / /_/ __/
|
|
8
8
|
# || || /_____/_/\__/\___/_/ \__,_/ /___/\___/
|
|
9
9
|
#
|
|
10
|
-
# Copyright (C) 2011-
|
|
10
|
+
# Copyright (C) 2011-2025 Bitcraze AB
|
|
11
11
|
#
|
|
12
12
|
# Crazyflie Nano Quadcopter Client
|
|
13
13
|
#
|
|
@@ -138,7 +138,7 @@ class FlightTab(TabToolbox, flight_tab_class):
|
|
|
138
138
|
self._emergency_stop_updated_signal.connect(self.updateEmergencyStop)
|
|
139
139
|
self._helper.inputDeviceReader.emergency_stop_updated.add_callback(
|
|
140
140
|
self._emergency_stop_updated_signal.emit)
|
|
141
|
-
self._arm_updated_signal.connect(self.updateArm)
|
|
141
|
+
self._arm_updated_signal.connect(lambda: self.updateArm(from_controller=True))
|
|
142
142
|
self._helper.inputDeviceReader.arm_updated.add_callback(self._arm_updated_signal.emit)
|
|
143
143
|
|
|
144
144
|
self._helper.inputDeviceReader.heighthold_input_updated.add_callback(
|
|
@@ -195,17 +195,6 @@ class FlightTab(TabToolbox, flight_tab_class):
|
|
|
195
195
|
|
|
196
196
|
self.uiSetupReady()
|
|
197
197
|
|
|
198
|
-
self._led_ring_headlight.clicked.connect(
|
|
199
|
-
lambda enabled: self._helper.cf.param.set_value("ring.headlightEnable", int(enabled)))
|
|
200
|
-
|
|
201
|
-
self._helper.cf.param.add_update_callback(
|
|
202
|
-
group="ring", name="headlightEnable",
|
|
203
|
-
cb=(lambda name, checked: self._led_ring_headlight.setChecked(bool(int(checked)))))
|
|
204
|
-
|
|
205
|
-
self._ledring_nbr_effects = 0
|
|
206
|
-
|
|
207
|
-
self._helper.cf.param.add_update_callback(group="ring", name="effect", cb=self._ring_effect_updated)
|
|
208
|
-
|
|
209
198
|
self._helper.cf.param.add_update_callback(group="imu_sensors", cb=self._set_available_sensors)
|
|
210
199
|
|
|
211
200
|
self._helper.cf.param.all_updated.add_callback(self._all_params_updated)
|
|
@@ -219,10 +208,7 @@ class FlightTab(TabToolbox, flight_tab_class):
|
|
|
219
208
|
self.targetCalPitch.setValue(Config().get("trim_pitch"))
|
|
220
209
|
self.targetCalRoll.setValue(Config().get("trim_roll"))
|
|
221
210
|
|
|
222
|
-
self._helper.inputDeviceReader.alt1_updated.add_callback(self.alt1_updated)
|
|
223
|
-
self._helper.inputDeviceReader.alt2_updated.add_callback(self.alt2_updated)
|
|
224
211
|
self._tf_state = 0
|
|
225
|
-
self._ring_effect = 0
|
|
226
212
|
|
|
227
213
|
# Connect callbacks for input device limiting of roll/pitch/yaw/thrust
|
|
228
214
|
self._helper.inputDeviceReader.limiting_updated.add_callback(self._limiting_updated.emit)
|
|
@@ -446,7 +432,7 @@ class FlightTab(TabToolbox, flight_tab_class):
|
|
|
446
432
|
# To prevent conflicting commands from the controller and the flight panel
|
|
447
433
|
if JoystickReader().available_devices():
|
|
448
434
|
self.commanderBox.setToolTip(
|
|
449
|
-
'
|
|
435
|
+
'Cannot use both a controller and Command Based Flight'
|
|
450
436
|
)
|
|
451
437
|
self.commanderBox.setEnabled(False)
|
|
452
438
|
return
|
|
@@ -516,16 +502,6 @@ class FlightTab(TabToolbox, flight_tab_class):
|
|
|
516
502
|
self._enable_estimators(False)
|
|
517
503
|
|
|
518
504
|
self.logAltHold = None
|
|
519
|
-
self._led_ring_effect.setEnabled(False)
|
|
520
|
-
self._led_ring_effect.clear()
|
|
521
|
-
try:
|
|
522
|
-
self._led_ring_effect.currentIndexChanged.disconnect(
|
|
523
|
-
self._ring_effect_changed)
|
|
524
|
-
except TypeError:
|
|
525
|
-
# Signal was not connected
|
|
526
|
-
pass
|
|
527
|
-
self._led_ring_effect.setCurrentIndex(-1)
|
|
528
|
-
self._led_ring_headlight.setEnabled(False)
|
|
529
505
|
|
|
530
506
|
try:
|
|
531
507
|
self._assist_mode_combo.currentIndexChanged.disconnect(
|
|
@@ -638,19 +614,16 @@ class FlightTab(TabToolbox, flight_tab_class):
|
|
|
638
614
|
else:
|
|
639
615
|
self.setMotorLabelsEnabled(True)
|
|
640
616
|
|
|
641
|
-
def updateArm(self):
|
|
642
|
-
if self._is_flying():
|
|
617
|
+
def updateArm(self, from_controller=False):
|
|
618
|
+
if self._is_flying() and not from_controller:
|
|
643
619
|
self._helper.cf.loc.send_emergency_stop()
|
|
644
|
-
|
|
645
|
-
if self._is_crashed():
|
|
620
|
+
elif self._is_crashed():
|
|
646
621
|
self._helper.cf.platform.send_crash_recovery_request()
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
self.armButton.setStyleSheet("background-color: orange")
|
|
653
|
-
self._helper.cf.platform.send_arming_request(True)
|
|
622
|
+
elif self._is_armed():
|
|
623
|
+
self._helper.cf.platform.send_arming_request(False)
|
|
624
|
+
elif self._can_arm():
|
|
625
|
+
self.armButton.setStyleSheet("background-color: orange")
|
|
626
|
+
self._helper.cf.platform.send_arming_request(True)
|
|
654
627
|
|
|
655
628
|
def flightmodeChange(self, item):
|
|
656
629
|
Config().set("flightmode", str(self.flightModeCombo.itemText(item)))
|
|
@@ -717,81 +690,11 @@ class FlightTab(TabToolbox, flight_tab_class):
|
|
|
717
690
|
else:
|
|
718
691
|
self._helper.cf.param.set_value("flightmode.althold", int(enabled))
|
|
719
692
|
|
|
720
|
-
def alt1_updated(self, state):
|
|
721
|
-
if state:
|
|
722
|
-
new_index = (self._ring_effect+1) % (self._ledring_nbr_effects+1)
|
|
723
|
-
self._helper.cf.param.set_value("ring.effect", str(new_index))
|
|
724
|
-
|
|
725
|
-
def alt2_updated(self, state):
|
|
726
|
-
self._helper.cf.param.set_value("ring.headlightEnable", str(state))
|
|
727
|
-
|
|
728
693
|
def _all_params_updated(self):
|
|
729
|
-
self._ring_populate_dropdown()
|
|
730
694
|
self._populate_assisted_mode_dropdown()
|
|
731
695
|
self._update_flight_commander(True)
|
|
732
696
|
self._update_supervisor_and_arming(True)
|
|
733
697
|
|
|
734
|
-
def _ring_populate_dropdown(self):
|
|
735
|
-
try:
|
|
736
|
-
nbr = int(self._helper.cf.param.values["ring"]["neffect"])
|
|
737
|
-
current = int(self._helper.cf.param.values["ring"]["effect"])
|
|
738
|
-
except KeyError:
|
|
739
|
-
return
|
|
740
|
-
|
|
741
|
-
# Used only in alt1_updated function
|
|
742
|
-
self._ring_effect = current
|
|
743
|
-
self._ledring_nbr_effects = nbr
|
|
744
|
-
|
|
745
|
-
hardcoded_names = {
|
|
746
|
-
0: "Off",
|
|
747
|
-
1: "White spinner",
|
|
748
|
-
2: "Color spinner",
|
|
749
|
-
3: "Tilt effect",
|
|
750
|
-
4: "Brightness effect",
|
|
751
|
-
5: "Color spinner 2",
|
|
752
|
-
6: "Double spinner",
|
|
753
|
-
7: "Solid color effect",
|
|
754
|
-
8: "Factory test",
|
|
755
|
-
9: "Battery status",
|
|
756
|
-
10: "Boat lights",
|
|
757
|
-
11: "Alert",
|
|
758
|
-
12: "Gravity",
|
|
759
|
-
13: "LED tab",
|
|
760
|
-
14: "Color fader",
|
|
761
|
-
15: "Link quality",
|
|
762
|
-
16: "Location server status",
|
|
763
|
-
17: "Sequencer",
|
|
764
|
-
18: "Lighthouse quality",
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
for i in range(nbr + 1):
|
|
768
|
-
name = "{}: ".format(i)
|
|
769
|
-
if i in hardcoded_names:
|
|
770
|
-
name += hardcoded_names[i]
|
|
771
|
-
else:
|
|
772
|
-
name += "N/A"
|
|
773
|
-
self._led_ring_effect.addItem(name, i)
|
|
774
|
-
|
|
775
|
-
self._led_ring_effect.currentIndexChanged.connect(
|
|
776
|
-
self._ring_effect_changed)
|
|
777
|
-
|
|
778
|
-
self._led_ring_effect.setCurrentIndex(current)
|
|
779
|
-
if int(self._helper.cf.param.values["deck"]["bcLedRing"]) == 1:
|
|
780
|
-
self._led_ring_effect.setEnabled(True)
|
|
781
|
-
self._led_ring_headlight.setEnabled(True)
|
|
782
|
-
|
|
783
|
-
def _ring_effect_changed(self, index):
|
|
784
|
-
self._ring_effect = index
|
|
785
|
-
if index > -1:
|
|
786
|
-
i = self._led_ring_effect.itemData(index)
|
|
787
|
-
logger.debug("Changed effect to {}".format(i))
|
|
788
|
-
if i != int(self._helper.cf.param.values["ring"]["effect"]):
|
|
789
|
-
self._helper.cf.param.set_value("ring.effect", str(i))
|
|
790
|
-
|
|
791
|
-
def _ring_effect_updated(self, name, value):
|
|
792
|
-
if self._helper.cf.param.is_updated:
|
|
793
|
-
self._led_ring_effect.setCurrentIndex(int(value))
|
|
794
|
-
|
|
795
698
|
def _populate_assisted_mode_dropdown(self):
|
|
796
699
|
self._assist_mode_combo.addItem("Altitude hold", 0)
|
|
797
700
|
self._assist_mode_combo.addItem("Position hold", 1)
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
# +------+ / /_/ / / /_/ /__/ / / /_/ / / /_/ __/
|
|
8
8
|
# || || /_____/_/\__/\___/_/ \__,_/ /___/\___/
|
|
9
9
|
#
|
|
10
|
-
# Copyright (C) 2011-
|
|
10
|
+
# Copyright (C) 2011-2025 Bitcraze AB
|
|
11
11
|
#
|
|
12
12
|
# Crazyflie Nano Quadcopter Client
|
|
13
13
|
#
|
|
@@ -38,28 +38,47 @@ from PyQt6 import QtWidgets
|
|
|
38
38
|
|
|
39
39
|
import cfclient
|
|
40
40
|
from cfclient.ui.tab_toolbox import TabToolbox
|
|
41
|
-
from cfclient.utils.ui import UiUtils
|
|
42
41
|
|
|
43
42
|
from cflib.crazyflie.mem import MemoryElement
|
|
44
43
|
|
|
45
44
|
__author__ = 'Bitcraze AB'
|
|
46
|
-
__all__ = ['
|
|
45
|
+
__all__ = ['LEDRingTab']
|
|
47
46
|
|
|
48
47
|
logger = logging.getLogger(__name__)
|
|
49
48
|
|
|
50
|
-
|
|
49
|
+
led_ring_tab_class = uic.loadUiType(cfclient.module_path + "/ui/tabs/ledRingTab.ui")[0]
|
|
51
50
|
|
|
52
51
|
|
|
53
|
-
class
|
|
52
|
+
class LEDRingTab(TabToolbox, led_ring_tab_class):
|
|
54
53
|
"""Tab for plotting logging data"""
|
|
55
54
|
|
|
56
55
|
_connected_signal = pyqtSignal(str)
|
|
57
56
|
_disconnected_signal = pyqtSignal(str)
|
|
58
57
|
|
|
59
58
|
def __init__(self, helper):
|
|
60
|
-
super(
|
|
59
|
+
super(LEDRingTab, self).__init__(helper, 'LED Ring')
|
|
61
60
|
self.setupUi(self)
|
|
62
61
|
|
|
62
|
+
# LED-ring effect dropdown and headlight checkbox
|
|
63
|
+
self._ledring_nbr_effects = 0
|
|
64
|
+
|
|
65
|
+
# Connect the headlight checkbox
|
|
66
|
+
self._led_ring_headlight.clicked.connect(
|
|
67
|
+
lambda enabled: self._helper.cf.param.set_value("ring.headlightEnable", int(enabled)))
|
|
68
|
+
|
|
69
|
+
# Update headlight when param changes
|
|
70
|
+
self._helper.cf.param.add_update_callback(
|
|
71
|
+
group="ring", name="headlightEnable",
|
|
72
|
+
cb=lambda name, checked: self._led_ring_headlight.setChecked(bool(int(checked))))
|
|
73
|
+
|
|
74
|
+
# Update LED-ring effect when param changes
|
|
75
|
+
self._helper.cf.param.add_update_callback(
|
|
76
|
+
group="ring", name="effect",
|
|
77
|
+
cb=self._ring_effect_updated)
|
|
78
|
+
|
|
79
|
+
# Populate dropdown when all params are updated
|
|
80
|
+
self._helper.cf.param.all_updated.add_callback(self._ring_populate_dropdown)
|
|
81
|
+
|
|
63
82
|
# Always wrap callbacks from Crazyflie API though QT Signal/Slots
|
|
64
83
|
# to avoid manipulating the UI when rendering it
|
|
65
84
|
self._connected_signal.connect(self._connected)
|
|
@@ -108,8 +127,14 @@ class LEDTab(TabToolbox, led_tab_class):
|
|
|
108
127
|
self._intensity_spin.valueChanged.connect(
|
|
109
128
|
self._intensity_slider.setValue)
|
|
110
129
|
|
|
130
|
+
self._helper.inputDeviceReader.alt1_updated.add_callback(self.alt1_updated)
|
|
131
|
+
self._helper.inputDeviceReader.alt2_updated.add_callback(self.alt2_updated)
|
|
132
|
+
|
|
133
|
+
self._led_ring_effect.setEnabled(False)
|
|
134
|
+
self._led_ring_headlight.setEnabled(False)
|
|
135
|
+
|
|
111
136
|
def _select(self, nbr):
|
|
112
|
-
col = QtGui.QColor()
|
|
137
|
+
col = QtGui.QColor()
|
|
113
138
|
|
|
114
139
|
if self._mem:
|
|
115
140
|
led = self._mem.leds[nbr]
|
|
@@ -118,10 +143,16 @@ class LEDTab(TabToolbox, led_tab_class):
|
|
|
118
143
|
col = QtWidgets.QColorDialog.getColor(col)
|
|
119
144
|
|
|
120
145
|
if col.isValid() and self._mem:
|
|
121
|
-
|
|
122
|
-
self._mem.leds[nbr].set(r=
|
|
123
|
-
|
|
124
|
-
|
|
146
|
+
r, g, b = col.red(), col.green(), col.blue()
|
|
147
|
+
self._mem.leds[nbr].set(r=r, g=g, b=b)
|
|
148
|
+
|
|
149
|
+
brightness = 0.299 * r + 0.587 * g + 0.114 * b
|
|
150
|
+
text_color = "white" if brightness < 128 else "black"
|
|
151
|
+
|
|
152
|
+
self.sender().setStyleSheet(
|
|
153
|
+
f"background-color: rgb({r}, {g}, {b}); color: {text_color};"
|
|
154
|
+
)
|
|
155
|
+
|
|
125
156
|
self._write_led_output()
|
|
126
157
|
|
|
127
158
|
def _intensity_change(self, value):
|
|
@@ -149,10 +180,13 @@ class LEDTab(TabToolbox, led_tab_class):
|
|
|
149
180
|
if self._mem:
|
|
150
181
|
for btn in self._btns:
|
|
151
182
|
btn.setEnabled(True)
|
|
152
|
-
btn.setStyleSheet("background-color: black")
|
|
183
|
+
btn.setStyleSheet("background-color: black; color: white")
|
|
153
184
|
self._intensity_slider.setEnabled(True)
|
|
154
185
|
self._intensity_spin.setEnabled(True)
|
|
155
186
|
|
|
187
|
+
self._led_ring_effect.setEnabled(True)
|
|
188
|
+
self._led_ring_headlight.setEnabled(True)
|
|
189
|
+
|
|
156
190
|
def _disconnected(self, link_uri):
|
|
157
191
|
"""Callback for when the Crazyflie has been disconnected"""
|
|
158
192
|
for btn in self._btns:
|
|
@@ -161,3 +195,83 @@ class LEDTab(TabToolbox, led_tab_class):
|
|
|
161
195
|
self._intensity_slider.setEnabled(False)
|
|
162
196
|
self._intensity_spin.setEnabled(False)
|
|
163
197
|
self._intensity_slider.setValue(100)
|
|
198
|
+
|
|
199
|
+
self._led_ring_effect.setEnabled(False)
|
|
200
|
+
self._led_ring_headlight.setEnabled(False)
|
|
201
|
+
|
|
202
|
+
def _ring_populate_dropdown(self):
|
|
203
|
+
try:
|
|
204
|
+
nbr = int(self._helper.cf.param.values["ring"]["neffect"])
|
|
205
|
+
current = int(self._helper.cf.param.values["ring"]["effect"])
|
|
206
|
+
except KeyError:
|
|
207
|
+
return
|
|
208
|
+
|
|
209
|
+
self._ring_effect = current
|
|
210
|
+
self._ledring_nbr_effects = nbr
|
|
211
|
+
|
|
212
|
+
hardcoded_names = {
|
|
213
|
+
0: "Off",
|
|
214
|
+
1: "White spinner",
|
|
215
|
+
2: "Color spinner",
|
|
216
|
+
3: "Tilt effect",
|
|
217
|
+
4: "Brightness effect",
|
|
218
|
+
5: "Color spinner 2",
|
|
219
|
+
6: "Double spinner",
|
|
220
|
+
7: "Solid color effect",
|
|
221
|
+
8: "Factory test",
|
|
222
|
+
9: "Battery status",
|
|
223
|
+
10: "Boat lights",
|
|
224
|
+
11: "Alert",
|
|
225
|
+
12: "Gravity",
|
|
226
|
+
13: "LED tab",
|
|
227
|
+
14: "Color fader",
|
|
228
|
+
15: "Link quality",
|
|
229
|
+
16: "Location server status",
|
|
230
|
+
17: "Sequencer",
|
|
231
|
+
18: "Lighthouse quality",
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
self._led_ring_effect.clear()
|
|
235
|
+
for i in range(nbr + 1):
|
|
236
|
+
name = "{}: ".format(i)
|
|
237
|
+
name += hardcoded_names.get(i, "N/A")
|
|
238
|
+
self._led_ring_effect.addItem(name, i)
|
|
239
|
+
|
|
240
|
+
self._led_ring_effect.currentIndexChanged.connect(self._ring_effect_changed)
|
|
241
|
+
self._led_ring_effect.setCurrentIndex(current)
|
|
242
|
+
self._led_ring_effect.setEnabled(int(self._helper.cf.param.values["deck"]["bcLedRing"]) == 1)
|
|
243
|
+
self._led_ring_headlight.setEnabled(int(self._helper.cf.param.values["deck"]["bcLedRing"]) == 1)
|
|
244
|
+
|
|
245
|
+
try:
|
|
246
|
+
self._helper.cf.param.set_value("ring.effect", "13")
|
|
247
|
+
self._led_ring_effect.setCurrentIndex(13)
|
|
248
|
+
self._ring_effect = 13
|
|
249
|
+
logger.info("Initialized LED ring to 'LED tab' mode (effect 13).")
|
|
250
|
+
except Exception as e:
|
|
251
|
+
logger.warning(f"Could not set LED tab effect on connect: {e}")
|
|
252
|
+
|
|
253
|
+
def _ring_effect_changed(self, index):
|
|
254
|
+
self._ring_effect = index
|
|
255
|
+
if index > -1:
|
|
256
|
+
i = self._led_ring_effect.itemData(index)
|
|
257
|
+
if i != int(self._helper.cf.param.values["ring"]["effect"]):
|
|
258
|
+
self._helper.cf.param.set_value("ring.effect", str(i))
|
|
259
|
+
|
|
260
|
+
if i == 13:
|
|
261
|
+
self._intensity_slider.setEnabled(True)
|
|
262
|
+
self._intensity_spin.setEnabled(True)
|
|
263
|
+
else:
|
|
264
|
+
self._intensity_slider.setEnabled(False)
|
|
265
|
+
self._intensity_spin.setEnabled(False)
|
|
266
|
+
|
|
267
|
+
def _ring_effect_updated(self, name, value):
|
|
268
|
+
if self._helper.cf.param.is_updated:
|
|
269
|
+
self._led_ring_effect.setCurrentIndex(int(value))
|
|
270
|
+
|
|
271
|
+
def alt1_updated(self, state):
|
|
272
|
+
if state:
|
|
273
|
+
new_index = (self._ring_effect+1) % (self._ledring_nbr_effects+1)
|
|
274
|
+
self._helper.cf.param.set_value("ring.effect", str(new_index))
|
|
275
|
+
|
|
276
|
+
def alt2_updated(self, state):
|
|
277
|
+
self._helper.cf.param.set_value("ring.headlightEnable", str(state))
|
cfclient/ui/tabs/LogTab.py
CHANGED
|
@@ -99,7 +99,7 @@ class LogTab(TabToolbox, param_tab_class):
|
|
|
99
99
|
log_groups = cfclient.log_param_doc['logs'][group]
|
|
100
100
|
log_variable = log_groups['variables'][param]
|
|
101
101
|
item.setData(4, Qt.ItemDataRole.DisplayRole, log_variable['short_desc'])
|
|
102
|
-
except:
|
|
102
|
+
except (KeyError, TypeError, AttributeError):
|
|
103
103
|
pass
|
|
104
104
|
|
|
105
105
|
groupItem.addChild(item)
|
|
@@ -112,5 +112,5 @@ class LogTab(TabToolbox, param_tab_class):
|
|
|
112
112
|
label = QtWidgets.QLabel(log_groups['desc'])
|
|
113
113
|
label.setWordWrap(True)
|
|
114
114
|
self.logTree.setItemWidget(groupItem, 4, label)
|
|
115
|
-
except:
|
|
115
|
+
except (KeyError, TypeError, AttributeError):
|
|
116
116
|
pass
|
cfclient/ui/tabs/ParamTab.py
CHANGED
|
@@ -78,6 +78,7 @@ class ParamChildItem(object):
|
|
|
78
78
|
self._cf = crazyflie
|
|
79
79
|
self.is_updating = True
|
|
80
80
|
self.state = None
|
|
81
|
+
self.stored_value = ""
|
|
81
82
|
|
|
82
83
|
def updated(self, name, value):
|
|
83
84
|
"""Callback from the param layer when a parameter has been updated"""
|
|
@@ -113,7 +114,7 @@ class ParamBlockModel(QAbstractItemModel):
|
|
|
113
114
|
"""Create the empty model"""
|
|
114
115
|
super(ParamBlockModel, self).__init__(parent)
|
|
115
116
|
self._nodes = []
|
|
116
|
-
self._column_headers = ['Name', 'Type', 'Access', 'Persistent', 'Value']
|
|
117
|
+
self._column_headers = ['Name', 'Type', 'Access', 'Persistent', 'Value', 'Stored Value']
|
|
117
118
|
self._red_brush = QBrush(QColor("red"))
|
|
118
119
|
self._enabled = False
|
|
119
120
|
self._mainUI = mainUI
|
|
@@ -227,12 +228,68 @@ class ParamBlockModel(QAbstractItemModel):
|
|
|
227
228
|
return 'Yes' if node.persistent else 'No'
|
|
228
229
|
if index.column() == 4:
|
|
229
230
|
return node.value
|
|
231
|
+
if index.column() == 5:
|
|
232
|
+
return node.stored_value
|
|
230
233
|
elif (role == Qt.ItemDataRole.BackgroundRole and index.column() == 4 and
|
|
231
234
|
node.is_updating):
|
|
232
235
|
return self._red_brush
|
|
233
236
|
|
|
234
237
|
return None
|
|
235
238
|
|
|
239
|
+
def update_stored_value_and_refresh(self, node):
|
|
240
|
+
"""
|
|
241
|
+
Fetch the persistent stored value for this node, store it in node.stored_value,
|
|
242
|
+
and refresh the Stored Value column in the view.
|
|
243
|
+
"""
|
|
244
|
+
if not node.persistent:
|
|
245
|
+
node.stored_value = ''
|
|
246
|
+
return
|
|
247
|
+
|
|
248
|
+
# Fetch stored value synchronously
|
|
249
|
+
complete_name = f"{node.parent.name}.{node.name}"
|
|
250
|
+
from threading import Event
|
|
251
|
+
wait_event = Event()
|
|
252
|
+
state_value = None
|
|
253
|
+
|
|
254
|
+
def cb(_, state):
|
|
255
|
+
nonlocal state_value
|
|
256
|
+
state_value = state
|
|
257
|
+
wait_event.set()
|
|
258
|
+
|
|
259
|
+
self._mainUI.cf.param.persistent_get_state(complete_name, cb)
|
|
260
|
+
wait_event.wait(timeout=0.2)
|
|
261
|
+
|
|
262
|
+
if state_value and state_value.is_stored:
|
|
263
|
+
node.stored_value = round_if_float(state_value.stored_value)
|
|
264
|
+
else:
|
|
265
|
+
node.stored_value = ''
|
|
266
|
+
|
|
267
|
+
# Refresh only column 5 (Stored Value)
|
|
268
|
+
source_row = node.parent.children.index(node)
|
|
269
|
+
parent_row = self._nodes.index(node.parent)
|
|
270
|
+
col = 5
|
|
271
|
+
row_index = self.index(source_row, col, self.createIndex(parent_row, 0, node.parent))
|
|
272
|
+
proxy_index = self.proxy.mapFromSource(row_index)
|
|
273
|
+
self.proxy.dataChanged.emit(proxy_index, proxy_index)
|
|
274
|
+
|
|
275
|
+
def update_stored_value_from_state(self, node, state: 'PersistentParamState'):
|
|
276
|
+
"""
|
|
277
|
+
Update the node's stored_value from a PersistentParamState object
|
|
278
|
+
and refresh the Stored Value column.
|
|
279
|
+
"""
|
|
280
|
+
if state and state.is_stored:
|
|
281
|
+
node.stored_value = round_if_float(state.stored_value)
|
|
282
|
+
else:
|
|
283
|
+
node.stored_value = ''
|
|
284
|
+
|
|
285
|
+
# Refresh only column 5 (Stored Value)
|
|
286
|
+
source_row = node.parent.children.index(node)
|
|
287
|
+
parent_row = self._nodes.index(node.parent)
|
|
288
|
+
col = 5 # Stored Value column
|
|
289
|
+
row_index = self.index(source_row, col, self.createIndex(parent_row, 0, node.parent))
|
|
290
|
+
proxy_index = self.proxy.mapFromSource(row_index)
|
|
291
|
+
self.proxy.dataChanged.emit(proxy_index, proxy_index)
|
|
292
|
+
|
|
236
293
|
def flags(self, index):
|
|
237
294
|
"""Re-implemented function for getting the flags for a certain index"""
|
|
238
295
|
flag = super(ParamBlockModel, self).flags(index)
|
|
@@ -336,7 +393,17 @@ class ParamTab(TabToolbox, param_tab_class):
|
|
|
336
393
|
def success_cb(name, success):
|
|
337
394
|
print(f'store {success}!')
|
|
338
395
|
if success:
|
|
339
|
-
|
|
396
|
+
# Fetch the persistent state after store
|
|
397
|
+
def state_cb(_, state):
|
|
398
|
+
for group in self._model._nodes:
|
|
399
|
+
for node in group.children:
|
|
400
|
+
if f"{group.name}.{node.name}" == name:
|
|
401
|
+
# Update stored value in model
|
|
402
|
+
self._model.update_stored_value_from_state(node, state)
|
|
403
|
+
# Update the button text immediately
|
|
404
|
+
self._persistent_state_signal.emit(state)
|
|
405
|
+
break
|
|
406
|
+
self.cf.param.persistent_get_state(name, state_cb)
|
|
340
407
|
|
|
341
408
|
complete = self.paramDetailsLabel.text()
|
|
342
409
|
if self.persistentButton.text() == 'Clear':
|
|
@@ -398,7 +465,7 @@ class ParamTab(TabToolbox, param_tab_class):
|
|
|
398
465
|
|
|
399
466
|
self.paramDetailsDescription.setWordWrap(True)
|
|
400
467
|
self.paramDetailsDescription.setText(desc.replace('\n', ''))
|
|
401
|
-
except:
|
|
468
|
+
except (KeyError, TypeError, AttributeError):
|
|
402
469
|
self.paramDetailsDescription.setText('')
|
|
403
470
|
|
|
404
471
|
complete = f'{group}.{param}'
|
|
@@ -417,6 +484,9 @@ class ParamTab(TabToolbox, param_tab_class):
|
|
|
417
484
|
|
|
418
485
|
if elem.is_persistent():
|
|
419
486
|
self.cf.param.persistent_get_state(complete, lambda _, state: self._persistent_state_signal.emit(state))
|
|
487
|
+
source_index = self.proxyModel.mapToSource(indexes[0])
|
|
488
|
+
node = source_index.internalPointer()
|
|
489
|
+
self._model.update_stored_value_and_refresh(node)
|
|
420
490
|
|
|
421
491
|
def _update_param_io_buttons(self):
|
|
422
492
|
enabled = self._is_connected
|
|
@@ -536,7 +606,7 @@ class ParamTab(TabToolbox, param_tab_class):
|
|
|
536
606
|
def _clear_stored_persistent_params_button_clicked(self):
|
|
537
607
|
dlg = QMessageBox(self)
|
|
538
608
|
dlg.setWindowTitle("Clear Stored Parameters Confirmation")
|
|
539
|
-
dlg.setText("Are you sure you want to clear your stored persistent
|
|
609
|
+
dlg.setText("Are you sure you want to clear your stored persistent parameters?")
|
|
540
610
|
dlg.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
|
|
541
611
|
button = dlg.exec()
|
|
542
612
|
|
|
@@ -544,6 +614,22 @@ class ParamTab(TabToolbox, param_tab_class):
|
|
|
544
614
|
stored_persistent_params = self._get_all_stored_persistent_param_names()
|
|
545
615
|
for complete_name in stored_persistent_params:
|
|
546
616
|
self._clear_persistent_parameter(complete_name)
|
|
617
|
+
for group in self._model._nodes:
|
|
618
|
+
for node in group.children:
|
|
619
|
+
if f"{group.name}.{node.name}" == complete_name:
|
|
620
|
+
node.stored_value = ''
|
|
621
|
+
# Emit a targeted dataChanged for column 5 only
|
|
622
|
+
source_row = group.children.index(node)
|
|
623
|
+
parent_row = self._model._nodes.index(group)
|
|
624
|
+
col = 5
|
|
625
|
+
|
|
626
|
+
index = self._model.index(
|
|
627
|
+
source_row, col,
|
|
628
|
+
self._model.index(parent_row, 0, QModelIndex())
|
|
629
|
+
)
|
|
630
|
+
proxy_index = self._model.proxy.mapFromSource(index)
|
|
631
|
+
self._model.proxy.dataChanged.emit(proxy_index, proxy_index)
|
|
632
|
+
break
|
|
547
633
|
|
|
548
634
|
def _connected(self, link_uri):
|
|
549
635
|
self._model.reset()
|
cfclient/ui/tabs/__init__.py
CHANGED
|
@@ -34,7 +34,7 @@ from .CrtpSharkToolbox import CrtpSharkToolbox
|
|
|
34
34
|
# from .ExampleTab import ExampleTab
|
|
35
35
|
from .FlightTab import FlightTab
|
|
36
36
|
# from .GpsTab import GpsTab
|
|
37
|
-
from .
|
|
37
|
+
from .LEDRingTab import LEDRingTab
|
|
38
38
|
from .LogBlockTab import LogBlockTab
|
|
39
39
|
from .LogTab import LogTab
|
|
40
40
|
from .ParamTab import ParamTab
|
|
@@ -43,6 +43,7 @@ from .locopositioning_tab import LocoPositioningTab
|
|
|
43
43
|
from .LogClientTab import LogClientTab
|
|
44
44
|
from .lighthouse_tab import LighthouseTab
|
|
45
45
|
from .TuningTab import TuningTab
|
|
46
|
+
from .ColorLEDTab import ColorLEDTab
|
|
46
47
|
|
|
47
48
|
__author__ = 'Bitcraze AB'
|
|
48
49
|
__all__ = []
|
|
@@ -52,7 +53,8 @@ available = [
|
|
|
52
53
|
# ExampleTab,
|
|
53
54
|
FlightTab,
|
|
54
55
|
# GpsTab,
|
|
55
|
-
|
|
56
|
+
LEDRingTab,
|
|
57
|
+
ColorLEDTab,
|
|
56
58
|
LogBlockTab,
|
|
57
59
|
LogTab,
|
|
58
60
|
ParamTab,
|