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.
- cfclient/__init__.py +16 -11
- cfclient/configs/config.json +4 -3
- cfclient/configs/input/Generic_OS_X.json +1 -0
- cfclient/configs/input/Joystick.json +1 -0
- cfclient/configs/input/PS3_Mode_1.json +1 -0
- cfclient/configs/input/PS3_Mode_2.json +1 -0
- cfclient/configs/input/PS3_Mode_3.json +1 -0
- cfclient/configs/input/PS4_Mode_1.json +1 -0
- cfclient/configs/input/PS4_Mode_2.json +1 -0
- cfclient/configs/input/PS4_shoulder_btns_yaw.json +1 -0
- cfclient/configs/input/xbox360_mode1.json +1 -0
- 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/gui.py +44 -9
- cfclient/headless.py +3 -12
- cfclient/resources/log_param_doc.json +1 -0
- cfclient/ui/connectivity_manager.py +198 -0
- cfclient/ui/dialogs/about.py +53 -36
- cfclient/ui/dialogs/about.ui +23 -3
- cfclient/ui/dialogs/anchor_position_dialog.py +252 -0
- cfclient/ui/dialogs/anchor_position_dialog.ui +138 -0
- cfclient/ui/dialogs/basestation_mode_dialog.py +185 -0
- cfclient/ui/dialogs/basestation_mode_dialog.ui +186 -0
- cfclient/ui/dialogs/bootloader.py +448 -85
- cfclient/ui/dialogs/bootloader.ui +387 -134
- cfclient/ui/dialogs/cf2config.py +4 -4
- cfclient/ui/dialogs/cf2config.ui +3 -4
- cfclient/ui/dialogs/inputconfigdialogue.py +24 -19
- cfclient/ui/dialogs/inputconfigdialogue.ui +53 -30
- cfclient/ui/dialogs/lighthouse_bs_geometry_dialog.py +220 -0
- cfclient/ui/dialogs/lighthouse_bs_geometry_dialog.ui +110 -0
- cfclient/ui/dialogs/lighthouse_system_type_dialog.py +93 -0
- cfclient/ui/dialogs/lighthouse_system_type_dialog.ui +121 -0
- cfclient/ui/dialogs/logconfigdialogue.py +401 -101
- cfclient/ui/dialogs/logconfigdialogue.ui +117 -72
- cfclient/ui/icons/bl.webp +0 -0
- cfclient/ui/icons/bolt.webp +0 -0
- cfclient/ui/icons/cf21.webp +0 -0
- cfclient/ui/icons/checkmark_black.png +0 -0
- cfclient/ui/icons/checkmark_white.png +0 -0
- cfclient/ui/icons/create.png +0 -0
- cfclient/ui/icons/delete.png +0 -0
- cfclient/ui/icons/flapper.webp +0 -0
- cfclient/ui/icons/tag.webp +0 -0
- cfclient/ui/main.py +328 -258
- cfclient/ui/main.ui +184 -80
- cfclient/ui/pluginhelper.py +7 -1
- cfclient/ui/pose_logger.py +116 -0
- cfclient/ui/tab_toolbox.py +208 -0
- cfclient/ui/tabs/ColorLEDTab.py +752 -0
- cfclient/ui/tabs/ConsoleTab.py +48 -13
- cfclient/ui/{toolboxes → tabs}/CrtpSharkToolbox.py +19 -34
- cfclient/ui/tabs/ExampleTab.py +9 -16
- cfclient/ui/tabs/FlightTab.py +437 -325
- cfclient/ui/tabs/GpsTab.py +14 -20
- cfclient/ui/tabs/LEDRingTab.py +277 -0
- cfclient/ui/tabs/LogBlockDebugTab.py +20 -27
- cfclient/ui/tabs/LogBlockTab.py +35 -35
- cfclient/ui/tabs/LogClientTab.py +85 -0
- cfclient/ui/tabs/LogTab.py +50 -27
- cfclient/ui/tabs/ParamTab.py +443 -57
- cfclient/ui/tabs/PlotTab.py +23 -25
- cfclient/ui/tabs/TuningTab.py +292 -0
- cfclient/ui/tabs/__init__.py +12 -2
- cfclient/ui/tabs/colorLEDTab.ui +624 -0
- cfclient/ui/tabs/consoleTab.ui +46 -0
- cfclient/ui/tabs/flightActionContainer.ui +103 -0
- cfclient/ui/tabs/flightTab.ui +724 -237
- cfclient/ui/tabs/{ledTab.ui → ledRingTab.ui} +63 -46
- cfclient/ui/tabs/lighthouse_tab.py +714 -0
- cfclient/ui/tabs/lighthouse_tab.ui +430 -0
- cfclient/ui/tabs/locopositioning_tab.py +606 -389
- cfclient/ui/tabs/locopositioning_tab.ui +370 -253
- cfclient/ui/tabs/logClientTab.ui +52 -0
- cfclient/ui/tabs/logTab.ui +1 -1
- cfclient/ui/tabs/paramTab.ui +204 -3
- cfclient/ui/tabs/tuningTab.ui +773 -0
- cfclient/ui/widgets/ai.py +37 -39
- cfclient/ui/widgets/hexspinbox.py +16 -10
- cfclient/ui/widgets/plotter.ui +39 -47
- cfclient/ui/widgets/plotwidget.py +57 -22
- cfclient/ui/widgets/super_slider.py +112 -0
- cfclient/ui/wizards/__init__.py +0 -0
- cfclient/ui/wizards/bslh_1.png +0 -0
- cfclient/ui/wizards/bslh_2.png +0 -0
- cfclient/ui/wizards/bslh_3.png +0 -0
- cfclient/ui/wizards/bslh_4.png +0 -0
- cfclient/ui/wizards/bslh_5.png +0 -0
- cfclient/ui/wizards/lighthouse_geo_bs_estimation_wizard.py +465 -0
- cfclient/utils/config_manager.py +5 -4
- cfclient/utils/input/__init__.py +77 -19
- cfclient/utils/input/inputinterfaces/wiimote.py +2 -2
- cfclient/utils/input/inputreaderinterface.py +17 -7
- cfclient/utils/input/inputreaders/__init__.py +17 -0
- cfclient/utils/logconfigreader.py +245 -25
- cfclient/utils/logdatawriter.py +3 -1
- cfclient/utils/periodictimer.py +1 -1
- cfclient/utils/ui.py +336 -0
- cfclient/utils/zmq_led_driver.py +5 -0
- cfclient/utils/zmq_param.py +6 -0
- cfclient/version.py +34 -1
- cfclient-2025.12.1.dist-info/METADATA +70 -0
- cfclient-2025.12.1.dist-info/RECORD +152 -0
- {cfclient-2017.4.dist-info → cfclient-2025.12.1.dist-info}/WHEEL +1 -1
- {cfclient-2017.4.dist-info → cfclient-2025.12.1.dist-info}/entry_points.txt +0 -1
- cfclient-2025.12.1.dist-info/licenses/LICENSE.txt +350 -0
- {cfclient-2017.4.dist-info → cfclient-2025.12.1.dist-info}/top_level.txt +1 -0
- cfconfig/Makefile +51 -0
- cfconfig/configblock.py +111 -0
- cfloader/__init__.py +41 -55
- cfzmq/__init__.py +22 -14
- cfclient/ui/dialogs/cf1config.py +0 -265
- cfclient/ui/dialogs/cf1config.ui +0 -260
- cfclient/ui/tab.py +0 -96
- cfclient/ui/tabs/LEDTab.py +0 -169
- cfclient/ui/toolboxes/ConsoleToolbox.py +0 -69
- cfclient/ui/toolboxes/DebugDriverToolbox.py +0 -107
- cfclient/ui/toolboxes/__init__.py +0 -45
- cfclient/ui/toolboxes/consoleToolbox.ui +0 -62
- cfclient/ui/toolboxes/debugDriverToolbox.ui +0 -86
- cfclient-2017.4.dist-info/DESCRIPTION.rst +0 -3
- cfclient-2017.4.dist-info/METADATA +0 -22
- cfclient-2017.4.dist-info/RECORD +0 -104
- cfclient-2017.4.dist-info/metadata.json +0 -1
- /cfclient/{icon-256.png → ui/icons/icon-256.png} +0 -0
- /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())
|
cfclient/utils/config_manager.py
CHANGED
|
@@ -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)
|