Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 28 additions & 2 deletions src/cfclient/ui/dialogs/inputconfigdialogue.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
from PyQt6.QtCore import QTimer
from PyQt6.QtCore import pyqtSignal
from PyQt6.QtWidgets import QMessageBox
from cfclient.utils.config import Config
from cfclient.utils.config_manager import ConfigManager
from PyQt6 import QtWidgets
from PyQt6 import uic
Expand All @@ -51,6 +52,8 @@

class InputConfigDialogue(QtWidgets.QWidget, inputconfig_widget_class):

closed = pyqtSignal()

def __init__(self, joystickReader, *args):
super(InputConfigDialogue, self).__init__(*args)
self.setupUi(self)
Expand Down Expand Up @@ -171,6 +174,9 @@ def __init__(self, joystickReader, *args):

self._map = {}
self._saved_open_device = None
self._original_input_map = None
self._original_input_map_name = None
self._config_was_saved = False

@staticmethod
def _scale(max_value, value):
Expand Down Expand Up @@ -224,8 +230,13 @@ def _show_config_popup(self, caption, message, directions=[]):
self._popup.show()

def _start_configuration(self):
self._input.enableRawReading(
str(self.inputDeviceSelector.currentText()))
device_name = str(self.inputDeviceSelector.currentText())
dev = self._input._get_device_from_name(device_name)
if dev:
self._original_input_map = dev.input_map
self._original_input_map_name = getattr(
dev, 'input_map_name', None)
self._input.enableRawReading(device_name)
self._input_device_reader.start_reading()
self._populate_config_dropdown()
self.profileCombo.setEnabled(True)
Expand Down Expand Up @@ -391,6 +402,14 @@ def _save_config(self):
if config_name is None:
config_name = str(self.profileCombo.currentText())
ConfigManager().save_config(self._map, config_name)
# Update the name on the raw device so the Flight tab shows it.
# The actual mapping data is already applied via set_raw_input_map.
if self._input._input_device:
self._input._input_device.input_map_name = config_name
device_name = str(self.inputDeviceSelector.currentText())
Config().get("device_config_mapping")[
device_name] = config_name
self._config_was_saved = True
self.close()

def showEvent(self, event):
Expand All @@ -403,8 +422,15 @@ def closeEvent(self, event):
"""Called when dialog is closed"""
self._input.stop_raw_reading()
self._input_device_reader.stop_reading()
if not self._config_was_saved and self._original_input_map is not None:
device_name = str(self.inputDeviceSelector.currentText())
dev = self._input._get_device_from_name(device_name)
if dev:
dev.input_map = self._original_input_map
dev.input_map_name = self._original_input_map_name
# self._input.start_input(self._saved_open_device)
self._input.resume_input()
self.closed.emit()


class DeviceReader(QThread):
Expand Down
142 changes: 98 additions & 44 deletions src/cfclient/ui/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@
from PyQt6.QtGui import QShortcut
from PyQt6.QtGui import QDesktopServices
from PyQt6.QtGui import QPalette
from PyQt6.QtWidgets import QLabel
from PyQt6.QtWidgets import QMenu
from PyQt6.QtWidgets import QMessageBox

Expand Down Expand Up @@ -100,6 +99,7 @@ class MainUI(QtWidgets.QMainWindow, main_window_class):
disconnectedSignal = pyqtSignal(str)
linkQualitySignal = pyqtSignal(float)

_gamepad_device_updated = pyqtSignal(str, str, str)
_input_device_error_signal = pyqtSignal(str)
_input_discovery_signal = pyqtSignal(object)
_log_error_signal = pyqtSignal(object, str)
Expand Down Expand Up @@ -130,18 +130,8 @@ def __init__(self, *args):
self.scanner.interfaceFoundSignal.connect(self.foundInterfaces)
self.scanner.start()

# Create and start the Input Reader
self._statusbar_label = QLabel("No input-device found, insert one to"
" fly.")
self.statusBar().addWidget(self._statusbar_label)

#
# We use this hacky-trick to find out if we are in dark-mode and
# figure out what bgcolor to set from that. We always use the current
# palette forgreound.
#
self.textColor = self._statusbar_label.palette().color(QPalette.ColorRole.WindowText)
self.bgColor = self._statusbar_label.palette().color(QPalette.ColorRole.Window)
self.textColor = self.palette().color(QPalette.ColorRole.WindowText)
self.bgColor = self.palette().color(QPalette.ColorRole.Window)
self.isDark = self.textColor.value() > self.bgColor.value()

self.joystickReader = JoystickReader()
Expand Down Expand Up @@ -552,10 +542,27 @@ def set_preferred_dock_area(self, area):
tab_toolbox = dock_widget.tab_toolbox
tab_toolbox.set_preferred_dock_area(area)

