cfclient 2017.4__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.
Files changed (140) hide show
  1. cfclient/__init__.py +16 -11
  2. cfclient/configs/config.json +4 -3
  3. cfclient/configs/input/Generic_OS_X.json +1 -0
  4. cfclient/configs/input/Joystick.json +1 -0
  5. cfclient/configs/input/PS3_Mode_1.json +1 -0
  6. cfclient/configs/input/PS3_Mode_2.json +1 -0
  7. cfclient/configs/input/PS3_Mode_3.json +1 -0
  8. cfclient/configs/input/PS4_Mode_1.json +1 -0
  9. cfclient/configs/input/PS4_Mode_2.json +1 -0
  10. cfclient/configs/input/PS4_shoulder_btns_yaw.json +1 -0
  11. cfclient/configs/input/xbox360_mode1.json +1 -0
  12. cfclient/configs/log/PID_tuning/Attitude.json +46 -0
  13. cfclient/configs/log/PID_tuning/Attitude_rate.json +46 -0
  14. cfclient/configs/log/PID_tuning/Position.json +46 -0
  15. cfclient/configs/log/PID_tuning/Velocity.json +46 -0
  16. cfclient/configs/log/PID_tuning_components/Pitch.json +22 -0
  17. cfclient/configs/log/PID_tuning_components/Pitch_rate.json +22 -0
  18. cfclient/configs/log/PID_tuning_components/Position_x.json +22 -0
  19. cfclient/configs/log/PID_tuning_components/Position_y.json +22 -0
  20. cfclient/configs/log/PID_tuning_components/Position_z.json +22 -0
  21. cfclient/configs/log/PID_tuning_components/Roll.json +22 -0
  22. cfclient/configs/log/PID_tuning_components/Roll_rate.json +22 -0
  23. cfclient/configs/log/PID_tuning_components/Velocity_x.json +22 -0
  24. cfclient/configs/log/PID_tuning_components/Velocity_y.json +22 -0
  25. cfclient/configs/log/PID_tuning_components/Velocity_z.json +22 -0
  26. cfclient/configs/log/PID_tuning_components/Yaw.json +22 -0
  27. cfclient/configs/log/PID_tuning_components/Yaw_rate.json +22 -0
  28. cfclient/gui.py +44 -9
  29. cfclient/headless.py +3 -12
  30. cfclient/resources/log_param_doc.json +1 -0
  31. cfclient/ui/connectivity_manager.py +198 -0
  32. cfclient/ui/dialogs/about.py +53 -36
  33. cfclient/ui/dialogs/about.ui +23 -3
  34. cfclient/ui/dialogs/anchor_position_dialog.py +252 -0
  35. cfclient/ui/dialogs/anchor_position_dialog.ui +138 -0
  36. cfclient/ui/dialogs/basestation_mode_dialog.py +185 -0
  37. cfclient/ui/dialogs/basestation_mode_dialog.ui +186 -0
  38. cfclient/ui/dialogs/bootloader.py +448 -85
  39. cfclient/ui/dialogs/bootloader.ui +387 -134
  40. cfclient/ui/dialogs/cf2config.py +4 -4
  41. cfclient/ui/dialogs/cf2config.ui +3 -4
  42. cfclient/ui/dialogs/inputconfigdialogue.py +24 -19
  43. cfclient/ui/dialogs/inputconfigdialogue.ui +53 -30
  44. cfclient/ui/dialogs/lighthouse_bs_geometry_dialog.py +220 -0
  45. cfclient/ui/dialogs/lighthouse_bs_geometry_dialog.ui +110 -0
  46. cfclient/ui/dialogs/lighthouse_system_type_dialog.py +93 -0
  47. cfclient/ui/dialogs/lighthouse_system_type_dialog.ui +121 -0
  48. cfclient/ui/dialogs/logconfigdialogue.py +401 -101
  49. cfclient/ui/dialogs/logconfigdialogue.ui +117 -72
  50. cfclient/ui/icons/bl.webp +0 -0
  51. cfclient/ui/icons/bolt.webp +0 -0
  52. cfclient/ui/icons/cf21.webp +0 -0
  53. cfclient/ui/icons/checkmark_black.png +0 -0
  54. cfclient/ui/icons/checkmark_white.png +0 -0
  55. cfclient/ui/icons/create.png +0 -0
  56. cfclient/ui/icons/delete.png +0 -0
  57. cfclient/ui/icons/flapper.webp +0 -0
  58. cfclient/ui/icons/tag.webp +0 -0
  59. cfclient/ui/main.py +328 -258
  60. cfclient/ui/main.ui +184 -80
  61. cfclient/ui/pluginhelper.py +7 -1
  62. cfclient/ui/pose_logger.py +116 -0
  63. cfclient/ui/tab_toolbox.py +208 -0
  64. cfclient/ui/tabs/ColorLEDTab.py +752 -0
  65. cfclient/ui/tabs/ConsoleTab.py +48 -13
  66. cfclient/ui/{toolboxes → tabs}/CrtpSharkToolbox.py +19 -34
  67. cfclient/ui/tabs/ExampleTab.py +9 -16
  68. cfclient/ui/tabs/FlightTab.py +437 -325
  69. cfclient/ui/tabs/GpsTab.py +14 -20
  70. cfclient/ui/tabs/LEDRingTab.py +277 -0
  71. cfclient/ui/tabs/LogBlockDebugTab.py +20 -27
  72. cfclient/ui/tabs/LogBlockTab.py +35 -35
  73. cfclient/ui/tabs/LogClientTab.py +85 -0
  74. cfclient/ui/tabs/LogTab.py +50 -27
  75. cfclient/ui/tabs/ParamTab.py +443 -57
  76. cfclient/ui/tabs/PlotTab.py +23 -25
  77. cfclient/ui/tabs/TuningTab.py +292 -0
  78. cfclient/ui/tabs/__init__.py +12 -2
  79. cfclient/ui/tabs/colorLEDTab.ui +624 -0
  80. cfclient/ui/tabs/consoleTab.ui +46 -0
  81. cfclient/ui/tabs/flightActionContainer.ui +103 -0
  82. cfclient/ui/tabs/flightTab.ui +724 -237
  83. cfclient/ui/tabs/{ledTab.ui → ledRingTab.ui} +63 -46
  84. cfclient/ui/tabs/lighthouse_tab.py +714 -0
  85. cfclient/ui/tabs/lighthouse_tab.ui +430 -0
  86. cfclient/ui/tabs/locopositioning_tab.py +606 -389
  87. cfclient/ui/tabs/locopositioning_tab.ui +370 -253
  88. cfclient/ui/tabs/logClientTab.ui +52 -0
  89. cfclient/ui/tabs/logTab.ui +1 -1
  90. cfclient/ui/tabs/paramTab.ui +204 -3
  91. cfclient/ui/tabs/tuningTab.ui +773 -0
  92. cfclient/ui/widgets/ai.py +37 -39
  93. cfclient/ui/widgets/hexspinbox.py +16 -10
  94. cfclient/ui/widgets/plotter.ui +39 -47
  95. cfclient/ui/widgets/plotwidget.py +57 -22
  96. cfclient/ui/widgets/super_slider.py +112 -0
  97. cfclient/ui/wizards/__init__.py +0 -0
  98. cfclient/ui/wizards/bslh_1.png +0 -0
  99. cfclient/ui/wizards/bslh_2.png +0 -0
  100. cfclient/ui/wizards/bslh_3.png +0 -0
  101. cfclient/ui/wizards/bslh_4.png +0 -0
  102. cfclient/ui/wizards/bslh_5.png +0 -0
  103. cfclient/ui/wizards/lighthouse_geo_bs_estimation_wizard.py +465 -0
  104. cfclient/utils/config_manager.py +5 -4
  105. cfclient/utils/input/__init__.py +77 -19
  106. cfclient/utils/input/inputinterfaces/wiimote.py +2 -2
  107. cfclient/utils/input/inputreaderinterface.py +17 -7
  108. cfclient/utils/input/inputreaders/__init__.py +17 -0
  109. cfclient/utils/logconfigreader.py +245 -25
  110. cfclient/utils/logdatawriter.py +3 -1
  111. cfclient/utils/periodictimer.py +1 -1
  112. cfclient/utils/ui.py +336 -0
  113. cfclient/utils/zmq_led_driver.py +5 -0
  114. cfclient/utils/zmq_param.py +6 -0
  115. cfclient/version.py +34 -1
  116. cfclient-2025.12.1.dist-info/METADATA +70 -0
  117. cfclient-2025.12.1.dist-info/RECORD +152 -0
  118. {cfclient-2017.4.dist-info → cfclient-2025.12.1.dist-info}/WHEEL +1 -1
  119. {cfclient-2017.4.dist-info → cfclient-2025.12.1.dist-info}/entry_points.txt +0 -1
  120. cfclient-2025.12.1.dist-info/licenses/LICENSE.txt +350 -0
  121. {cfclient-2017.4.dist-info → cfclient-2025.12.1.dist-info}/top_level.txt +1 -0
  122. cfconfig/Makefile +51 -0
  123. cfconfig/configblock.py +111 -0
  124. cfloader/__init__.py +41 -55
  125. cfzmq/__init__.py +22 -14
  126. cfclient/ui/dialogs/cf1config.py +0 -265
  127. cfclient/ui/dialogs/cf1config.ui +0 -260
  128. cfclient/ui/tab.py +0 -96
  129. cfclient/ui/tabs/LEDTab.py +0 -169
  130. cfclient/ui/toolboxes/ConsoleToolbox.py +0 -69
  131. cfclient/ui/toolboxes/DebugDriverToolbox.py +0 -107
  132. cfclient/ui/toolboxes/__init__.py +0 -45
  133. cfclient/ui/toolboxes/consoleToolbox.ui +0 -62
  134. cfclient/ui/toolboxes/debugDriverToolbox.ui +0 -86
  135. cfclient-2017.4.dist-info/DESCRIPTION.rst +0 -3
  136. cfclient-2017.4.dist-info/METADATA +0 -22
  137. cfclient-2017.4.dist-info/RECORD +0 -104
  138. cfclient-2017.4.dist-info/metadata.json +0 -1
  139. /cfclient/{icon-256.png → ui/icons/icon-256.png} +0 -0
  140. /cfclient/ui/{toolboxes → tabs}/crtpSharkToolbox.ui +0 -0
