cfclient 2024.3__py3-none-any.whl → 2024.7__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.
Potentially problematic release.
This version of cfclient might be problematic. Click here for more details.
- cfclient/resources/log_param_doc.json +1 -1
- cfclient/ui/dialogs/inputconfigdialogue.py +2 -2
- cfclient/ui/tabs/FlightTab.py +1 -1
- cfclient/ui/tabs/ParamTab.py +142 -2
- cfclient/ui/tabs/paramTab.ui +38 -2
- cfclient/ui/widgets/plotter.ui +39 -47
- cfclient/ui/widgets/plotwidget.py +54 -6
- cfclient/utils/logconfigreader.py +14 -21
- {cfclient-2024.3.dist-info → cfclient-2024.7.dist-info}/METADATA +2 -2
- {cfclient-2024.3.dist-info → cfclient-2024.7.dist-info}/RECORD +14 -14
- {cfclient-2024.3.dist-info → cfclient-2024.7.dist-info}/WHEEL +1 -1
- {cfclient-2024.3.dist-info → cfclient-2024.7.dist-info}/LICENSE.txt +0 -0
- {cfclient-2024.3.dist-info → cfclient-2024.7.dist-info}/entry_points.txt +0 -0
- {cfclient-2024.3.dist-info → cfclient-2024.7.dist-info}/top_level.txt +0 -0
|
@@ -210,7 +210,7 @@ class InputConfigDialogue(QtWidgets.QWidget, inputconfig_widget_class):
|
|
|
210
210
|
self._combined_button = QtWidgets.QPushButton('Combined Axis ' +
|
|
211
211
|
'Detection')
|
|
212
212
|
self.cancelButton = QtWidgets.QPushButton('Cancel')
|
|
213
|
-
self._popup.addButton(self.cancelButton, QMessageBox.DestructiveRole)
|
|
213
|
+
self._popup.addButton(self.cancelButton, QMessageBox.ButtonRole.DestructiveRole)
|
|
214
214
|
self._popup.setWindowTitle(caption)
|
|
215
215
|
self._popup.setWindowFlags(Qt.WindowType.Dialog | Qt.WindowType.MSWindowsFixedSizeDialogHint)
|
|
216
216
|
if len(directions) > 1:
|
|
@@ -219,7 +219,7 @@ class InputConfigDialogue(QtWidgets.QWidget, inputconfig_widget_class):
|
|
|
219
219
|
self._combined_button.setCheckable(True)
|
|
220
220
|
self._combined_button.blockSignals(True)
|
|
221
221
|
self._popup.addButton(self._combined_button,
|
|
222
|
-
QMessageBox.ActionRole)
|
|
222
|
+
QMessageBox.ButtonRole.ActionRole)
|
|
223
223
|
self._popup.setText(message)
|
|
224
224
|
self._popup.show()
|
|
225
225
|
|
cfclient/ui/tabs/FlightTab.py
CHANGED
|
@@ -715,7 +715,7 @@ class FlightTab(TabToolbox, flight_tab_class):
|
|
|
715
715
|
self.targetHeight.setEnabled(enabled)
|
|
716
716
|
print('Chaning enable for target height: %s' % enabled)
|
|
717
717
|
else:
|
|
718
|
-
self._helper.cf.param.set_value("flightmode.althold",
|
|
718
|
+
self._helper.cf.param.set_value("flightmode.althold", int(enabled))
|
|
719
719
|
|
|
720
720
|
def alt1_updated(self, state):
|
|
721
721
|
if state:
|
cfclient/ui/tabs/ParamTab.py
CHANGED
|
@@ -31,17 +31,20 @@ to edit them.
|
|
|
31
31
|
"""
|
|
32
32
|
|
|
33
33
|
import logging
|
|
34
|
+
from threading import Event
|
|
34
35
|
|
|
35
36
|
from PyQt6 import uic, QtCore
|
|
36
37
|
from PyQt6.QtCore import QSortFilterProxyModel, Qt, pyqtSignal
|
|
37
38
|
from PyQt6.QtCore import QAbstractItemModel, QModelIndex, QVariant
|
|
38
39
|
from PyQt6.QtGui import QBrush, QColor
|
|
39
|
-
from PyQt6.QtWidgets import QHeaderView
|
|
40
|
+
from PyQt6.QtWidgets import QHeaderView, QFileDialog, QMessageBox
|
|
40
41
|
|
|
41
42
|
from cflib.crazyflie.param import PersistentParamState
|
|
43
|
+
from cflib.localization import ParamFileManager
|
|
42
44
|
|
|
43
45
|
import cfclient
|
|
44
46
|
from cfclient.ui.tab_toolbox import TabToolbox
|
|
47
|
+
from cfclient.utils.logconfigreader import FILE_REGEX_YAML
|
|
45
48
|
|
|
46
49
|
__author__ = 'Bitcraze AB'
|
|
47
50
|
__all__ = ['ParamTab']
|
|
@@ -316,6 +319,13 @@ class ParamTab(TabToolbox, param_tab_class):
|
|
|
316
319
|
self.paramTree.header().setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents)
|
|
317
320
|
self.paramTree.selectionModel().selectionChanged.connect(self._paramChanged)
|
|
318
321
|
|
|
322
|
+
self._load_param_button.clicked.connect(self._load_param_button_clicked)
|
|
323
|
+
self._dump_param_button.clicked.connect(self._dump_param_button_clicked)
|
|
324
|
+
self._clear_param_button.clicked.connect(self._clear_stored_persistent_params_button_clicked)
|
|
325
|
+
|
|
326
|
+
self._is_connected = False
|
|
327
|
+
self._update_param_io_buttons()
|
|
328
|
+
|
|
319
329
|
def _param_default_cb(self, default_value):
|
|
320
330
|
if default_value is not None:
|
|
321
331
|
self.defaultValue.setText(str(default_value))
|
|
@@ -408,14 +418,144 @@ class ParamTab(TabToolbox, param_tab_class):
|
|
|
408
418
|
if elem.is_persistent():
|
|
409
419
|
self.cf.param.persistent_get_state(complete, lambda _, state: self._persistent_state_signal.emit(state))
|
|
410
420
|
|
|
421
|
+
def _update_param_io_buttons(self):
|
|
422
|
+
enabled = self._is_connected
|
|
423
|
+
self._load_param_button.setEnabled(enabled)
|
|
424
|
+
self._dump_param_button.setEnabled(enabled)
|
|
425
|
+
self._clear_param_button.setEnabled(enabled)
|
|
426
|
+
|
|
427
|
+
def _load_param_button_clicked(self):
|
|
428
|
+
names = QFileDialog.getOpenFileName(self, 'Open file', cfclient.config_path, FILE_REGEX_YAML)
|
|
429
|
+
|
|
430
|
+
if names[0] == '':
|
|
431
|
+
return
|
|
432
|
+
filename = names[0]
|
|
433
|
+
parameters = ParamFileManager.read(filename)
|
|
434
|
+
|
|
435
|
+
def _is_persistent_stored_callback(complete_name, success):
|
|
436
|
+
if not success:
|
|
437
|
+
print(f'Persistent params: failed to store {complete_name}!')
|
|
438
|
+
QMessageBox.about(self, 'Warning', f'Failed to persistently store {complete_name}!')
|
|
439
|
+
else:
|
|
440
|
+
print(f'Persistent params: stored {complete_name}!')
|
|
441
|
+
|
|
442
|
+
_set_param_names = []
|
|
443
|
+
for param, state in parameters.items():
|
|
444
|
+
if state.is_stored:
|
|
445
|
+
try:
|
|
446
|
+
self.cf.param.set_value(param, state.stored_value)
|
|
447
|
+
_set_param_names.append(param)
|
|
448
|
+
except Exception:
|
|
449
|
+
print(f'Failed to set {param}!')
|
|
450
|
+
QMessageBox.about(self, 'Warning', f'Failed to set {param}!')
|
|
451
|
+
print(f'Set {param}!')
|
|
452
|
+
self.cf.param.persistent_store(param, _is_persistent_stored_callback)
|
|
453
|
+
|
|
454
|
+
self._update_param_io_buttons()
|
|
455
|
+
dlg = QMessageBox(self)
|
|
456
|
+
dlg.setWindowTitle("Info")
|
|
457
|
+
_parameters_and_values = [f"{_param_name}:{parameters[_param_name].stored_value}"
|
|
458
|
+
for _param_name in _set_param_names]
|
|
459
|
+
dlg.setText('Loaded persistent parameters from file:\n' + "\n".join(_parameters_and_values))
|
|
460
|
+
dlg.setIcon(QMessageBox.Icon.NoIcon)
|
|
461
|
+
dlg.exec()
|
|
462
|
+
|
|
463
|
+
def _get_persistent_state(self, complete_param_name):
|
|
464
|
+
wait_for_callback_event = Event()
|
|
465
|
+
state_value = None
|
|
466
|
+
|
|
467
|
+
def state_callback(complete_name, value):
|
|
468
|
+
nonlocal state_value
|
|
469
|
+
state_value = value
|
|
470
|
+
wait_for_callback_event.set()
|
|
471
|
+
|
|
472
|
+
self.cf.param.persistent_get_state(complete_param_name, state_callback)
|
|
473
|
+
wait_for_callback_event.wait()
|
|
474
|
+
return state_value
|
|
475
|
+
|
|
476
|
+
def _get_all_persistent_param_names(self):
|
|
477
|
+
persistent_params = []
|
|
478
|
+
for group_name, params in self.cf.param.toc.toc.items():
|
|
479
|
+
for param_name, element in params.items():
|
|
480
|
+
if element.is_persistent():
|
|
481
|
+
complete_name = group_name + '.' + param_name
|
|
482
|
+
persistent_params.append(complete_name)
|
|
483
|
+
|
|
484
|
+
return persistent_params
|
|
485
|
+
|
|
486
|
+
def _get_all_stored_persistent_param_names(self):
|
|
487
|
+
persistent_params = self._get_all_persistent_param_names()
|
|
488
|
+
stored_params = []
|
|
489
|
+
for complete_name in persistent_params:
|
|
490
|
+
state = self._get_persistent_state(complete_name)
|
|
491
|
+
if state.is_stored:
|
|
492
|
+
stored_params.append(complete_name)
|
|
493
|
+
return stored_params
|
|
494
|
+
|
|
495
|
+
def _get_all_stored_persistent_params(self):
|
|
496
|
+
persistent_params = self._get_all_persistent_param_names()
|
|
497
|
+
stored_params = {}
|
|
498
|
+
for complete_name in persistent_params:
|
|
499
|
+
state = self._get_persistent_state(complete_name)
|
|
500
|
+
if state.is_stored:
|
|
501
|
+
stored_params[complete_name] = state
|
|
502
|
+
return stored_params
|
|
503
|
+
|
|
504
|
+
def _dump_param_button_clicked(self):
|
|
505
|
+
stored_persistent_params = self._get_all_stored_persistent_params()
|
|
506
|
+
names = QFileDialog.getSaveFileName(self, 'Save file', cfclient.config_path, FILE_REGEX_YAML)
|
|
507
|
+
if names[0] == '':
|
|
508
|
+
return
|
|
509
|
+
if not names[0].endswith(".yaml"):
|
|
510
|
+
filename = names[0] + ".yaml"
|
|
511
|
+
else:
|
|
512
|
+
filename = names[0]
|
|
513
|
+
|
|
514
|
+
ParamFileManager.write(filename, stored_persistent_params)
|
|
515
|
+
dlg = QMessageBox(self)
|
|
516
|
+
dlg.setWindowTitle('Info')
|
|
517
|
+
_parameters_and_values = [f"{_param_name}: {stored_persistent_params[_param_name].stored_value}"
|
|
518
|
+
for _param_name in stored_persistent_params.keys()]
|
|
519
|
+
dlg.setText('Dumped persistent parameters to file:\n' + "\n".join(_parameters_and_values))
|
|
520
|
+
dlg.setIcon(QMessageBox.Icon.NoIcon)
|
|
521
|
+
dlg.exec()
|
|
522
|
+
|
|
523
|
+
def _clear_persistent_parameter(self, complete_param_name):
|
|
524
|
+
wait_for_callback_event = Event()
|
|
525
|
+
|
|
526
|
+
def is_stored_cleared(complete_name, success):
|
|
527
|
+
if success:
|
|
528
|
+
print(f'Persistent params: cleared {complete_name}!')
|
|
529
|
+
else:
|
|
530
|
+
print(f'Persistent params: failed to clear {complete_name}!')
|
|
531
|
+
wait_for_callback_event.set()
|
|
532
|
+
|
|
533
|
+
self.cf.param.persistent_clear(complete_param_name, callback=is_stored_cleared)
|
|
534
|
+
wait_for_callback_event.wait()
|
|
535
|
+
|
|
536
|
+
def _clear_stored_persistent_params_button_clicked(self):
|
|
537
|
+
dlg = QMessageBox(self)
|
|
538
|
+
dlg.setWindowTitle("Clear Stored Parameters Confirmation")
|
|
539
|
+
dlg.setText("Are you sure you want to clear your stored persistent parameter?")
|
|
540
|
+
dlg.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
|
|
541
|
+
button = dlg.exec()
|
|
542
|
+
|
|
543
|
+
if button == QMessageBox.StandardButton.Yes:
|
|
544
|
+
stored_persistent_params = self._get_all_stored_persistent_param_names()
|
|
545
|
+
for complete_name in stored_persistent_params:
|
|
546
|
+
self._clear_persistent_parameter(complete_name)
|
|
547
|
+
|
|
411
548
|
def _connected(self, link_uri):
|
|
412
549
|
self._model.reset()
|
|
413
550
|
self._model.set_toc(self.cf.param.toc.toc, self._helper.cf)
|
|
414
551
|
self._model.set_enabled(True)
|
|
415
552
|
self._helper.cf.param.request_update_of_all_params()
|
|
553
|
+
self._is_connected = True
|
|
554
|
+
self._update_param_io_buttons()
|
|
416
555
|
|
|
417
556
|
def _disconnected(self, link_uri):
|
|
418
|
-
|
|
557
|
+
self._is_connected = False
|
|
558
|
+
self._update_param_io_buttons()
|
|
419
559
|
self._model.reset()
|
|
420
560
|
self._paramChanged()
|
|
421
561
|
self._model.set_enabled(False)
|
cfclient/ui/tabs/paramTab.ui
CHANGED
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
</item>
|
|
23
23
|
<item row="1" column="0">
|
|
24
24
|
<widget class="QTreeView" name="paramTree"/>
|
|
25
|
-
|
|
26
|
-
<item row="
|
|
25
|
+
</item>
|
|
26
|
+
<item row="1" column="1" rowspan="2">
|
|
27
27
|
<widget class="QFrame" name="paramDetails">
|
|
28
28
|
<property name="frameShape">
|
|
29
29
|
<enum>QFrame::Box</enum>
|
|
@@ -182,6 +182,42 @@
|
|
|
182
182
|
</layout>
|
|
183
183
|
</widget>
|
|
184
184
|
</item>
|
|
185
|
+
<item row="0" column="1">
|
|
186
|
+
<layout class="QVBoxLayout" name="verticalLayout">
|
|
187
|
+
<item>
|
|
188
|
+
<widget class="QLabel" name="label">
|
|
189
|
+
<property name="text">
|
|
190
|
+
<string>Persistent Parameter Management</string>
|
|
191
|
+
</property>
|
|
192
|
+
</widget>
|
|
193
|
+
</item>
|
|
194
|
+
<item>
|
|
195
|
+
<layout class="QHBoxLayout" name="horizontalLayout">
|
|
196
|
+
<item>
|
|
197
|
+
<widget class="QPushButton" name="_dump_param_button">
|
|
198
|
+
<property name="text">
|
|
199
|
+
<string>Dump</string>
|
|
200
|
+
</property>
|
|
201
|
+
</widget>
|
|
202
|
+
</item>
|
|
203
|
+
<item>
|
|
204
|
+
<widget class="QPushButton" name="_load_param_button">
|
|
205
|
+
<property name="text">
|
|
206
|
+
<string>Load</string>
|
|
207
|
+
</property>
|
|
208
|
+
</widget>
|
|
209
|
+
</item>
|
|
210
|
+
<item>
|
|
211
|
+
<widget class="QPushButton" name="_clear_param_button">
|
|
212
|
+
<property name="text">
|
|
213
|
+
<string>Clear</string>
|
|
214
|
+
</property>
|
|
215
|
+
</widget>
|
|
216
|
+
</item>
|
|
217
|
+
</layout>
|
|
218
|
+
</item>
|
|
219
|
+
</layout>
|
|
220
|
+
</item>
|
|
185
221
|
</layout>
|
|
186
222
|
</widget>
|
|
187
223
|
<resources/>
|
cfclient/ui/widgets/plotter.ui
CHANGED
|
@@ -31,20 +31,51 @@
|
|
|
31
31
|
<layout class="QGridLayout" name="gridLayout">
|
|
32
32
|
<item row="1" column="0">
|
|
33
33
|
<layout class="QGridLayout" name="gridLayout_5">
|
|
34
|
+
<item row="0" column="0">
|
|
35
|
+
<widget class="QRadioButton" name="_enable_range_x">
|
|
36
|
+
<property name="text">
|
|
37
|
+
<string>Range (s)</string>
|
|
38
|
+
</property>
|
|
39
|
+
<property name="autoExclusive">
|
|
40
|
+
<bool>false</bool>
|
|
41
|
+
</property>
|
|
42
|
+
</widget>
|
|
43
|
+
</item>
|
|
34
44
|
<item row="0" column="1">
|
|
35
|
-
<widget class="
|
|
45
|
+
<widget class="QDoubleSpinBox" name="_range_x_min">
|
|
36
46
|
<property name="enabled">
|
|
37
47
|
<bool>false</bool>
|
|
38
48
|
</property>
|
|
39
|
-
<property name="
|
|
40
|
-
<
|
|
49
|
+
<property name="minimum">
|
|
50
|
+
<double>0</double>
|
|
51
|
+
</property>
|
|
52
|
+
<property name="maximum">
|
|
53
|
+
<double>86400</double>
|
|
54
|
+
</property>
|
|
55
|
+
<property name="singleStep">
|
|
56
|
+
<double>1</double>
|
|
57
|
+
</property>
|
|
58
|
+
<property name="value">
|
|
59
|
+
<double>0</double>
|
|
41
60
|
</property>
|
|
42
61
|
</widget>
|
|
43
62
|
</item>
|
|
44
|
-
<item row="0" column="
|
|
45
|
-
<widget class="
|
|
46
|
-
<property name="
|
|
47
|
-
<
|
|
63
|
+
<item row="0" column="4">
|
|
64
|
+
<widget class="QDoubleSpinBox" name="_range_x_max">
|
|
65
|
+
<property name="enabled">
|
|
66
|
+
<bool>false</bool>
|
|
67
|
+
</property>
|
|
68
|
+
<property name="minimum">
|
|
69
|
+
<double>0</double>
|
|
70
|
+
</property>
|
|
71
|
+
<property name="maximum">
|
|
72
|
+
<double>86400</double>
|
|
73
|
+
</property>
|
|
74
|
+
<property name="singleStep">
|
|
75
|
+
<double>1</double>
|
|
76
|
+
</property>
|
|
77
|
+
<property name="value">
|
|
78
|
+
<double>60</double>
|
|
48
79
|
</property>
|
|
49
80
|
</widget>
|
|
50
81
|
</item>
|
|
@@ -70,7 +101,7 @@
|
|
|
70
101
|
<string/>
|
|
71
102
|
</property>
|
|
72
103
|
<property name="maximum">
|
|
73
|
-
<number>
|
|
104
|
+
<number>3000</number>
|
|
74
105
|
</property>
|
|
75
106
|
<property name="singleStep">
|
|
76
107
|
<number>100</number>
|
|
@@ -80,47 +111,8 @@
|
|
|
80
111
|
</property>
|
|
81
112
|
</widget>
|
|
82
113
|
</item>
|
|
83
|
-
<item row="0" column="3">
|
|
84
|
-
<widget class="QLineEdit" name="_range_x_max">
|
|
85
|
-
<property name="enabled">
|
|
86
|
-
<bool>false</bool>
|
|
87
|
-
</property>
|
|
88
|
-
<property name="text">
|
|
89
|
-
<string>1000</string>
|
|
90
|
-
</property>
|
|
91
|
-
</widget>
|
|
92
|
-
</item>
|
|
93
|
-
<item row="0" column="0">
|
|
94
|
-
<widget class="QRadioButton" name="_enable_range_x">
|
|
95
|
-
<property name="enabled">
|
|
96
|
-
<bool>false</bool>
|
|
97
|
-
</property>
|
|
98
|
-
<property name="text">
|
|
99
|
-
<string>Range</string>
|
|
100
|
-
</property>
|
|
101
|
-
<property name="autoExclusive">
|
|
102
|
-
<bool>false</bool>
|
|
103
|
-
</property>
|
|
104
|
-
</widget>
|
|
105
|
-
</item>
|
|
106
|
-
<item row="3" column="0">
|
|
107
|
-
<widget class="QRadioButton" name="_enable_manual_x">
|
|
108
|
-
<property name="enabled">
|
|
109
|
-
<bool>false</bool>
|
|
110
|
-
</property>
|
|
111
|
-
<property name="text">
|
|
112
|
-
<string>Manual</string>
|
|
113
|
-
</property>
|
|
114
|
-
<property name="autoExclusive">
|
|
115
|
-
<bool>false</bool>
|
|
116
|
-
</property>
|
|
117
|
-
</widget>
|
|
118
|
-
</item>
|
|
119
114
|
<item row="2" column="0">
|
|
120
115
|
<widget class="QRadioButton" name="_enable_seconds_x">
|
|
121
|
-
<property name="enabled">
|
|
122
|
-
<bool>false</bool>
|
|
123
|
-
</property>
|
|
124
116
|
<property name="text">
|
|
125
117
|
<string>Seconds</string>
|
|
126
118
|
</property>
|
|
@@ -156,12 +156,17 @@ class PlotWidget(QtWidgets.QWidget, plot_widget_class):
|
|
|
156
156
|
self._enable_samples_x.setChecked(True)
|
|
157
157
|
self._last_ts = None
|
|
158
158
|
self._dtime = None
|
|
159
|
+
self._first_ts = None
|
|
159
160
|
|
|
160
161
|
self._x_range = (
|
|
161
162
|
float(self._range_x_min.text()), float(self._range_x_max.text()))
|
|
162
163
|
self._nbr_samples = int(self._nbr_of_samples_x.text())
|
|
163
|
-
|
|
164
164
|
self._nbr_of_samples_x.valueChanged.connect(self._nbr_samples_changed)
|
|
165
|
+
self._nbr_seconds = int(self._nbr_of_seconds_x.text())
|
|
166
|
+
self._nbr_of_seconds_x.valueChanged.connect(self._nbr_of_seconds_changed)
|
|
167
|
+
self._range_x_min.valueChanged.connect(self._x_range_changed)
|
|
168
|
+
self._range_x_max.valueChanged.connect(self._x_range_changed)
|
|
169
|
+
|
|
165
170
|
self._range_y_min.valueChanged.connect(self._y_range_changed)
|
|
166
171
|
self._range_y_max.valueChanged.connect(self._y_range_changed)
|
|
167
172
|
|
|
@@ -175,8 +180,8 @@ class PlotWidget(QtWidgets.QWidget, plot_widget_class):
|
|
|
175
180
|
self._x_btn_group.addButton(self._enable_range_x)
|
|
176
181
|
self._x_btn_group.addButton(self._enable_samples_x)
|
|
177
182
|
self._x_btn_group.addButton(self._enable_seconds_x)
|
|
178
|
-
self._x_btn_group.addButton(self._enable_manual_x)
|
|
179
183
|
self._x_btn_group.setExclusive(True)
|
|
184
|
+
self._x_btn_group.buttonClicked.connect(self._x_mode_change)
|
|
180
185
|
|
|
181
186
|
self._draw_graph = True
|
|
182
187
|
self._auto_redraw.stateChanged.connect(self._auto_redraw_change)
|
|
@@ -188,6 +193,20 @@ class PlotWidget(QtWidgets.QWidget, plot_widget_class):
|
|
|
188
193
|
else:
|
|
189
194
|
self._draw_graph = True
|
|
190
195
|
|
|
196
|
+
def _x_mode_change(self, box):
|
|
197
|
+
"""Callback when user changes the X-axis mode"""
|
|
198
|
+
self._nbr_of_samples_x.setEnabled(False)
|
|
199
|
+
self._nbr_of_seconds_x.setEnabled(False)
|
|
200
|
+
self._range_x_min.setEnabled(False)
|
|
201
|
+
self._range_x_max.setEnabled(False)
|
|
202
|
+
if box == self._enable_range_x:
|
|
203
|
+
self._range_x_min.setEnabled(True)
|
|
204
|
+
self._range_x_max.setEnabled(True)
|
|
205
|
+
elif box == self._enable_samples_x:
|
|
206
|
+
self._nbr_of_samples_x.setEnabled(True)
|
|
207
|
+
elif box == self._enable_seconds_x:
|
|
208
|
+
self._nbr_of_seconds_x.setEnabled(True)
|
|
209
|
+
|
|
191
210
|
def _y_mode_change(self, box):
|
|
192
211
|
"""Callback when user changes the Y-axis mode"""
|
|
193
212
|
if box == self._enable_range_y:
|
|
@@ -228,6 +247,14 @@ class PlotWidget(QtWidgets.QWidget, plot_widget_class):
|
|
|
228
247
|
"""Callback when user changes the number of samples to be shown"""
|
|
229
248
|
self._nbr_samples = val
|
|
230
249
|
|
|
250
|
+
def _nbr_of_seconds_changed(self, val):
|
|
251
|
+
"""Callback when user changes the number of seconds to be shown"""
|
|
252
|
+
self._nbr_seconds = val
|
|
253
|
+
|
|
254
|
+
def _x_range_changed(self, val):
|
|
255
|
+
self._range_x_min.setMaximum(self._range_x_max.value()-1)
|
|
256
|
+
self._range_x_max.setMinimum(self._range_x_min.value()+1)
|
|
257
|
+
|
|
231
258
|
def set_title(self, title):
|
|
232
259
|
"""
|
|
233
260
|
Set the title of the plot.
|
|
@@ -254,11 +281,15 @@ class PlotWidget(QtWidgets.QWidget, plot_widget_class):
|
|
|
254
281
|
pairs
|
|
255
282
|
ts - timestamp of the data in ms
|
|
256
283
|
"""
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
284
|
+
|
|
285
|
+
if self._first_ts is None:
|
|
286
|
+
self._first_ts = ts
|
|
287
|
+
|
|
288
|
+
if self._last_ts is None:
|
|
289
|
+
self._dtime = 1e12
|
|
290
|
+
else:
|
|
260
291
|
self._dtime = ts - self._last_ts
|
|
261
|
-
|
|
292
|
+
self._last_ts = ts
|
|
262
293
|
|
|
263
294
|
x_min_limit = 0
|
|
264
295
|
x_max_limit = 0
|
|
@@ -266,6 +297,17 @@ class PlotWidget(QtWidgets.QWidget, plot_widget_class):
|
|
|
266
297
|
if self._enable_samples_x.isChecked():
|
|
267
298
|
x_min_limit = max(0, self._last_item - self._nbr_samples)
|
|
268
299
|
x_max_limit = max(self._last_item, self._nbr_samples)
|
|
300
|
+
self._range_x_min.setValue(int(self._first_ts + x_min_limit * self._dtime)/1000.)
|
|
301
|
+
self._range_x_max.setValue(int(self._first_ts + x_max_limit * self._dtime)/1000.)
|
|
302
|
+
elif self._enable_seconds_x.isChecked():
|
|
303
|
+
x_min_limit = max(0, int(((self._last_ts - self._first_ts) - self._nbr_seconds * 1000.) / self._dtime))
|
|
304
|
+
x_max_limit = max(0, self._last_item)
|
|
305
|
+
print(self._first_ts, x_min_limit, x_max_limit, self._dtime, self._nbr_seconds)
|
|
306
|
+
self._range_x_min.setValue((self._first_ts + x_min_limit * self._dtime)/1000.)
|
|
307
|
+
self._range_x_max.setValue((self._first_ts + x_min_limit * self._dtime)/1000. + self._nbr_seconds)
|
|
308
|
+
elif self._enable_range_x.isChecked():
|
|
309
|
+
x_min_limit = max(0, int((self._range_x_min.value() * 1000. - self._first_ts) / self._dtime))
|
|
310
|
+
x_max_limit = max(0, int((self._range_x_max.value() * 1000. - self._first_ts) / self._dtime))
|
|
269
311
|
|
|
270
312
|
for name in self._items:
|
|
271
313
|
self._items[name].add_point(data[name], ts)
|
|
@@ -277,6 +319,11 @@ class PlotWidget(QtWidgets.QWidget, plot_widget_class):
|
|
|
277
319
|
if (self._enable_samples_x.isChecked() and self._dtime and
|
|
278
320
|
self._last_item < self._nbr_samples):
|
|
279
321
|
self._x_max = self._x_min + self._nbr_samples * self._dtime
|
|
322
|
+
elif (self._enable_seconds_x.isChecked() and self._dtime and
|
|
323
|
+
self._last_item < int(self._nbr_seconds * 1000. / self._dtime)):
|
|
324
|
+
self._x_max = self._x_min + self._nbr_seconds * 1000.
|
|
325
|
+
elif (self._enable_range_x.isChecked() and self._dtime) and self._last_item < x_max_limit:
|
|
326
|
+
self._x_max = self._x_min + (x_max_limit - x_min_limit) * self._dtime
|
|
280
327
|
|
|
281
328
|
self._last_item = self._last_item + 1
|
|
282
329
|
self._plot_widget.getViewBox().setRange(
|
|
@@ -292,6 +339,7 @@ class PlotWidget(QtWidgets.QWidget, plot_widget_class):
|
|
|
292
339
|
self._items = {}
|
|
293
340
|
self._last_item = 0
|
|
294
341
|
self._last_ts = None
|
|
342
|
+
self._first_ts = None
|
|
295
343
|
self._dtime = None
|
|
296
344
|
self._plot_widget.clear()
|
|
297
345
|
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
# +------+ / /_/ / / /_/ /__/ / / /_/ / / /_/ __/
|
|
8
8
|
# || || /_____/_/\__/\___/_/ \__,_/ /___/\___/
|
|
9
9
|
#
|
|
10
|
-
# Copyright (C) 2011-
|
|
10
|
+
# Copyright (C) 2011-2024 Bitcraze AB
|
|
11
11
|
#
|
|
12
12
|
# Crazyflie Nano Quadcopter Client
|
|
13
13
|
#
|
|
@@ -27,15 +27,11 @@
|
|
|
27
27
|
# MA 02110-1301, USA.
|
|
28
28
|
|
|
29
29
|
"""
|
|
30
|
-
The
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
This module can use different drivers for reading the input device data.
|
|
34
|
-
Currently it can just use the PySdl2 driver but in the future there will be a
|
|
35
|
-
Linux and Windows driver that can bypass PySdl2.
|
|
30
|
+
The logconfigreader module is responsible for reading and parsing log
|
|
31
|
+
configuration data. This data is used to control what information is logged
|
|
32
|
+
in the client.
|
|
36
33
|
"""
|
|
37
34
|
|
|
38
|
-
import glob
|
|
39
35
|
import json
|
|
40
36
|
import logging
|
|
41
37
|
import os
|
|
@@ -68,10 +64,7 @@ class LogConfigReader():
|
|
|
68
64
|
# Check if user config exists, otherwise copy files
|
|
69
65
|
if (not os.path.exists(cfclient.config_path + "/log")):
|
|
70
66
|
logger.info("No user config found, copying dist files")
|
|
71
|
-
|
|
72
|
-
for f in glob.glob(
|
|
73
|
-
cfclient.module_path + "/configs/log/[A-Za-z]*.json"):
|
|
74
|
-
shutil.copy2(f, cfclient.config_path + "/log")
|
|
67
|
+
shutil.copytree(cfclient.module_path + "/configs/log", cfclient.config_path + "/log")
|
|
75
68
|
self._cf = crazyflie
|
|
76
69
|
self._cf.connected.add_callback(self._connected)
|
|
77
70
|
|
|
@@ -206,27 +199,27 @@ class LogConfigReader():
|
|
|
206
199
|
self._log_configs = {'Default': []}
|
|
207
200
|
log_path = os.path.join(cfclient.config_path, 'log')
|
|
208
201
|
|
|
209
|
-
for
|
|
202
|
+
for category in os.listdir(log_path):
|
|
210
203
|
|
|
211
|
-
|
|
204
|
+
category_path = os.path.join(log_path, category)
|
|
212
205
|
|
|
213
206
|
try:
|
|
214
|
-
if (os.path.isdir(
|
|
207
|
+
if (os.path.isdir(category_path)):
|
|
215
208
|
# create a new cathegory
|
|
216
|
-
self._log_configs[
|
|
217
|
-
for conf in os.listdir(
|
|
209
|
+
self._log_configs[category] = []
|
|
210
|
+
for conf in os.listdir(category_path):
|
|
218
211
|
if conf.endswith('.json'):
|
|
219
|
-
conf_path = os.path.join(
|
|
212
|
+
conf_path = os.path.join(category_path, conf)
|
|
220
213
|
log_conf = self._get_conf(conf_path)
|
|
221
214
|
|
|
222
215
|
# add the log configuration to the cathegory
|
|
223
|
-
self._log_configs[
|
|
216
|
+
self._log_configs[category].append(log_conf)
|
|
224
217
|
|
|
225
218
|
else:
|
|
226
219
|
# if it's not a directory, the log config is placed
|
|
227
220
|
# in the 'Default' cathegory
|
|
228
|
-
if
|
|
229
|
-
log_conf = self._get_conf(
|
|
221
|
+
if category_path.endswith('.json'):
|
|
222
|
+
log_conf = self._get_conf(category_path)
|
|
230
223
|
self._log_configs['Default'].append(log_conf)
|
|
231
224
|
|
|
232
225
|
except Exception as e:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: cfclient
|
|
3
|
-
Version: 2024.
|
|
3
|
+
Version: 2024.7
|
|
4
4
|
Summary: Bitcraze Cazyflie quadcopter client
|
|
5
5
|
Home-page: http://www.bitcraze.io
|
|
6
6
|
Author: Bitcraze team
|
|
@@ -11,7 +11,7 @@ Classifier: Programming Language :: Python :: 3.4
|
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.5
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
License-File: LICENSE.txt
|
|
14
|
-
Requires-Dist: cflib >=0.1.
|
|
14
|
+
Requires-Dist: cflib >=0.1.26
|
|
15
15
|
Requires-Dist: setuptools
|
|
16
16
|
Requires-Dist: appdirs ~=1.4.0
|
|
17
17
|
Requires-Dist: pyzmq ~=25.0
|