def _rescan_devices(self):
self._gamepad_device_updated.emit("No input device connected", "—", "—")
self._menu_devices.clear()
self._active_device = ""
self.joystickReader.stop_input()

# for c in self._menu_mappings.actions():
# c.setEnabled(False)
# devs = self.joystickReader.available_devices()
# if (len(devs) > 0):
# self.device_discovery(devs)

def _show_input_device_config_dialog(self):
self.inputConfig = InputConfigDialogue(self.joystickReader)
self.inputConfig.closed.connect(self._on_input_config_closed)
self.inputConfig.show()

def _on_input_config_closed(self):
self._sync_input_map_menus()
self._update_input_device_status()

def _show_connect_dialog(self):
self.logConfigDialogue.show()

Expand Down Expand Up @@ -583,6 +590,7 @@ def _update_battery(self, timestamp, data, logconf):
def _connected(self):
self.uiState = UIState.CONNECTED
self._update_ui_state()
self.joystickReader.require_thrust_zero()

Config().set("link_uri", str(self._connectivity_manager.get_interface()))

Expand Down Expand Up @@ -709,41 +717,87 @@ def _mux_selected(self, checked):
if type(dev_node) is QAction and dev_node.isChecked():
dev_node.toggled.emit(True)

self._update_input_device_footer()

def _get_dev_status(self, device):
msg = "{}".format(device.name)
if device.supports_mapping:
map_name = "No input mapping"
if device.input_map:
# Display the friendly name instead of the config file name
map_name = ConfigManager().get_display_name(device.input_map_name)
msg += " ({})".format(map_name)
return msg
self._update_input_device_status()

def _update_input_device_footer(self):
"""Update the footer in the bottom of the UI with status for the
input device and its mapping"""
def _sync_input_map_menus(self):
"""Sync the Input device menu to reflect the current mapping.

msg = ""
After the config dialog saves a new mapping, the menu may be
missing the new entry and still have the old one checked. Walk
every role menu, find the checked device, and make sure its map
sub-menu contains and selects the active mapping name.
"""
for menu in self._all_role_menus:
role_menu = menu["rolemenu"]
for dev_node in role_menu.actions():
if not dev_node.isChecked():
continue
data = dev_node.data()
if data is None or not isinstance(data, tuple):
continue
if len(data) < 3:
continue
(map_menu, device, _mux_menu) = data
if map_menu is None or not device.supports_mapping:
continue

active_name = getattr(device, 'input_map_name', None)
if not active_name:
continue

# Get the QActionGroup from an existing map action
map_actions = map_menu.actions()
map_group = None
if map_actions:
map_group = map_actions[0].actionGroup()

# Add a menu entry if the config is new
existing = {a.text() for a in map_actions}
if active_name not in existing and map_group:
node = QAction(active_name, map_menu,
checkable=True, enabled=True)
node.toggled.connect(self._inputconfig_selected)
node.setData(dev_node)
map_menu.addAction(node)
map_group.addAction(node)

# Check the active mapping without triggering
# _inputconfig_selected (which would reload the config
# from disk and overwrite the live mapping).
# Uncheck all first since blockSignals prevents the
# exclusive QActionGroup from doing it automatically.
for action in map_menu.actions():
action.blockSignals(True)
action.setChecked(action.text() == active_name)
action.blockSignals(False)

def _update_input_device_status(self):
"""Update the gamepad device info in the Flight tab."""

if len(self.joystickReader.available_devices()) > 0:
mux = self.joystickReader._selected_mux
msg = "Using {} mux with ".format(mux.name)
for key in list(mux._devs.keys())[:-1]:
if mux._devs[key]:
msg += "{}, ".format(self._get_dev_status(mux._devs[key]))
mux_name = mux.name
device_names = []
mapping_names = []
for dev in mux._devs.values():
if dev:
device_names.append(dev.name)
if dev.supports_mapping:
mapping_names.append(
ConfigManager().get_display_name(dev.input_map_name)
if dev.input_map else "No mapping")
else:
mapping_names.append("N/A")
else:
msg += "N/A, "
# Last item
key = list(mux._devs.keys())[-1]
if mux._devs[key]:
msg += "{}".format(self._get_dev_status(mux._devs[key]))
else:
msg += "N/A"
device_names.append("N/A")
mapping_names.append("N/A")
device_str = ", ".join(device_names)
mapping_str = ", ".join(mapping_names)
else:
msg = "No input device found"
self._statusbar_label.setText(msg)
device_str = "No input device found"
mapping_str = "—"
mux_name = "—"
self._gamepad_device_updated.emit(device_str, mapping_str, mux_name)