@@ -0,0 +1,465 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # || ____ _ __
4
+ # +------+ / __ )(_) /_______________ _____ ___
5
+ # | 0xBC | / __ / / __/ ___/ ___/ __ `/_ / / _ \
6
+ # +------+ / /_/ / / /_/ /__/ / / /_/ / / /_/ __/
7
+ # || || /_____/_/\__/\___/_/ \__,_/ /___/\___/
8
+ #
9
+ # Copyright (C) 2022-2023 Bitcraze AB
10
+ #
11
+ # This program is free software; you can redistribute it and/or
12
+ # modify it under the terms of the GNU General Public License
13
+ # as published by the Free Software Foundation; either version 2
14
+ # of the License, or (at your option) any later version.
15
+ #
16
+ # This program is distributed in the hope that it will be useful,
17
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
18
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19
+ # GNU General Public License for more details.
20
+ # You should have received a copy of the GNU General Public License
21
+ # along with this program; if not, write to the Free Software
22
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
23
+ # MA 02110-1301, USA.
24
+
25
+ """
26
+ Wizard to estimate the geometry of the lighthouse base stations.
27
+ Used in the lighthouse tab from the manage geometry dialog
28
+ """
29
+
30
+ from __future__ import annotations
31
+
32
+ import cfclient
33
+
34
+ from cflib.crazyflie import Crazyflie
35
+ from cflib.crazyflie.mem.lighthouse_memory import LighthouseBsGeometry
36
+ from cflib.localization.lighthouse_sweep_angle_reader import LighthouseSweepAngleAverageReader
37
+ from cflib.localization.lighthouse_sweep_angle_reader import LighthouseSweepAngleReader
38
+ from cflib.localization.lighthouse_bs_vector import LighthouseBsVectors
39
+ from cflib.localization.lighthouse_initial_estimator import LighthouseInitialEstimator
40
+ from cflib.localization.lighthouse_sample_matcher import LighthouseSampleMatcher
41
+ from cflib.localization.lighthouse_system_aligner import LighthouseSystemAligner
42
+ from cflib.localization.lighthouse_geometry_solver import LighthouseGeometrySolver
43
+ from cflib.localization.lighthouse_system_scaler import LighthouseSystemScaler
44
+ from cflib.localization.lighthouse_types import Pose, LhDeck4SensorPositions, LhMeasurement, LhCfPoseSample
45
+
46
+ from PyQt6 import QtCore, QtWidgets, QtGui
47
+ import time
48
+
49
+
50
+ REFERENCE_DIST = 1.0
51
+ ITERATION_MAX_NR = 2
52
+ DEFAULT_RECORD_TIME = 20
53
+ TIMEOUT_TIME = 2000
54
+ STRING_PAD_TOTAL = 6
55
+ WINDOW_STARTING_WIDTH = 780
56
+ WINDOW_STARTING_HEIGHT = 720
57
+ SPACER_LABEL_HEIGHT = 27
58
+ PICTURE_WIDTH = 640
59
+
60
+
61
+ class LighthouseBasestationGeometryWizard(QtWidgets.QWizard):
62
+ def __init__(self, cf, ready_cb, parent=None, *args):
63
+ super(LighthouseBasestationGeometryWizard, self).__init__(parent)
64
+ self.cf = cf
65
+ self.ready_cb = ready_cb
66
+ self.wizard_opened_first_time = True
67
+ self.reset()
68
+
69
+ self.button(QtWidgets.QWizard.WizardButton.FinishButton).clicked.connect(self._finish_button_clicked_callback)
70
+
71
+ def _finish_button_clicked_callback(self):
72
+ self.ready_cb(self.get_geometry_page.get_geometry())
73
+
74
+ def reset(self):
75
+ self.setWindowFlags(self.windowFlags() & ~QtCore.Qt.WindowType.WindowContextHelpButtonHint)
76
+ self.setWindowFlags(self.windowFlags() & ~QtCore.Qt.WindowType.WindowCloseButtonHint)
77
+
78
+ if not self.wizard_opened_first_time:
79
+ self.removePage(0)
80
+ self.removePage(1)
81
+ self.removePage(2)
82
+ self.removePage(3)
83
+ self.removePage(4)
84
+ del self.get_origin_page, self.get_xaxis_page, self.get_xyplane_page
85
+ del self.get_xyzspace_page, self.get_geometry_page
86
+ else:
87
+ self.wizard_opened_first_time = False
88
+
89
+ self.get_origin_page = RecordOriginSamplePage(self.cf, self)
90
+ self.get_xaxis_page = RecordXAxisSamplePage(self.cf, self)
91
+ self.get_xyplane_page = RecordXYPlaneSamplesPage(self.cf, self)
92
+ self.get_xyzspace_page = RecordXYZSpaceSamplesPage(self.cf, self)
93
+ self.get_geometry_page = EstimateBSGeometryPage(
94
+ self.cf, self.get_origin_page, self.get_xaxis_page, self.get_xyplane_page, self.get_xyzspace_page, self)
95
+
96
+ self.addPage(self.get_origin_page)
97
+ self.addPage(self.get_xaxis_page)
98
+ self.addPage(self.get_xyplane_page)
99
+ self.addPage(self.get_xyzspace_page)
100
+ self.addPage(self.get_geometry_page)
101
+
102
+ self.setWindowTitle("Lighthouse Base Station Geometry Wizard")
103
+ self.resize(WINDOW_STARTING_WIDTH, WINDOW_STARTING_HEIGHT)
104
+
105
+
106
+ class LighthouseBasestationGeometryWizardBasePage(QtWidgets.QWizardPage):
107
+
108
+ def __init__(self, cf: Crazyflie, show_add_measurements=False, parent=None):
109
+ super(LighthouseBasestationGeometryWizardBasePage, self).__init__(parent)
110
+ self.show_add_measurements = show_add_measurements
111
+ self.cf = cf
112
+ self.layout = QtWidgets.QVBoxLayout()
113
+
114
+ self.explanation_picture = QtWidgets.QLabel()
115
+ self.explanation_picture.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
116
+ self.layout.addWidget(self.explanation_picture)
117
+
118
+ self.explanation_text = QtWidgets.QLabel()
119
+ self.explanation_text.setText(' ')
120
+ self.explanation_text.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
121
+ self.layout.addWidget(self.explanation_text)
122
+
123
+ self.layout.addStretch()
124
+
125
+ self.extra_layout_field()
126
+
127
+ self.status_text = QtWidgets.QLabel()
128
+ self.status_text.setFont(QtGui.QFont('Courier New', 10))
129
+ self.status_text.setText(self.str_pad(''))
130
+ self.status_text.setFrameStyle(QtWidgets.QFrame.Shape.Panel | QtWidgets.QFrame.Shadow.Plain)
131
+ self.layout.addWidget(self.status_text)
132
+
133
+ self.start_action_button = QtWidgets.QPushButton("Start Measurement")
134
+ self.start_action_button.clicked.connect(self._action_btn_clicked)
135
+ action_button_h_box = QtWidgets.QHBoxLayout()
136
+ action_button_h_box.addStretch()
137
+ action_button_h_box.addWidget(self.start_action_button)
138
+ action_button_h_box.addStretch()
139
+ self.layout.addLayout(action_button_h_box)
140
+ self.setLayout(self.layout)
141
+ self.is_done = False
142
+ self.too_few_bs = False
143
+ self.timeout_timer = QtCore.QTimer()
144
+ self.timeout_timer.timeout.connect(self._timeout_cb)
145
+ self.reader = LighthouseSweepAngleAverageReader(self.cf, self._ready_cb)
146
+ self.recorded_angle_result = None
147
+ self.recorded_angles_result: list[LhCfPoseSample] = []
148
+
149
+ def isComplete(self):
150
+ return self.is_done and (self.too_few_bs is not True)
151
+
152
+ def extra_layout_field(self):
153
+ self.spacer = QtWidgets.QLabel()
154
+ self.spacer.setText(' ')
155
+ self.spacer.setFixedSize(50, SPACER_LABEL_HEIGHT)
156
+ self.layout.addWidget(self.spacer)
157
+
158
+ def _action_btn_clicked(self):
159
+ self.is_done = False
160
+ self.reader.start_angle_collection()
161
+ self.timeout_timer.start(TIMEOUT_TIME)
162
+ self.status_text.setText(self.str_pad('Collecting sweep angles...'))
163
+ self.start_action_button.setDisabled(True)
164
+
165
+ def _timeout_cb(self):
166
+ if self.is_done is not True:
167
+ self.status_text.setText(self.str_pad('No sweep angles recorded! \n' +
168
+ 'Make sure that the lighthouse base stations are turned on!'))
169
+ self.reader.stop_angle_collection()
170
+ self.start_action_button.setText("Restart Measurement")
171
+ self.start_action_button.setDisabled(False)
172
+ elif self.too_few_bs:
173
+ self.timeout_timer.stop()
174
+
175
+ def _ready_cb(self, averages):
176
+ print(self.show_add_measurements)
177
+ recorded_angles = averages
178
+ angles_calibrated = {}
179
+ for bs_id, data in recorded_angles.items():
180
+ angles_calibrated[bs_id] = data[1]
181
+ self.recorded_angle_result = LhCfPoseSample(angles_calibrated=angles_calibrated)
182
+ self.visible_basestations = ', '.join(map(lambda x: str(x + 1), recorded_angles.keys()))
183
+ amount_of_basestations = len(recorded_angles.keys())
184
+
185
+ if amount_of_basestations < 2:
186
+ self.status_text.setText(self.str_pad('Recording Done!' +
187
+ f' Visible Base stations: {self.visible_basestations}\n' +
188
+ 'Received too few base stations,' +
189
+ 'we need at least two. Please try again!'))
190
+ self.too_few_bs = True
191
+ self.is_done = True
192
+ if self.show_add_measurements and len(self.recorded_angles_result) > 0:
193
+ self.too_few_bs = False
194
+ self.completeChanged.emit()
195
+ self.start_action_button.setText("Restart Measurement")
196
+ self.start_action_button.setDisabled(False)
197
+ else:
198
+ self.too_few_bs = False
199
+ status_text_string = f'Recording Done! Visible Base stations: {self.visible_basestations}\n'
200
+ if self.show_add_measurements:
201
+ self.recorded_angles_result.append(self.get_sample())
202
+ status_text_string += f'Total measurements added: {len(self.recorded_angles_result)}\n'
203
+ self.status_text.setText(self.str_pad(status_text_string))
204
+ self.is_done = True
205
+ self.completeChanged.emit()
206
+
207
+ if self.show_add_measurements:
208
+ self.start_action_button.setText("Add more measurements")
209
+ self.start_action_button.setDisabled(False)
210
+ else:
211
+ self.start_action_button.setText("Restart Measurement")
212
+ self.start_action_button.setDisabled(False)
213
+
214
+ def get_sample(self):
215
+ return self.recorded_angle_result
216
+
217
+ def str_pad(self, string_msg):
218
+ new_string_msg = string_msg
219
+
220
+ if string_msg.count('\n') < STRING_PAD_TOTAL:
221
+ for i in range(STRING_PAD_TOTAL-string_msg.count('\n')):
222
+ new_string_msg += '\n'
223
+
224
+ return new_string_msg
225
+
226
+
227
+ class RecordOriginSamplePage(LighthouseBasestationGeometryWizardBasePage):
228
+ def __init__(self, cf: Crazyflie, parent=None):
229
+ super(RecordOriginSamplePage, self).__init__(cf)
230
+ self.explanation_text.setText(
231
+ 'Step 1. Put the Crazyflie where you want the origin of your coordinate system.\n')
232
+ pixmap = QtGui.QPixmap(cfclient.module_path + "/ui/wizards/bslh_1.png")
233
+ pixmap = pixmap.scaledToWidth(PICTURE_WIDTH)
234
+ self.explanation_picture.setPixmap(pixmap)
235
+
236
+
237
+ class RecordXAxisSamplePage(LighthouseBasestationGeometryWizardBasePage):
238
+ def __init__(self, cf: Crazyflie, parent=None):
239
+ super(RecordXAxisSamplePage, self).__init__(cf)
240
+ self.explanation_text.setText('Step 2. Put the Crazyflie on the positive X-axis,' +
241
+ f' exactly {REFERENCE_DIST} meters from the origin.\n' +
242
+ 'This will be used to define the X-axis as well as scaling of the system.')
243
+ pixmap = QtGui.QPixmap(cfclient.module_path + "/ui/wizards/bslh_2.png")
244
+ pixmap = pixmap.scaledToWidth(PICTURE_WIDTH)
245
+ self.explanation_picture.setPixmap(pixmap)
246
+
247
+
248
+ class RecordXYPlaneSamplesPage(LighthouseBasestationGeometryWizardBasePage):
249
+ def __init__(self, cf: Crazyflie, parent=None):
250
+ super(RecordXYPlaneSamplesPage, self).__init__(cf, show_add_measurements=True)
251
+ self.explanation_text.setText('Step 3. Put the Crazyflie somewhere in the XY-plane, but not on the X-axis.\n' +
252
+ 'This position is used to map the the XY-plane to the floor.\n' +
253
+ 'You can sample multiple positions to get a more precise definition.')
254
+ pixmap = QtGui.QPixmap(cfclient.module_path + "/ui/wizards/bslh_3.png")
255
+ pixmap = pixmap.scaledToWidth(PICTURE_WIDTH)
256
+ self.explanation_picture.setPixmap(pixmap)
257
+
258
+ def get_samples(self):
259
+ return self.recorded_angles_result
260
+
261
+
262
+ class RecordXYZSpaceSamplesPage(LighthouseBasestationGeometryWizardBasePage):
263
+ def __init__(self, cf: Crazyflie, parent=None):
264
+ super(RecordXYZSpaceSamplesPage, self).__init__(cf)
265
+ self.explanation_text.setText('Step 4. Move the Crazyflie around, try to cover all of the flying space,\n' +
266
+ 'make sure all the base stations are received.\n' +
267
+ 'Avoid moving too fast, you can increase the record time if needed.\n')
268
+ pixmap = QtGui.QPixmap(cfclient.module_path + "/ui/wizards/bslh_4.png")
269
+ pixmap = pixmap.scaledToWidth(PICTURE_WIDTH)
270
+ self.explanation_picture.setPixmap(pixmap)
271
+
272
+ self.record_timer = QtCore.QTimer()
273
+ self.record_timer.timeout.connect(self._record_timer_cb)
274
+ self.record_time_total = DEFAULT_RECORD_TIME
275
+ self.record_time_current = 0
276
+ self.reader = LighthouseSweepAngleReader(self.cf, self._ready_single_sample_cb)
277
+ self.bs_seen = set()
278
+
279
+ def extra_layout_field(self):
280
+ h_box = QtWidgets.QHBoxLayout()
281
+ self.seconds_explanation_text = QtWidgets.QLabel()
282
+ self.fill_record_times_line_edit = QtWidgets.QLineEdit(str(DEFAULT_RECORD_TIME))
283
+ self.seconds_explanation_text.setText('Enter the number of seconds you want to record:')
284
+ h_box.addStretch()
285
+ h_box.addWidget(self.seconds_explanation_text)
286
+ h_box.addWidget(self.fill_record_times_line_edit)
287
+ h_box.addStretch()
288
+ self.layout.addLayout(h_box)
289
+
290
+ def _record_timer_cb(self):
291
+ self.record_time_current += 1
292
+ self.status_text.setText(self.str_pad('Collecting sweep angles...' +
293
+ f' seconds remaining: {self.record_time_total-self.record_time_current}'))
294
+
295
+ if self.record_time_current == self.record_time_total:
296
+ self.reader.stop()
297
+ self.status_text.setText(self.str_pad(
298
+ 'Recording Done!'+f' Got {len(self.recorded_angles_result)} samples!'))
299
+ self.start_action_button.setText("Restart measurements")
300
+ self.start_action_button.setDisabled(False)
301
+ self.is_done = True
302
+ self.completeChanged.emit()
303
+ self.record_timer.stop()
304
+
305
+ def _action_btn_clicked(self):
306
+ self.is_done = False
307
+ self.reader.start()
308
+ self.record_time_current = 0
309
+ self.record_time_total = int(self.fill_record_times_line_edit.text())
310
+ self.record_timer.start(1000)
311
+ self.status_text.setText(self.str_pad('Collecting sweep angles...' +
312
+ f' seconds remaining: {self.record_time_total}'))
313
+
314
+ self.start_action_button.setDisabled(True)
315
+
316
+ def _ready_single_sample_cb(self, bs_id: int, angles: LighthouseBsVectors):
317
+ now = time.time()
318
+ measurement = LhMeasurement(timestamp=now, base_station_id=bs_id, angles=angles)
319
+ self.recorded_angles_result.append(measurement)
320
+ self.bs_seen.add(str(bs_id + 1))
321
+
322
+ def get_samples(self):
323
+ return self.recorded_angles_result
324
+
325
+
326
+ class EstimateGeometryThread(QtCore.QObject):
327
+ finished = QtCore.pyqtSignal()
328
+ failed = QtCore.pyqtSignal()
329
+
330
+ def __init__(self, origin, x_axis, xy_plane, samples):
331
+ super(EstimateGeometryThread, self).__init__()
332
+
333
+ self.origin = origin
334
+ self.x_axis = x_axis
335
+ self.xy_plane = xy_plane
336
+ self.samples = samples
337
+ self.bs_poses = {}
338
+
339
+ def run(self):
340
+ try:
341
+ self.bs_poses = self._estimate_geometry(self.origin, self.x_axis, self.xy_plane, self.samples)
342
+ self.finished.emit()
343
+ except Exception as ex:
344
+ print(ex)
345
+ self.failed.emit()
346
+
347
+ def get_poses(self):
348
+ return self.bs_poses
349
+
350
+ def _estimate_geometry(self, origin: LhCfPoseSample,
351
+ x_axis: list[LhCfPoseSample],
352
+ xy_plane: list[LhCfPoseSample],
353
+ samples: list[LhCfPoseSample]) -> dict[int, Pose]:
354
+ """Estimate the geometry of the system based on samples recorded by a Crazyflie"""
355
+ matched_samples = [origin] + x_axis + xy_plane + LighthouseSampleMatcher.match(samples, min_nr_of_bs_in_match=2)
356
+ initial_guess, cleaned_matched_samples = LighthouseInitialEstimator.estimate(matched_samples,
357
+ LhDeck4SensorPositions.positions)
358
+
359
+ solution = LighthouseGeometrySolver.solve(initial_guess,
360
+ cleaned_matched_samples,
361
+ LhDeck4SensorPositions.positions)
362
+ if not solution.success:
363
+ raise Exception("No lighthouse base station geometry solution could be found!")
364
+
365
+ start_x_axis = 1
366
+ start_xy_plane = 1 + len(x_axis)
367
+ origin_pos = solution.cf_poses[0].translation
368
+ x_axis_poses = solution.cf_poses[start_x_axis:start_x_axis + len(x_axis)]
369
+ x_axis_pos = list(map(lambda x: x.translation, x_axis_poses))
370
+ xy_plane_poses = solution.cf_poses[start_xy_plane:start_xy_plane + len(xy_plane)]
371
+ xy_plane_pos = list(map(lambda x: x.translation, xy_plane_poses))
372
+
373
+ # Align the solution
374
+ bs_aligned_poses, transformation = LighthouseSystemAligner.align(
375
+ origin_pos, x_axis_pos, xy_plane_pos, solution.bs_poses)
376
+
377
+ cf_aligned_poses = list(map(transformation.rotate_translate_pose, solution.cf_poses))
378
+
379
+ # Scale the solution
380
+ bs_scaled_poses, cf_scaled_poses, scale = LighthouseSystemScaler.scale_fixed_point(bs_aligned_poses,
381
+ cf_aligned_poses,
382
+ [REFERENCE_DIST, 0, 0],
383
+ cf_aligned_poses[1])
384
+
385
+ return bs_scaled_poses
386
+
387
+
388
+ class EstimateBSGeometryPage(LighthouseBasestationGeometryWizardBasePage):
389
+ def __init__(self, cf: Crazyflie, origin_page: RecordOriginSamplePage, xaxis_page: RecordXAxisSamplePage,
390
+ xyplane_page: RecordXYPlaneSamplesPage, xyzspace_page: RecordXYZSpaceSamplesPage, parent=None):
391
+
392
+ super(EstimateBSGeometryPage, self).__init__(cf)
393
+ self.explanation_text.setText('Step 5. Press the button to estimate the geometry and check the result.\n' +
394
+ 'If the positions of the base stations look reasonable, press finish to close ' +
395
+ 'the wizard,\n' +
396
+ 'if not restart the wizard.')
397
+ pixmap = QtGui.QPixmap(cfclient.module_path + "/ui/wizards/bslh_5.png")
398
+ pixmap = pixmap.scaledToWidth(640)
399
+ self.explanation_picture.setPixmap(pixmap)
400
+ self.start_action_button.setText('Estimate Geometry')
401
+ self.origin_page = origin_page
402
+ self.xaxis_page = xaxis_page
403
+ self.xyplane_page = xyplane_page
404
+ self.xyzspace_page = xyzspace_page
405
+ self.bs_poses = {}
406
+
407
+ def _action_btn_clicked(self):
408
+ self.start_action_button.setDisabled(True)
409
+ self.status_text.setText(self.str_pad('Estimating geometry...'))
410
+ origin = self.origin_page.get_sample()
411
+ x_axis = [self.xaxis_page.get_sample()]
412
+ xy_plane = self.xyplane_page.get_samples()
413
+ samples = self.xyzspace_page.get_samples()
414
+ self.thread_estimator = QtCore.QThread()
415
+ self.worker = EstimateGeometryThread(origin, x_axis, xy_plane, samples)
416
+ self.worker.moveToThread(self.thread_estimator)
417
+ self.thread_estimator.started.connect(self.worker.run)
418
+ self.worker.finished.connect(self.thread_estimator.quit)
419
+ self.worker.finished.connect(self._geometry_estimated_finished)
420
+ self.worker.failed.connect(self._geometry_estimated_failed)
421
+ self.worker.finished.connect(self.worker.deleteLater)
422
+ self.thread_estimator.finished.connect(self.thread_estimator.deleteLater)
423
+ self.thread_estimator.start()
424
+
425
+ def _geometry_estimated_finished(self):
426
+ self.bs_poses = self.worker.get_poses()
427
+ self.start_action_button.setDisabled(False)
428
+ self.status_text.setText(self.str_pad('Geometry estimated! (X,Y,Z) in meters \n' +
429
+ self._print_base_stations_poses(self.bs_poses)))
430
+ self.is_done = True
431
+ self.completeChanged.emit()
432
+
433
+ def _geometry_estimated_failed(self):
434
+ self.bs_poses = self.worker.get_poses()
435
+ self.status_text.setText(self.str_pad('Geometry estimate failed! \n' +
436
+ 'Hit Cancel to close the wizard and start again'))
437
+
438
+ def _print_base_stations_poses(self, base_stations: dict[int, Pose]):
439
+ """Pretty print of base stations pose"""
440
+ bs_string = ''
441
+ for bs_id, pose in sorted(base_stations.items()):
442
+ pos = pose.translation
443
+ temp_string = f' {bs_id + 1}: ({pos[0]}, {pos[1]}, {pos[2]})'
444
+ bs_string += '\n' + temp_string
445
+
446
+ return bs_string
447
+
448
+ def get_geometry(self):
449
+ geo_dict = {}
450
+ for bs_id, pose in self.bs_poses.items():
451
+ geo = LighthouseBsGeometry()
452
+ geo.origin = pose.translation.tolist()
453
+ geo.rotation_matrix = pose.rot_matrix.tolist()
454
+ geo.valid = True
455
+ geo_dict[bs_id] = geo
456
+
457
+ return geo_dict
458
+
459
+
460
+ if __name__ == '__main__':
461
+ import sys
462
+ app = QtWidgets.QApplication(sys.argv)
463
+ wizard = LighthouseBasestationGeometryWizard()
464
+ wizard.show()
465
+ sys.exit(app.exec())
@@ -62,7 +62,7 @@ class ConfigManager(metaclass=Singleton):
62
62
  try:
