-
Notifications
You must be signed in to change notification settings - Fork 24
Keyboard joy #652
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Keyboard joy #652
Changes from 15 commits
fcfa385
eec9cb3
2d999ba
9f73fc5
631feb9
ec5f5ee
8c0770c
f42e1d7
93807e8
c1f8b43
5d8c813
8893e9c
f38ab63
5a4b55b
6599bda
43e9c10
e454384
efbde0e
b707ca9
36ca3c0
71488e6
d5344fb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| # keyboard_joy | ||
|
|
||
| `keyboard_joy` is a ROS 2 Python node that publishes `sensor_msgs/Joy` messages based on keyboard input, acting as a simple joystick replacement. | ||
| Key-to-axis and key-to-button mappings are configurable via YAML and support both hold and sticky axis modes. | ||
|
|
||
| TODO: Currently **only works on Xorg** (not Wayland) due to limitations in global keyboard capture. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| axes: | ||
| # Each mapping is [axis_index, value, mode] | ||
| # axis_index is which Joy.axes[] index to control | ||
| # value is the target value while the key is held (-1.0 to 1.0) | ||
| # mode is 'hold' or 'sticky' | ||
|
|
||
| # Left stick (surge/sway) | ||
| w: [1, 1.0, 'hold'] | ||
| s: [1, -1.0, 'hold'] | ||
| a: [0, 1.0, 'hold'] | ||
| d: [0, -1.0, 'hold'] | ||
|
|
||
| # Heave | ||
| Key.space: [2, 1.0, 'hold'] # Up (RT) | ||
| Key.shift: [2, -1.0, 'hold'] # Down (LT) | ||
|
|
||
| # Rotation (pitch/yaw) | ||
| Key.up: [4, 1.0, 'hold'] | ||
kluge7 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Key.down: [4, -1.0, 'hold'] | ||
| Key.left: [3, 1.0, 'hold'] | ||
| Key.right: [3, -1.0, 'hold'] | ||
|
|
||
| parameters: | ||
| axis_update_period: 0.02 # seconds per update | ||
| publish_period: 0.02 # seconds per publish | ||
|
|
||
| # Motion tuning | ||
| axis_increment_step: 0.1 # axis change per update (higher = faster movement) | ||
|
|
||
|
|
||
| buttons: | ||
| # Roll | ||
| q: 4 # LB | ||
| e: 5 # RB | ||
|
|
||
| # Modes | ||
| '1': 0 # A - Xbox mode | ||
| '2': 1 # B - Killswitch | ||
| '3': 2 # X - Auto | ||
| '4': 3 # Y - Reference | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,200 @@ | ||||||||||||||||||||||||||||
| #!/usr/bin/env python3 | ||||||||||||||||||||||||||||
| import threading | ||||||||||||||||||||||||||||
| from dataclasses import dataclass | ||||||||||||||||||||||||||||
| from enum import Enum | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| import rclpy | ||||||||||||||||||||||||||||
| import yaml | ||||||||||||||||||||||||||||
| from pynput import keyboard | ||||||||||||||||||||||||||||
| from rclpy.node import Node | ||||||||||||||||||||||||||||
| from rclpy.parameter import Parameter | ||||||||||||||||||||||||||||
| from sensor_msgs.msg import Joy | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| start_message = r""" | ||||||||||||||||||||||||||||
| ██ ▄█▀▓█████▓██ ██▓ ▄▄▄▄ ▒█████ ▄▄▄ ██▀███ ▓█████▄ ▄▄▄██▀▀▀▒█████ ▓██ ██▓ | ||||||||||||||||||||||||||||
| ██▄█▒ ▓█ ▀ ▒██ ██▒▓█████▄ ▒██▒ ██▒▒████▄ ▓██ ▒ ██▒▒██▀ ██▌ ▒██ ▒██▒ ██▒▒██ ██▒ | ||||||||||||||||||||||||||||
| ▓███▄░ ▒███ ▒██ ██░▒██▒ ▄██▒██░ ██▒▒██ ▀█▄ ▓██ ░▄█ ▒░██ █▌ ░██ ▒██░ ██▒ ▒██ ██░ | ||||||||||||||||||||||||||||
| ▓██ █▄ ▒▓█ ▄ ░ ▐██▓░▒██░█▀ ▒██ ██░░██▄▄▄▄██ ▒██▀▀█▄ ░▓█▄ ▌▓██▄██▓ ▒██ ██░ ░ ▐██▓░ | ||||||||||||||||||||||||||||
| ▒██▒ █▄░▒████▒ ░ ██▒▓░░▓█ ▀█▓░ ████▓▒░ ▓█ ▓██▒░██▓ ▒██▒░▒████▓ ▓███▒ ░ ████▓▒░ ░ ██▒▓░ | ||||||||||||||||||||||||||||
| ▒ ▒▒ ▓▒░░ ▒░ ░ ██▒▒▒ ░▒▓███▀▒░ ▒░▒░▒░ ▒▒ ▓▒█░░ ▒▓ ░▒▓░ ▒▒▓ ▒ ▒▓▒▒░ ░ ▒░▒░▒░ ██▒▒▒ | ||||||||||||||||||||||||||||
| ░ ░▒ ▒░ ░ ░ ░▓██ ░▒░ ▒░▒ ░ ░ ▒ ▒░ ▒ ▒▒ ░ ░▒ ░ ▒░ ░ ▒ ▒ ▒ ░▒░ ░ ▒ ▒░ ▓██ ░▒░ | ||||||||||||||||||||||||||||
| ░ ░░ ░ ░ ▒ ▒ ░░ ░ ░ ░ ░ ░ ▒ ░ ▒ ░░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ▒ ▒ ▒ ░░ | ||||||||||||||||||||||||||||
| ░ ░ ░ ░░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ | ||||||||||||||||||||||||||||
| ░ ░ ░ ░ ░ ░ | ||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||
|
Comment on lines
+10
to
+21
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes |
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| class AxisMode(Enum): | ||||||||||||||||||||||||||||
| HOLD = "hold" | ||||||||||||||||||||||||||||
| STICKY = "sticky" | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| @dataclass(frozen=True) | ||||||||||||||||||||||||||||
| class AxisBinding: | ||||||||||||||||||||||||||||
| axis: int | ||||||||||||||||||||||||||||
| val: float | ||||||||||||||||||||||||||||
| mode: AxisMode | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| class KeyboardJoy(Node): | ||||||||||||||||||||||||||||
| def __init__(self): | ||||||||||||||||||||||||||||
| super().__init__("keyboard_joy") | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| self.declare_parameter("config", Parameter.Type.STRING) | ||||||||||||||||||||||||||||
| self.load_key_mappings() | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| self.declare_parameter("topics.joy", Parameter.Type.STRING) | ||||||||||||||||||||||||||||
| joy_topic = self.get_parameter("topics.joy").value | ||||||||||||||||||||||||||||
kluge7 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||
| self.joy_pub = self.create_publisher(Joy, joy_topic, 10) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
Q3rkses marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||
| self._init_joy_message() | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| self.active_axes = {} | ||||||||||||||||||||||||||||
| self.sticky_axes = {} | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| self.lock = threading.Lock() | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| self.listener = keyboard.Listener( | ||||||||||||||||||||||||||||
| on_press=self.on_press, on_release=self.on_release | ||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||
| self.listener.start() | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| self.create_timer(self.publish_period, self.publish_joy) | ||||||||||||||||||||||||||||
| self.create_timer(self.axis_update_period, self.update_active_axes) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| self.get_logger().info(start_message) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
Comment on lines
+46
to
+52
|
||||||||||||||||||||||||||||
| self.listener.start() | |
| self.create_timer(self.core.publish_period, self.publish_joy) | |
| self.create_timer(self.core.axis_update_period, self.update_active_axes) | |
| self.get_logger().info(start_message) | |
| self.create_timer(self.core.publish_period, self.publish_joy) | |
| self.create_timer(self.core.axis_update_period, self.update_active_axes) | |
| self.get_logger().info(start_message) | |
| self.listener.start() |
Q3rkses marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
Q3rkses marked this conversation as resolved.
Show resolved
Hide resolved
Q3rkses marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
Q3rkses marked this conversation as resolved.
Show resolved
Hide resolved
Q3rkses marked this conversation as resolved.
Show resolved
Hide resolved
kluge7 marked this conversation as resolved.
Show resolved
Hide resolved
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| import os | ||
|
|
||
| from ament_index_python.packages import get_package_share_directory | ||
| from launch import LaunchDescription | ||
| from launch.actions import DeclareLaunchArgument | ||
| from launch.substitutions import LaunchConfiguration | ||
| from launch_ros.actions import Node | ||
|
|
||
|
|
||
| def generate_launch_description(): | ||
| keyboard_config = os.path.join( | ||
| get_package_share_directory('keyboard_joy'), 'config', 'key_mappings.yaml' | ||
| ) | ||
|
|
||
| orca_params = os.path.join( | ||
| get_package_share_directory('auv_setup'), 'config', 'robots', 'orca.yaml' | ||
| ) | ||
Q3rkses marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| return LaunchDescription( | ||
| [ | ||
| DeclareLaunchArgument( | ||
| 'config', | ||
| default_value=keyboard_config, | ||
| description='Path to key mappings YAML file', | ||
| ), | ||
| Node( | ||
| package='keyboard_joy', | ||
| executable='keyboard_joy_node', | ||
| name='keyboard_joy', | ||
| namespace='orca', | ||
Q3rkses marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| output='screen', | ||
| parameters=[{'config': LaunchConfiguration('config')}, orca_params], | ||
kluge7 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ), | ||
| ] | ||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| <?xml version="1.0"?> | ||
| <?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?> | ||
| <package format="3"> | ||
| <name>keyboard_joy</name> | ||
| <version>0.0.0</version> | ||
| <description>Keyboard teleop node that publishes sensor_msgs/Joy messages</description> | ||
| <maintainer email="andreas.svendsrud@vortexntnu.no">kluge7</maintainer> | ||
| <license>MIT</license> | ||
|
|
||
| <depend>rclpy</depend> | ||
| <depend>sensor_msgs</depend> | ||
| <depend>python3-pynput</depend> | ||
|
|
||
| <test_depend>ament_pytest</test_depend> | ||
| <test_depend>python3-pytest</test_depend> | ||
|
|
||
| <export> | ||
| <build_type>ament_python</build_type> | ||
| </export> | ||
| </package> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| [develop] | ||
| script_dir=$base/lib/keyboard_joy | ||
| [install] | ||
| install_scripts=$base/lib/keyboard_joy |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| import os | ||
| from glob import glob | ||
|
|
||
| from setuptools import setup | ||
|
|
||
| package_name = 'keyboard_joy' | ||
|
|
||
| setup( | ||
| name=package_name, | ||
| version='0.0.0', | ||
| packages=[package_name], | ||
| data_files=[ | ||
| ('share/ament_index/resource_index/packages', ['resource/' + package_name]), | ||
| ('share/' + package_name, ['package.xml']), | ||
| (os.path.join('share', package_name, 'launch'), glob('launch/*.launch.py')), | ||
| (os.path.join('share', package_name, 'config'), glob('config/*.yaml')), | ||
| ], | ||
| install_requires=['setuptools'], | ||
| tests_require=['pytest'], | ||
| zip_safe=True, | ||
| maintainer='kluge7', | ||
| maintainer_email='andreas.svendsrud@vortexntnu.no', | ||
| description='Keyboard teleop node that publishes sensor_msgs/Joy messages', | ||
| license='MIT', | ||
| entry_points={ | ||
| 'console_scripts': [ | ||
| 'keyboard_joy_node = keyboard_joy.keyboard_joy_node:main', | ||
| ], | ||
| }, | ||
| ) |
Uh oh!
There was an error while loading. Please reload this page.