def _inputdevice_selected(self, checked):
"""Called when a new input device has been selected from the menu. The
Expand Down Expand Up @@ -777,7 +831,7 @@ def _inputdevice_selected(self, checked):
self._mapping_support = self.joystickReader.start_input(
device.name,
role_in_mux)
self._update_input_device_footer()
self._update_input_device_status()

def _inputconfig_selected(self, checked):
"""Called when a new configuration has been selected from the menu. The
Expand All @@ -790,7 +844,7 @@ def _inputconfig_selected(self, checked):
selected_mapping = self.sender().config_name
device = self.sender().data().data()[1]
self.joystickReader.set_input_map(device.name, selected_mapping)
self._update_input_device_footer()
self._update_input_device_status()

def device_discovery(self, devs):
"""Called when new devices have been added"""
Expand Down Expand Up @@ -870,7 +924,7 @@ def device_discovery(self, devs):
self._all_role_menus[0]["rolemenu"].actions()[0].setChecked(True)
logger.info("Select first device")

self._update_input_device_footer()
self._update_input_device_status()

def _open_config_folder(self):
QDesktopServices.openUrl(
Expand Down
50 changes: 48 additions & 2 deletions src/cfclient/ui/tabs/FlightTab.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,15 +92,15 @@ class FlightTab(TabToolbox, flight_tab_class):

_log_data_signal = pyqtSignal(int, object, object)
_pose_data_signal = pyqtSignal(object, object)

_thrust_lock_signal = pyqtSignal(bool)
_gamepad_device_signal = pyqtSignal(str, str, str)
_input_updated_signal = pyqtSignal(float, float, float, float)
_rp_trim_updated_signal = pyqtSignal(float, float)
_emergency_stop_updated_signal = pyqtSignal(bool)
_arm_updated_signal = pyqtSignal(bool)
_assisted_control_updated_signal = pyqtSignal(bool)
_heighthold_input_updated_signal = pyqtSignal(float, float, float, float)
_hover_input_updated_signal = pyqtSignal(float, float, float, float)

_log_error_signal = pyqtSignal(object, str)

# UI_DATA_UPDATE_FPS = 10
Expand All @@ -122,6 +122,14 @@ def __init__(self, helper):
super(FlightTab, self).__init__(helper, 'Flight Control')
self.setupUi(self)

self._thrust_lock_signal.connect(self._thrust_lock_updated)
self._helper.inputDeviceReader.thrust_lock_active.add_callback(
self._thrust_lock_signal.emit)

self._gamepad_device_signal.connect(self._gamepad_device_updated)
self._helper.mainUI._gamepad_device_updated.connect(
self._gamepad_device_signal.emit)

self.disconnectedSignal.connect(self.disconnected)
self.connectionFinishedSignal.connect(self.connected)
# Incomming signals
Expand Down Expand Up @@ -162,6 +170,8 @@ def __init__(self, helper):
self._log_error_signal.connect(self._logging_error)

self._isConnected = False
self._has_device = False
self._has_mapping = False

# Connect UI signals that are in this tab
self.flightModeCombo.currentIndexChanged.connect(self.flightmodeChange)
Expand Down Expand Up @@ -216,6 +226,42 @@ def __init__(self, helper):

self._helper.pose_logger.data_received_cb.add_callback(self._pose_data_signal.emit)

def _thrust_lock_updated(self, active):
if active:
self.gamepadStatusLabel.setText("Lower throttle to arm")
self.gamepadStatusLabel.setStyleSheet("color: red;")
else:
if self._has_device and self._has_mapping:
self.gamepadStatusLabel.setText("Ready")
self.gamepadStatusLabel.setStyleSheet("")
elif self._has_device:
self.gamepadStatusLabel.setText(
"No mapping selected")
self.gamepadStatusLabel.setStyleSheet(
"color: orange;")
else:
self.gamepadStatusLabel.setText("\u2014")
self.gamepadStatusLabel.setStyleSheet("")

def _gamepad_device_updated(self, device, mapping, mux):
self.gamepadNameLabel.setText(device)
self.gamepadMappingLabel.setText(mapping)
self.gamepadMuxLabel.setText(mux)
no_device_strings = ("No input device connected",
"No input device found")
self._has_device = device not in no_device_strings
if self._has_device:
self._has_mapping = "No mapping" not in mapping
if not self._has_mapping:
self.gamepadStatusLabel.setText(
"No mapping selected")
self.gamepadStatusLabel.setStyleSheet(
"color: orange;")
else:
self._has_mapping = False
self.gamepadStatusLabel.setText("\u2014")
self.gamepadStatusLabel.setStyleSheet("")

def _set_limiting_enabled(self, rp_limiting_enabled, yaw_limiting_enabled, thrust_limiting_enabled):

self.targetCalRoll.setEnabled(rp_limiting_enabled)
Expand Down
Loading
Loading