63
63
  idx = self._list_of_configs.index(config_name)
64
64
  return self._input_config[idx]
65
- except:
65
+ except Exception:
66
66
  return None
67
67
 
68
68
  def get_settings(self, config_name):
@@ -70,7 +70,7 @@ class ConfigManager(metaclass=Singleton):
70
70
  try:
71
71
  idx = self._list_of_configs.index(config_name)
72
72
  return self._input_settings[idx]
73
- except:
73
+ except Exception:
74
74
  return None
75
75
 
76
76
  def get_list_of_configs(self):
@@ -87,7 +87,8 @@ class ConfigManager(metaclass=Singleton):
87
87
  data = json.load(json_data)
88
88
  new_input_device = {}
89
89
  new_input_settings = {"updateperiod": 10,
90
- "springythrottle": True}
90
+ "springythrottle": True,
91
+ "rp_dead_band": 0.05}
91
92
  for s in data["inputconfig"]["inputdevice"]:
92
93
  if s == "axis":
93
94
  for a in data["inputconfig"]["inputdevice"]["axis"]:
@@ -103,7 +104,7 @@ class ConfigManager(metaclass=Singleton):
103
104
 
104
105
  try:
105
106
  ids = a["ids"]
106
- except:
107
+ except Exception:
107
108
  ids = [a["id"]]
108
109
  for id in ids:
109
110
  locaxis = copy.deepcopy(axis)