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,714 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
#
|
|
4
|
+
# || ____ _ __
|
|
5
|
+
# +------+ / __ )(_) /_______________ _____ ___
|
|
6
|
+
# | 0xBC | / __ / / __/ ___/ ___/ __ `/_ / / _ \
|
|
7
|
+
# +------+ / /_/ / / /_/ /__/ / / /_/ / / /_/ __/
|
|
8
|
+
# || || /_____/_/\__/\___/_/ \__,_/ /___/\___/
|
|
9
|
+
#
|
|
10
|
+
# Copyright (C) 2022-2023 Bitcraze AB
|
|
11
|
+
#
|
|
12
|
+
# Crazyflie Nano Quadcopter Client
|
|
13
|
+
#
|
|
14
|
+
# This program is free software; you can redistribute it and/or
|
|
15
|
+
# modify it under the terms of the GNU General Public License
|
|
16
|
+
# as published by the Free Software Foundation; either version 2
|
|
17
|
+
# of the License, or (at your option) any later version.
|
|
18
|
+
#
|
|
19
|
+
# This program is distributed in the hope that it will be useful,
|
|
20
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
21
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
22
|
+
# GNU General Public License for more details.
|
|
23
|
+
|
|
24
|
+
# You should have received a copy of the GNU General Public License
|
|
25
|
+
# along with this program; if not, write to the Free Software
|
|
26
|
+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
27
|
+
# 02110-1301, USA.
|
|
28
|
+
|
|
29
|
+
"""
|
|
30
|
+
Shows data for the Lighthouse Positioning system
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
import logging
|
|
34
|
+
|
|
35
|
+
from PyQt6 import uic
|
|
36
|
+
from PyQt6.QtCore import Qt, pyqtSignal, QTimer
|
|
37
|
+
from PyQt6.QtWidgets import QMessageBox
|
|
38
|
+
from PyQt6.QtWidgets import QFileDialog
|
|
39
|
+
from PyQt6.QtWidgets import QLabel
|
|
40
|
+
|
|
41
|
+
import cfclient
|
|
42
|
+
from cfclient.ui.tab_toolbox import TabToolbox
|
|
43
|
+
|
|
44
|
+
from cflib.crazyflie.log import LogConfig
|
|
45
|
+
from cflib.crazyflie.mem import LighthouseMemHelper
|
|
46
|
+
from cflib.localization import LighthouseConfigWriter
|
|
47
|
+
from cflib.localization import LighthouseConfigFileManager
|
|
48
|
+
|
|
49
|
+
from cfclient.ui.dialogs.lighthouse_bs_geometry_dialog import LighthouseBsGeometryDialog
|
|
50
|
+
from cfclient.ui.dialogs.basestation_mode_dialog import LighthouseBsModeDialog
|
|
51
|
+
from cfclient.ui.dialogs.lighthouse_system_type_dialog import LighthouseSystemTypeDialog
|
|
52
|
+
from cfclient.utils.logconfigreader import FILE_REGEX_YAML
|
|
53
|
+
|
|
54
|
+
from vispy import scene
|
|
55
|
+
import numpy as np
|
|
56
|
+
import math
|
|
57
|
+
import os
|
|
58
|
+
|
|
59
|
+
__author__ = 'Bitcraze AB'
|
|
60
|
+
__all__ = ['LighthouseTab']
|
|
61
|
+
|
|
62
|
+
logger = logging.getLogger(__name__)
|
|
63
|
+
|
|
64
|
+
lighthouse_tab_class = uic.loadUiType(cfclient.module_path + "/ui/tabs/lighthouse_tab.ui")[0]
|
|
65
|
+
|
|
66
|
+
STYLE_RED_BACKGROUND = "background-color: lightpink;"
|
|
67
|
+
STYLE_GREEN_BACKGROUND = "background-color: lightgreen;"
|
|
68
|
+
STYLE_BLUE_BACKGROUND = "background-color: lightblue;"
|
|
69
|
+
STYLE_ORANGE_BACKGROUND = "background-color: orange;"
|
|
70
|
+
STYLE_NO_BACKGROUND = "background-color: none;"
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class MarkerPose():
|
|
74
|
+
COL_X_AXIS = 'red'
|
|
75
|
+
COL_Y_AXIS = 'green'
|
|
76
|
+
COL_Z_AXIS = 'blue'
|
|
77
|
+
|
|
78
|
+
AXIS_LEN = 0.3
|
|
79
|
+
|
|
80
|
+
LABEL_SIZE = 100
|
|
81
|
+
LABEL_OFFSET = np.array((0.0, 0, 0.25))
|
|
82
|
+
|
|
83
|
+
def __init__(self, the_scene, color, text=None):
|
|
84
|
+
self._scene = the_scene
|
|
85
|
+
self._color = color
|
|
86
|
+
self._text = text
|
|
87
|
+
|
|
88
|
+
self._marker = scene.visuals.Markers(
|
|
89
|
+
pos=np.array([[0, 0, 0]]),
|
|
90
|
+
parent=self._scene,
|
|
91
|
+
face_color=self._color)
|
|
92
|
+
|
|
93
|
+
self._x_axis = scene.visuals.Line(
|
|
94
|
+
pos=np.array([[0, 0, 0], [0, 0, 0]]),
|
|
95
|
+
color=self.COL_X_AXIS,
|
|
96
|
+
parent=self._scene)
|
|
97
|
+
|
|
98
|
+
self._y_axis = scene.visuals.Line(pos=np.array(
|
|
99
|
+
[[0, 0, 0], [0, 0, 0]]),
|
|
100
|
+
color=self.COL_Y_AXIS,
|
|
101
|
+
parent=self._scene)
|
|
102
|
+
|
|
103
|
+
self._z_axis = scene.visuals.Line(
|
|
104
|
+
pos=np.array([[0, 0, 0], [0, 0, 0]]),
|
|
105
|
+
color=self.COL_Z_AXIS,
|
|
106
|
+
parent=self._scene)
|
|
107
|
+
|
|
108
|
+
self._label = None
|
|
109
|
+
if self._text:
|
|
110
|
+
self._label = scene.visuals.Text(
|
|
111
|
+
text=self._text,
|
|
112
|
+
font_size=self.LABEL_SIZE,
|
|
113
|
+
pos=self.LABEL_OFFSET,
|
|
114
|
+
parent=self._scene)
|
|
115
|
+
|
|
116
|
+
def set_pose(self, position, rot):
|
|
117
|
+
self._marker.set_data(pos=np.array([position]), face_color=self._color)
|
|
118
|
+
|
|
119
|
+
if self._label:
|
|
120
|
+
self._label.pos = self.LABEL_OFFSET + position
|
|
121
|
+
|
|
122
|
+
x_tip = np.dot(np.array(rot), np.array([self.AXIS_LEN, 0, 0]))
|
|
123
|
+
self._x_axis.set_data(np.array([position, x_tip + position]), color=self.COL_X_AXIS)
|
|
124
|
+
|
|
125
|
+
y_tip = np.dot(np.array(rot), np.array([0, self.AXIS_LEN, 0]))
|
|
126
|
+
self._y_axis.set_data(np.array([position, y_tip + position]), color=self.COL_Y_AXIS)
|
|
127
|
+
|
|
128
|
+
z_tip = np.dot(np.array(rot), np.array([0, 0, self.AXIS_LEN]))
|
|
129
|
+
self._z_axis.set_data(np.array([position, z_tip + position]), color=self.COL_Z_AXIS)
|
|
130
|
+
|
|
131
|
+
def remove(self):
|
|
132
|
+
self._marker.parent = None
|
|
133
|
+
self._x_axis.parent = None
|
|
134
|
+
self._y_axis.parent = None
|
|
135
|
+
self._z_axis.parent = None
|
|
136
|
+
if self._label:
|
|
137
|
+
self._label.parent = None
|
|
138
|
+
|
|
139
|
+
def set_color(self, color):
|
|
140
|
+
self._color = color
|
|
141
|
+
self._marker.set_data(face_color=self._color)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class Plot3dLighthouse(scene.SceneCanvas):
|
|
145
|
+
POSITION_BRUSH = np.array((0, 0, 1.0))
|
|
146
|
+
BS_BRUSH_VISIBLE = np.array((0.2, 0.5, 0.2))
|
|
147
|
+
BS_BRUSH_NOT_VISIBLE = np.array((0.8, 0.5, 0.5))
|
|
148
|
+
|
|
149
|
+
VICINITY_DISTANCE = 2.5
|
|
150
|
+
HIGHLIGHT_DISTANCE = 0.5
|
|
151
|
+
|
|
152
|
+
LABEL_SIZE = 100
|
|
153
|
+
LABEL_HIGHLIGHT_SIZE = 200
|
|
154
|
+
|
|
155
|
+
HIGHLIGHT_SIZE = 20
|
|
156
|
+
|
|
157
|
+
TEXT_OFFSET = np.array((0.0, 0, 0.25))
|
|
158
|
+
|
|
159
|
+
def __init__(self):
|
|
160
|
+
scene.SceneCanvas.__init__(self, keys=None)
|
|
161
|
+
self.unfreeze()
|
|
162
|
+
|
|
163
|
+
self._view = self.central_widget.add_view()
|
|
164
|
+
self._view.bgcolor = '#ffffff'
|
|
165
|
+
self._view.camera = scene.TurntableCamera(
|
|
166
|
+
distance=10.0,
|
|
167
|
+
up='+z',
|
|
168
|
+
center=(0.0, 0.0, 1.0))
|
|
169
|
+
|
|
170
|
+
self._cf = None
|
|
171
|
+
self._base_stations = {}
|
|
172
|
+
|
|
173
|
+
self.freeze()
|
|
174
|
+
|
|
175
|
+
plane_size = 10
|
|
176
|
+
scene.visuals.Plane(
|
|
177
|
+
width=plane_size,
|
|
178
|
+
height=plane_size,
|
|
179
|
+
width_segments=plane_size,
|
|
180
|
+
height_segments=plane_size,
|
|
181
|
+
color=(0.5, 0.5, 0.5, 0.5),
|
|
182
|
+
edge_color="gray",
|
|
183
|
+
parent=self._view.scene)
|
|
184
|
+
|
|
185
|
+
self._addArrows(1, 0.02, 0.1, 0.1, self._view.scene)
|
|
186
|
+
|
|
187
|
+
def _addArrows(self, length, width, head_length, head_width, parent):
|
|
188
|
+
# The Arrow visual in vispy does not seem to work very good,
|
|
189
|
+
# draw arrows using lines instead.
|
|
190
|
+
w = width / 2
|
|
191
|
+
hw = head_width / 2
|
|
192
|
+
base_len = length - head_length
|
|
193
|
+
|
|
194
|
+
# X-axis
|
|
195
|
+
scene.visuals.LinePlot([
|
|
196
|
+
[0, w, 0],
|
|
197
|
+
[base_len, w, 0],
|
|
198
|
+
[base_len, hw, 0],
|
|
199
|
+
[length, 0, 0],
|
|
200
|
+
[base_len, -hw, 0],
|
|
201
|
+
[base_len, -w, 0],
|
|
202
|
+
[0, -w, 0]],
|
|
203
|
+
width=1.0, color='red', parent=parent, marker_size=0.0)
|
|
204
|
+
|
|
205
|
+
# Y-axis
|
|
206
|
+
scene.visuals.LinePlot([
|
|
207
|
+
[w, 0, 0],
|
|
208
|
+
[w, base_len, 0],
|
|
209
|
+
[hw, base_len, 0],
|
|
210
|
+
[0, length, 0],
|
|
211
|
+
[-hw, base_len, 0],
|
|
212
|
+
[-w, base_len, 0],
|
|
213
|
+
[-w, 0, 0]],
|
|
214
|
+
width=1.0, color='green', parent=parent, marker_size=0.0)
|
|
215
|
+
|
|
216
|
+
# Z-axis
|
|
217
|
+
scene.visuals.LinePlot([
|
|
218
|
+
[0, w, 0],
|
|
219
|
+
[0, w, base_len],
|
|
220
|
+
[0, hw, base_len],
|
|
221
|
+
[0, 0, length],
|
|
222
|
+
[0, -hw, base_len],
|
|
223
|
+
[0, -w, base_len],
|
|
224
|
+
[0, -w, 0]],
|
|
225
|
+
width=1.0, color='blue', parent=parent, marker_size=0.0)
|
|
226
|
+
|
|
227
|
+
def update_cf_pose(self, position, rot):
|
|
228
|
+
if not self._cf:
|
|
229
|
+
self._cf = MarkerPose(self._view.scene, self.POSITION_BRUSH)
|
|
230
|
+
self._cf.set_pose(position, rot)
|
|
231
|
+
|
|
232
|
+
def update_base_station_geos(self, geos):
|
|
233
|
+
for id, geo in geos.items():
|
|
234
|
+
if (geo is not None) and (id not in self._base_stations):
|
|
235
|
+
self._base_stations[id] = MarkerPose(self._view.scene, self.BS_BRUSH_NOT_VISIBLE, text=f"{id + 1}")
|
|
236
|
+
self._base_stations[id].set_pose(geo.origin, geo.rotation_matrix)
|
|
237
|
+
|
|
238
|
+
geos_to_remove = self._base_stations.keys() - geos.keys()
|
|
239
|
+
for id in geos_to_remove:
|
|
240
|
+
existing = self._base_stations.pop(id)
|
|
241
|
+
existing.remove()
|
|
242
|
+
|
|
243
|
+
def update_base_station_visibility(self, visibility):
|
|
244
|
+
for id, bs in self._base_stations.items():
|
|
245
|
+
if id in visibility:
|
|
246
|
+
bs.set_color(self.BS_BRUSH_VISIBLE)
|
|
247
|
+
else:
|
|
248
|
+
bs.set_color(self.BS_BRUSH_NOT_VISIBLE)
|
|
249
|
+
|
|
250
|
+
def clear(self):
|
|
251
|
+
if self._cf:
|
|
252
|
+
self._cf.remove()
|
|
253
|
+
self._cf = None
|
|
254
|
+
|
|
255
|
+
for bs in self._base_stations.values():
|
|
256
|
+
bs.remove()
|
|
257
|
+
self._base_stations = {}
|
|
258
|
+
|
|
259
|
+
def _mix(self, col1, col2, mix):
|
|
260
|
+
return col1 * mix + col2 * (1.0 - mix)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
class LighthouseTab(TabToolbox, lighthouse_tab_class):
|
|
264
|
+
"""Tab for plotting Lighthouse data"""
|
|
265
|
+
|
|
266
|
+
# Update period of log data in ms
|
|
267
|
+
UPDATE_PERIOD_LOG = 100
|
|
268
|
+
|
|
269
|
+
# Frame rate (updates per second)
|
|
270
|
+
FPS = 2
|
|
271
|
+
|
|
272
|
+
STATUS_NOT_RECEIVING = 0
|
|
273
|
+
STATUS_MISSING_DATA = 1
|
|
274
|
+
STATUS_TO_ESTIMATOR = 2
|
|
275
|
+
|
|
276
|
+
# TODO change these names to something more logical
|
|
277
|
+
LOG_STATUS = "lighthouse.status"
|
|
278
|
+
LOG_RECEIVE = "lighthouse.bsReceive"
|
|
279
|
+
LOG_CALIBRATION_EXISTS = "lighthouse.bsCalVal"
|
|
280
|
+
LOG_CALIBRATION_CONFIRMED = "lighthouse.bsCalCon"
|
|
281
|
+
LOG_CALIBRATION_UPDATED = "lighthouse.bsCalUd"
|
|
282
|
+
LOG_GEOMETERY_EXISTS = "lighthouse.bsGeoVal"
|
|
283
|
+
LOG_ACTIVE = "lighthouse.bsActive"
|
|
284
|
+
LOG_AVAILABLE = "lighthouse.bsAvailable"
|
|
285
|
+
|
|
286
|
+
_connected_signal = pyqtSignal(str)
|
|
287
|
+
_disconnected_signal = pyqtSignal(str)
|
|
288
|
+
_log_error_signal = pyqtSignal(object, str)
|
|
289
|
+
_status_report_signal = pyqtSignal(int, object, object)
|
|
290
|
+
_new_system_config_written_to_cf_signal = pyqtSignal(bool)
|
|
291
|
+
_geometry_read_signal = pyqtSignal(object)
|
|
292
|
+
_calibration_read_signal = pyqtSignal(object)
|
|
293
|
+
|
|
294
|
+
def __init__(self, helper):
|
|
295
|
+
super(LighthouseTab, self).__init__(helper, 'Lighthouse Positioning')
|
|
296
|
+
self.setupUi(self)
|
|
297
|
+
|
|
298
|
+
# Always wrap callbacks from Crazyflie API though QT Signal/Slots
|
|
299
|
+
# to avoid manipulating the UI when rendering it
|
|
300
|
+
self._connected_signal.connect(self._connected)
|
|
301
|
+
self._disconnected_signal.connect(self._disconnected)
|
|
302
|
+
self._log_error_signal.connect(self._logging_error)
|
|
303
|
+
self._status_report_signal.connect(self._status_report_received)
|
|
304
|
+
self._new_system_config_written_to_cf_signal.connect(self._new_system_config_written_to_cf)
|
|
305
|
+
self._geometry_read_signal.connect(self._geometry_read_cb)
|
|
306
|
+
self._calibration_read_signal.connect(self._calibration_read_cb)
|
|
307
|
+
|
|
308
|
+
# Connect the Crazyflie API callbacks to the signals
|
|
309
|
+
self._helper.cf.connected.add_callback(self._connected_signal.emit)
|
|
310
|
+
self._helper.cf.disconnected.add_callback(self._disconnected_signal.emit)
|
|
311
|
+
|
|
312
|
+
self._set_up_plots()
|
|
313
|
+
|
|
314
|
+
self.is_lighthouse_deck_active = False
|
|
315
|
+
|
|
316
|
+
self._lh_memory_helper = None
|
|
317
|
+
self._lh_config_writer = None
|
|
318
|
+
self._lh_geos = {}
|
|
319
|
+
self._is_geometry_read_ongoing = False
|
|
320
|
+
|
|
321
|
+
self._bs_receives_light = set()
|
|
322
|
+
self._bs_calibration_data_exists = set()
|
|
323
|
+
self._bs_calibration_data_confirmed = set()
|
|
324
|
+
self._bs_calibration_data_updated = set()
|
|
325
|
+
self._bs_geometry_data_exists = set()
|
|
326
|
+
self._bs_data_to_estimator = set()
|
|
327
|
+
self._bs_available = set()
|
|
328
|
+
|
|
329
|
+
self._clear_state_indicator()
|
|
330
|
+
|
|
331
|
+
self._bs_stats = [
|
|
332
|
+
self._bs_receives_light,
|
|
333
|
+
self._bs_calibration_data_exists,
|
|
334
|
+
self._bs_calibration_data_confirmed,
|
|
335
|
+
self._bs_calibration_data_updated,
|
|
336
|
+
self._bs_geometry_data_exists,
|
|
337
|
+
self._bs_data_to_estimator,
|
|
338
|
+
self._bs_available]
|
|
339
|
+
|
|
340
|
+
self._lh_status = self.STATUS_NOT_RECEIVING
|
|
341
|
+
|
|
342
|
+
self._graph_timer = QTimer()
|
|
343
|
+
self._graph_timer.setInterval(int(1000 / self.FPS))
|
|
344
|
+
self._graph_timer.timeout.connect(self._update_graphics)
|
|
345
|
+
self._graph_timer.start()
|
|
346
|
+
|
|
347
|
+
self._basestation_geometry_dialog = LighthouseBsGeometryDialog(self)
|
|
348
|
+
self._basestation_mode_dialog = LighthouseBsModeDialog(self)
|
|
349
|
+
self._system_type_dialog = LighthouseSystemTypeDialog(helper)
|
|
350
|
+
|
|
351
|
+
self._manage_estimate_geometry_button.clicked.connect(self._show_basestation_geometry_dialog)
|
|
352
|
+
self._change_system_type_button.clicked.connect(lambda: self._system_type_dialog.show())
|
|
353
|
+
self._manage_basestation_mode_button.clicked.connect(self._show_basestation_mode_dialog)
|
|
354
|
+
|
|
355
|
+
self._load_sys_config_button.clicked.connect(self._load_sys_config_button_clicked)
|
|
356
|
+
self._save_sys_config_button.clicked.connect(self._save_sys_config_button_clicked)
|
|
357
|
+
|
|
358
|
+
self._is_connected = False
|
|
359
|
+
self._update_ui()
|
|
360
|
+
|
|
361
|
+
def write_and_store_geometry(self, geometries):
|
|
362
|
+
if self._lh_config_writer:
|
|
363
|
+
self._lh_config_writer.write_and_store_config(self._new_system_config_written_to_cf_signal.emit,
|
|
364
|
+
geos=geometries)
|
|
365
|
+
|
|
366
|
+
def _new_system_config_written_to_cf(self, success):
|
|
367
|
+
# Reset the bit fields for calibration data status to get a fresh view
|
|
368
|
+
self._helper.cf.param.set_value("lighthouse.bsCalibReset", '1')
|
|
369
|
+
# New geo data has been written and stored in the CF, read it back to update the UI
|
|
370
|
+
self._start_read_of_geo_data()
|
|
371
|
+
|
|
372
|
+
def _show_basestation_geometry_dialog(self):
|
|
373
|
+
self._basestation_geometry_dialog.reset()
|
|
374
|
+
self._basestation_geometry_dialog.show()
|
|
375
|
+
|
|
376
|
+
def _show_basestation_mode_dialog(self):
|
|
377
|
+
self._basestation_mode_dialog.reset()
|
|
378
|
+
self._basestation_mode_dialog.show()
|
|
379
|
+
|
|
380
|
+
def _set_up_plots(self):
|
|
381
|
+
self._plot_3d = Plot3dLighthouse()
|
|
382
|
+
self._plot_layout.addWidget(self._plot_3d.native)
|
|
383
|
+
|
|
384
|
+
def _connected(self, link_uri):
|
|
385
|
+
"""Callback when the Crazyflie has been connected"""
|
|
386
|
+
logger.debug("Crazyflie connected to {}".format(link_uri))
|
|
387
|
+
|
|
388
|
+
self._basestation_geometry_dialog.reset()
|
|
389
|
+
self._is_connected = True
|
|
390
|
+
|
|
391
|
+
if self._helper.cf.param.get_value('deck.bcLighthouse4') == '1':
|
|
392
|
+
self._lighthouse_deck_detected()
|
|
393
|
+
|
|
394
|
+
self._update_ui()
|
|
395
|
+
|
|
396
|
+
def _lighthouse_deck_detected(self):
|
|
397
|
+
"""Called when the lighthouse deck has been detected. Enables the tab,
|
|
398
|
+
starts logging and polling of the memory sub system as well as starts
|
|
399
|
+
timers for updating graphics"""
|
|
400
|
+
if not self.is_lighthouse_deck_active:
|
|
401
|
+
self.is_lighthouse_deck_active = True
|
|
402
|
+
|
|
403
|
+
try:
|
|
404
|
+
self._register_logblock(
|
|
405
|
+
"lhStatus",
|
|
406
|
+
[self.LOG_STATUS, self.LOG_RECEIVE, self.LOG_CALIBRATION_EXISTS, self.LOG_CALIBRATION_CONFIRMED,
|
|
407
|
+
self.LOG_CALIBRATION_UPDATED, self.LOG_GEOMETERY_EXISTS, self.LOG_ACTIVE, self.LOG_AVAILABLE],
|
|
408
|
+
self._status_report_signal.emit,
|
|
409
|
+
self._log_error_signal.emit)
|
|
410
|
+
except KeyError as e:
|
|
411
|
+
logger.warning(str(e))
|
|
412
|
+
except AttributeError as e:
|
|
413
|
+
logger.warning(str(e))
|
|
414
|
+
|
|
415
|
+
self._populate_status_matrix()
|
|
416
|
+
|
|
417
|
+
# Now that we know we have a lighthouse deck, setup the memory helper and config writer
|
|
418
|
+
self._lh_memory_helper = LighthouseMemHelper(self._helper.cf)
|
|
419
|
+
self._lh_config_writer = LighthouseConfigWriter(self._helper.cf)
|
|
420
|
+
|
|
421
|
+
def _start_read_of_geo_data(self):
|
|
422
|
+
if not self._is_geometry_read_ongoing:
|
|
423
|
+
self._is_geometry_read_ongoing = True
|
|
424
|
+
self._lh_memory_helper.read_all_geos(self._geometry_read_signal.emit)
|
|
425
|
+
|
|
426
|
+
def _geometry_read_cb(self, geometries):
|
|
427
|
+
# Remove any geo data where the valid flag is False
|
|
428
|
+
self._lh_geos = dict(filter(lambda key_value: key_value[1].valid, geometries.items()))
|
|
429
|
+
self._basestation_geometry_dialog.geometry_updated(self._lh_geos)
|
|
430
|
+
self._is_geometry_read_ongoing = False
|
|
431
|
+
|
|
432
|
+
def _is_matching_current_geo_data(self, geometries):
|
|
433
|
+
return geometries == self._lh_geos.keys()
|
|
434
|
+
|
|
435
|
+
def _adjust_bitmask(self, bit_mask, bs_list):
|
|
436
|
+
for id in range(16):
|
|
437
|
+
if bit_mask & (1 << id):
|
|
438
|
+
bs_list.add(id)
|
|
439
|
+
else:
|
|
440
|
+
if id in bs_list:
|
|
441
|
+
bs_list.remove(id)
|
|
442
|
+
|
|
443
|
+
def _status_report_received(self, timestamp, data, logconf):
|
|
444
|
+
"""Callback from the logging system when the status is updated."""
|
|
445
|
+
|
|
446
|
+
if self.LOG_RECEIVE in data:
|
|
447
|
+
bit_mask = data[self.LOG_RECEIVE]
|
|
448
|
+
self._adjust_bitmask(bit_mask, self._bs_receives_light)
|
|
449
|
+
if self.LOG_CALIBRATION_EXISTS in data:
|
|
450
|
+
bit_mask = data[self.LOG_CALIBRATION_EXISTS]
|
|
451
|
+
self._adjust_bitmask(bit_mask, self._bs_calibration_data_exists)
|
|
452
|
+
if self.LOG_CALIBRATION_CONFIRMED in data:
|
|
453
|
+
bit_mask = data[self.LOG_CALIBRATION_CONFIRMED]
|
|
454
|
+
self._adjust_bitmask(bit_mask, self._bs_calibration_data_confirmed)
|
|
455
|
+
if self.LOG_CALIBRATION_UPDATED in data:
|
|
456
|
+
bit_mask = data[self.LOG_CALIBRATION_UPDATED]
|
|
457
|
+
self._adjust_bitmask(bit_mask, self._bs_calibration_data_updated)
|
|
458
|
+
if self.LOG_GEOMETERY_EXISTS in data:
|
|
459
|
+
bit_mask = data[self.LOG_GEOMETERY_EXISTS]
|
|
460
|
+
self._adjust_bitmask(bit_mask, self._bs_geometry_data_exists)
|
|
461
|
+
if not self._is_matching_current_geo_data(self._bs_geometry_data_exists):
|
|
462
|
+
self._start_read_of_geo_data()
|
|
463
|
+
|
|
464
|
+
if self.LOG_ACTIVE in data:
|
|
465
|
+
bit_mask = data[self.LOG_ACTIVE]
|
|
466
|
+
self._adjust_bitmask(bit_mask, self._bs_data_to_estimator)
|
|
467
|
+
|
|
468
|
+
if self.LOG_STATUS in data:
|
|
469
|
+
self._lh_status = data[self.LOG_STATUS]
|
|
470
|
+
|
|
471
|
+
if self.LOG_AVAILABLE in data:
|
|
472
|
+
bit_mask = data[self.LOG_AVAILABLE]
|
|
473
|
+
self._adjust_bitmask(bit_mask, self._bs_available)
|
|
474
|
+
|
|
475
|
+
self._update_basestation_status_indicators()
|
|
476
|
+
|
|
477
|
+
def _disconnected(self, link_uri):
|
|
478
|
+
"""Callback for when the Crazyflie has been disconnected"""
|
|
479
|
+
logger.debug("Crazyflie disconnected from {}".format(link_uri))
|
|
480
|
+
self._clear_state()
|
|
481
|
+
self._update_graphics()
|
|
482
|
+
self._plot_3d.clear()
|
|
483
|
+
self._basestation_geometry_dialog.close()
|
|
484
|
+
self.is_lighthouse_deck_active = False
|
|
485
|
+
self._is_connected = False
|
|
486
|
+
self._update_ui()
|
|
487
|
+
|
|
488
|
+
def _register_logblock(self, logblock_name, variables, data_cb, error_cb,
|
|
489
|
+
update_period=UPDATE_PERIOD_LOG):
|
|
490
|
+
"""Register log data to listen for. One logblock can only contain a limited
|
|
491
|
+
number of parameters."""
|
|
492
|
+
lg = LogConfig(logblock_name, update_period)
|
|
493
|
+
for variable in variables:
|
|
494
|
+
if self._is_in_log_toc(variable):
|
|
495
|
+
lg.add_variable(variable)
|
|
496
|
+
|
|
497
|
+
self._helper.cf.log.add_config(lg)
|
|
498
|
+
lg.data_received_cb.add_callback(data_cb)
|
|
499
|
+
lg.error_cb.add_callback(error_cb)
|
|
500
|
+
lg.start()
|
|
501
|
+
return lg
|
|
502
|
+
|
|
503
|
+
def _is_in_log_toc(self, variable):
|
|
504
|
+
toc = self._helper.cf.log.toc
|
|
505
|
+
group, param = variable.split('.')
|
|
506
|
+
return group in toc.toc and param in toc.toc[group]
|
|
507
|
+
|
|
508
|
+
def _is_in_param_toc(self, group, param):
|
|
509
|
+
toc = self._helper.cf.param.toc
|
|
510
|
+
return bool(group in toc.toc and param in toc.toc[group])
|
|
511
|
+
|
|
512
|
+
def _logging_error(self, log_conf, msg):
|
|
513
|
+
"""Callback from the log layer when an error occurs"""
|
|
514
|
+
QMessageBox.about(self, "LighthouseTab error",
|
|
515
|
+
"Error when using log config",
|
|
516
|
+
" [{0}]: {1}".format(log_conf.name, msg))
|
|
517
|
+
|
|
518
|
+
def _update_graphics(self):
|
|
519
|
+
if self.is_visible() and self.is_lighthouse_deck_active:
|
|
520
|
+
self._plot_3d.update_cf_pose(self._helper.pose_logger.position,
|
|
521
|
+
self._rpy_to_rot(self._helper.pose_logger.rpy_rad))
|
|
522
|
+
self._plot_3d.update_base_station_geos(self._lh_geos)
|
|
523
|
+
self._plot_3d.update_base_station_visibility(self._bs_data_to_estimator)
|
|
524
|
+
self._update_position_label(self._helper.pose_logger.position)
|
|
525
|
+
self._update_status_label(self._lh_status)
|
|
526
|
+
self._mask_status_matrix(self._bs_available)
|
|
527
|
+
|
|
528
|
+
def _update_ui(self):
|
|
529
|
+
enabled = self._is_connected and self.is_lighthouse_deck_active
|
|
530
|
+
self._manage_estimate_geometry_button.setEnabled(enabled)
|
|
531
|
+
self._change_system_type_button.setEnabled(enabled)
|
|
532
|
+
self._load_sys_config_button.setEnabled(enabled)
|
|
533
|
+
self._save_sys_config_button.setEnabled(enabled)
|
|
534
|
+
|
|
535
|
+
def _update_position_label(self, position):
|
|
536
|
+
if len(position) == 3:
|
|
537
|
+
coordinate = "({:0.2f}, {:0.2f}, {:0.2f})".format(
|
|
538
|
+
position[0], position[1], position[2])
|
|
539
|
+
else:
|
|
540
|
+
coordinate = '(0.00, 0.00, 0.00)'
|
|
541
|
+
|
|
542
|
+
self._status_position.setText(coordinate)
|
|
543
|
+
|
|
544
|
+
def _update_status_label(self, status):
|
|
545
|
+
text = ''
|
|
546
|
+
if status == self.STATUS_NOT_RECEIVING:
|
|
547
|
+
text = 'Not receiving'
|
|
548
|
+
elif status == self.STATUS_MISSING_DATA:
|
|
549
|
+
text = 'No geo/calib'
|
|
550
|
+
elif status == self.STATUS_TO_ESTIMATOR:
|
|
551
|
+
text = 'LH ready'
|
|
552
|
+
|
|
553
|
+
self._status_status.setText(text)
|
|
554
|
+
|
|
555
|
+
def _clear_state(self):
|
|
556
|
+
self._lh_memory_helper = None
|
|
557
|
+
self._lh_config_writer = None
|
|
558
|
+
self._lh_geos = {}
|
|
559
|
+
self._is_geometry_read_ongoing = False
|
|
560
|
+
self._bs_receives_light.clear()
|
|
561
|
+
self._bs_calibration_data_exists.clear()
|
|
562
|
+
self._bs_calibration_data_confirmed.clear()
|
|
563
|
+
self._bs_calibration_data_updated.clear()
|
|
564
|
+
self._bs_geometry_data_exists.clear()
|
|
565
|
+
self._bs_data_to_estimator.clear()
|
|
566
|
+
self._update_basestation_status_indicators()
|
|
567
|
+
self._clear_state_indicator()
|
|
568
|
+
self._lh_status = self.STATUS_NOT_RECEIVING
|
|
569
|
+
|
|
570
|
+
def _clear_state_indicator(self):
|
|
571
|
+
container = self._basestation_stats_container
|
|
572
|
+
for row in range(0, 5):
|
|
573
|
+
for col in range(1, 17):
|
|
574
|
+
item = container.itemAtPosition(row, col)
|
|
575
|
+
if item is not None:
|
|
576
|
+
item.widget().deleteLater()
|
|
577
|
+
|
|
578
|
+
def _rpy_to_rot(self, rpy):
|
|
579
|
+
# http://planning.cs.uiuc.edu/node102.html
|
|
580
|
+
# Pitch reversed compared to page above
|
|
581
|
+
roll = rpy[0]
|
|
582
|
+
pitch = rpy[1]
|
|
583
|
+
yaw = rpy[2]
|
|
584
|
+
|
|
585
|
+
cg = math.cos(roll)
|
|
586
|
+
cb = math.cos(-pitch)
|
|
587
|
+
ca = math.cos(yaw)
|
|
588
|
+
sg = math.sin(roll)
|
|
589
|
+
sb = math.sin(-pitch)
|
|
590
|
+
sa = math.sin(yaw)
|
|
591
|
+
|
|
592
|
+
r = [
|
|
593
|
+
[ca * cb, ca * sb * sg - sa * cg, ca * sb * cg + sa * sg],
|
|
594
|
+
[sa * cb, sa * sb * sg + ca * cg, sa * sb * cg - ca * sg],
|
|
595
|
+
[-sb, cb * sg, cb * cg],
|
|
596
|
+
]
|
|
597
|
+
|
|
598
|
+
return np.array(r)
|
|
599
|
+
|
|
600
|
+
def _populate_status_matrix(self):
|
|
601
|
+
container = self._basestation_stats_container
|
|
602
|
+
|
|
603
|
+
# Find the nr of base stations by looking for the highest bit that is set
|
|
604
|
+
# Assume all bs up to that bit are available
|
|
605
|
+
for bs in range(0, 16):
|
|
606
|
+
container.addWidget(self._create_label(str(bs + 1)), 0, bs + 1)
|
|
607
|
+
for i in range(1, 5):
|
|
608
|
+
container.addWidget(self._create_label(), i, bs + 1)
|
|
609
|
+
|
|
610
|
+
def _mask_status_matrix(self, bs_available_mask):
|
|
611
|
+
container = self._basestation_stats_container
|
|
612
|
+
|
|
613
|
+
# Find the nr of base stations by looking for the highest bit that is set
|
|
614
|
+
# Assume all bs up to that bit are available
|
|
615
|
+
for bs in range(0, 16):
|
|
616
|
+
bs_indicator_id = bs + 1
|
|
617
|
+
for stats_indicator_id in range(0, 5):
|
|
618
|
+
item = container.itemAtPosition(stats_indicator_id, bs_indicator_id)
|
|
619
|
+
if item is not None:
|
|
620
|
+
label = item.widget()
|
|
621
|
+
if bs_indicator_id - 1 in bs_available_mask:
|
|
622
|
+
label.setHidden(False)
|
|
623
|
+
else:
|
|
624
|
+
label.setHidden(True)
|
|
625
|
+
|
|
626
|
+
def _create_label(self, text=None):
|
|
627
|
+
label = QLabel()
|
|
628
|
+
label.setMinimumSize(30, 0)
|
|
629
|
+
label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
630
|
+
|
|
631
|
+
if text:
|
|
632
|
+
label.setText(str(text))
|
|
633
|
+
else:
|
|
634
|
+
label.setProperty('frameShape', 'QFrame::Box')
|
|
635
|
+
label.setStyleSheet(STYLE_NO_BACKGROUND)
|
|
636
|
+
|
|
637
|
+
return label
|
|
638
|
+
|
|
639
|
+
def _update_basestation_status_indicators(self):
|
|
640
|
+
"""Handling the basestation status label handles to indicate
|
|
641
|
+
the state of received data per basestation"""
|
|
642
|
+
container = self._basestation_stats_container
|
|
643
|
+
|
|
644
|
+
# Ports the label number to the first index of the statistic id
|
|
645
|
+
stats_id_port = {1: 0, 2: 1, 3: 4, 4: 5}
|
|
646
|
+
|
|
647
|
+
for bs in range(16):
|
|
648
|
+
for stats_indicator_id in range(1, 5):
|
|
649
|
+
bs_indicator_id = bs + 1
|
|
650
|
+
item = container.itemAtPosition(stats_indicator_id, bs_indicator_id)
|
|
651
|
+
if item is not None:
|
|
652
|
+
label = item.widget()
|
|
653
|
+
stats_id = stats_id_port.get(stats_indicator_id)
|
|
654
|
+
temp_set = self._bs_stats[stats_id]
|
|
655
|
+
|
|
656
|
+
if bs in temp_set:
|
|
657
|
+
# If the status bar for calibration data is handled, have an intermediate status
|
|
658
|
+
# else just have red or green.
|
|
659
|
+
if stats_indicator_id == 2:
|
|
660
|
+
label.setStyleSheet(STYLE_BLUE_BACKGROUND)
|
|
661
|
+
label.setToolTip('Calibration data from cache')
|
|
662
|
+
|
|
663
|
+
calib_confirm = bs in self._bs_stats[stats_id + 1]
|
|
664
|
+
calib_updated = bs in self._bs_stats[stats_id + 2]
|
|
665
|
+
|
|
666
|
+
if calib_confirm:
|
|
667
|
+
label.setStyleSheet(STYLE_GREEN_BACKGROUND)
|
|
668
|
+
label.setToolTip('Calibration data verified')
|
|
669
|
+
if calib_updated:
|
|
670
|
+
label.setStyleSheet(STYLE_ORANGE_BACKGROUND)
|
|
671
|
+
label.setToolTip('Calibration data updated, the geometry probably needs to be ' +
|
|
672
|
+
're-estimated')
|
|
673
|
+
else:
|
|
674
|
+
label.setStyleSheet(STYLE_GREEN_BACKGROUND)
|
|
675
|
+
else:
|
|
676
|
+
label.setStyleSheet(STYLE_RED_BACKGROUND)
|
|
677
|
+
label.setToolTip('')
|
|
678
|
+
|
|
679
|
+
def _load_sys_config_button_clicked(self):
|
|
680
|
+
names = QFileDialog.getOpenFileName(self, 'Open file', self._helper.current_folder, FILE_REGEX_YAML)
|
|
681
|
+
|
|
682
|
+
if names[0] == '':
|
|
683
|
+
return
|
|
684
|
+
|
|
685
|
+
self._helper.current_folder = os.path.dirname(names[0])
|
|
686
|
+
|
|
687
|
+
if self._lh_config_writer is not None:
|
|
688
|
+
self._lh_config_writer.write_and_store_config_from_file(self._new_system_config_written_to_cf_signal.emit,
|
|
689
|
+
names[0])
|
|
690
|
+
|
|
691
|
+
def _save_sys_config_button_clicked(self):
|
|
692
|
+
# Get calibration data from the Crazyflie to complete the system config data set
|
|
693
|
+
# When the data is ready we get a callback on _calibration_read
|
|
694
|
+
self._lh_memory_helper.read_all_calibs(self._calibration_read_signal.emit)
|
|
695
|
+
|
|
696
|
+
def _calibration_read_cb(self, calibs):
|
|
697
|
+
# Got calibration data from the CF, we have the full system configuration
|
|
698
|
+
system_type = self._system_type_dialog.get_system_type()
|
|
699
|
+
self._save_sys_config(self._lh_geos, calibs, system_type)
|
|
700
|
+
|
|
701
|
+
def _save_sys_config(self, geos, calibs, system_type):
|
|
702
|
+
names = QFileDialog.getSaveFileName(self, 'Save file', self._helper.current_folder, FILE_REGEX_YAML)
|
|
703
|
+
|
|
704
|
+
if names[0] == '':
|
|
705
|
+
return
|
|
706
|
+
|
|
707
|
+
self._helper.current_folder = os.path.dirname(names[0])
|
|
708
|
+
|
|
709
|
+
if not names[0].endswith(".yaml") and names[0].find(".") < 0:
|
|
710
|
+
filename = names[0] + ".yaml"
|
|
711
|
+
else:
|
|
712
|
+
filename = names[0]
|
|
713
|
+
|
|
714
|
+
LighthouseConfigFileManager.write(filename, geos=geos, calibs=calibs, system_type=system_type)
|