Skip to content

Conversation

@kluge7
Copy link
Contributor

@kluge7 kluge7 commented Jan 4, 2026

This PR introduces keyboard_joy, a python package that publishes sensor_msgs/Joy messages based on keyboard input.

TODO: the current implementation uses pynput and only works on xorg (not Wayland).

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces keyboard_joy, a new ROS 2 Python package that enables keyboard-based control by publishing sensor_msgs/Joy messages. The package serves as a joystick replacement for development and testing purposes, with configurable key mappings and support for both hold and sticky axis modes. Note that the current implementation is limited to Xorg environments.

Key changes:

  • New keyboard_joy package with configurable YAML-based key mappings
  • Support for both axis control (hold/sticky modes) and button inputs via keyboard
  • Comprehensive test suite using pytest with mocked keyboard listener

Reviewed changes

Copilot reviewed 8 out of 10 changed files in this pull request and generated 17 comments.

Show a summary per file
File Description
mission/keyboard_joy/keyboard_joy/keyboard_joy_node.py Main node implementation with keyboard event handling, Joy message publishing, and axis/button state management
mission/keyboard_joy/config/key_mappings.yaml Default key mapping configuration defining axis and button bindings
mission/keyboard_joy/launch/keyboard_joy_node.launch.py Launch file for starting the keyboard_joy node with configurable parameters
mission/keyboard_joy/test/test_keyboard_joy_node.py Unit tests covering key mapping, axis modes, and button state transitions
mission/keyboard_joy/setup.py Python package setup configuration with dependencies and entry points
mission/keyboard_joy/setup.cfg Installation script configuration for ROS 2
mission/keyboard_joy/package.xml ROS 2 package manifest with dependencies
mission/keyboard_joy/README.md Package documentation describing functionality and known limitations
mission/keyboard_joy/keyboard_joy/init.py Empty package initialization file
mission/keyboard_joy/resource/keyboard_joy Empty resource marker file for ament

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@kluge7 kluge7 force-pushed the feat/keyboard-joy branch from 0588283 to 8893e9c Compare January 4, 2026 14:30
@codecov
Copy link

codecov bot commented Jan 4, 2026

Codecov Report

❌ Patch coverage is 0% with 106 lines in your changes missing coverage. Please review.
✅ Project coverage is 36.90%. Comparing base (c05c7b5) to head (8893e9c).

Files with missing lines Patch % Lines
...ion/keyboard_joy/keyboard_joy/keyboard_joy_node.py 0.00% 97 Missing and 4 partials ⚠️
mission/keyboard_joy/setup.py 0.00% 5 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #652      +/-   ##
==========================================
+ Coverage   35.62%   36.90%   +1.28%     
==========================================
  Files          38       36       -2     
  Lines        2254     2173      -81     
  Branches      686      702      +16     
==========================================
- Hits          803      802       -1     
+ Misses       1272     1188      -84     
- Partials      179      183       +4     
Flag Coverage Δ
unittests 36.90% <0.00%> (+1.28%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
mission/keyboard_joy/setup.py 0.00% <0.00%> (ø)
...ion/keyboard_joy/keyboard_joy/keyboard_joy_node.py 0.00% <0.00%> (ø)

... and 5 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Q3rkses
Copy link
Contributor

Q3rkses commented Jan 5, 2026

bro why bootleg chatgpt talking

@kluge7 kluge7 requested a review from Q3rkses January 5, 2026 13:58
Copy link
Contributor

@Q3rkses Q3rkses left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are looking good cooking

Comment on lines +12 to +23
start_message = r"""
██ ▄█▀▓█████▓██ ██▓ ▄▄▄▄ ▒█████ ▄▄▄ ██▀███ ▓█████▄ ▄▄▄██▀▀▀▒█████ ▓██ ██▓
██▄█▒ ▓█ ▀ ▒██ ██▒▓█████▄ ▒██▒ ██▒▒████▄ ▓██ ▒ ██▒▒██▀ ██▌ ▒██ ▒██▒ ██▒▒██ ██▒
▓███▄░ ▒███ ▒██ ██░▒██▒ ▄██▒██░ ██▒▒██ ▀█▄ ▓██ ░▄█ ▒░██ █▌ ░██ ▒██░ ██▒ ▒██ ██░
▓██ █▄ ▒▓█ ▄ ░ ▐██▓░▒██░█▀ ▒██ ██░░██▄▄▄▄██ ▒██▀▀█▄ ░▓█▄ ▌▓██▄██▓ ▒██ ██░ ░ ▐██▓░
▒██▒ █▄░▒████▒ ░ ██▒▓░░▓█ ▀█▓░ ████▓▒░ ▓█ ▓██▒░██▓ ▒██▒░▒████▓ ▓███▒ ░ ████▓▒░ ░ ██▒▓░
▒ ▒▒ ▓▒░░ ▒░ ░ ██▒▒▒ ░▒▓███▀▒░ ▒░▒░▒░ ▒▒ ▓▒█░░ ▒▓ ░▒▓░ ▒▒▓ ▒ ▒▓▒▒░ ░ ▒░▒░▒░ ██▒▒▒
░ ░▒ ▒░ ░ ░ ░▓██ ░▒░ ▒░▒ ░ ░ ▒ ▒░ ▒ ▒▒ ░ ░▒ ░ ▒░ ░ ▒ ▒ ▒ ░▒░ ░ ▒ ▒░ ▓██ ░▒░
░ ░░ ░ ░ ▒ ▒ ░░ ░ ░ ░ ░ ░ ▒ ░ ▒ ░░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ▒ ▒ ▒ ░░
░ ░ ░ ░░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░
░ ░ ░ ░ ░ ░
"""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes

Copy link
Contributor

@Andeshog Andeshog left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider separating the ros layer (publishing) and the logic layer. This would also make the code easier to test, as you would not need to go through ros. Personally I find nested dataclasses better than dictionaries here (easier to read, and safer to use). It would also pretty much mirror the yaml file. Also consider Enum class for "mode" instead of string

Comment on lines +18 to +19
parameters:
axis_increment_rate: 0.02 # update interval (higher = slower response)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name says rate but the value is a period, as the bot mentioned. Choose one (50 Hz or 0.02 s). Also publish rate/period should probably be a param too

Comment on lines +1 to +13
axes:
# 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']
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to have a comment or something explaining what the three values in the list represent.

Comment on lines +38 to +40
self.joy_msg = Joy()
self.joy_msg.axes = [0.0] * (max_axis_index + 1)
self.joy_msg.buttons = [0] * (max_button_index + 1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just an idea, but would it make sense to utilize the frame id of the joy message, e.g. "Joystick" and "Keyboard"? Wouldnt make a difference unless handled by the subscriber, but maybe nice for logs/bags?

Comment on lines +29 to +30

self.declare_parameter('config', '')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prefer

self.declare_parameter('config', Parameter.Type.STRING)

Comment on lines +32 to +33

self.joy_pub = self.create_publisher(Joy, 'joy', 10)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

topic name parameter in orca.yaml?

Comment on lines +58 to +62
config_file = self.get_parameter(
'config'
).get_parameter_value().string_value or os.path.join(
get_package_share_directory('keyboard_joy'), 'config', 'key_mappings.yaml'
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default case here seems redundant if the node is launched anyway?

Comment on lines +35 to +40
max_axis_index = max((v[0] for v in self.axis_mappings.values()), default=-1)
max_button_index = max(self.button_mappings.values(), default=-1)

self.joy_msg = Joy()
self.joy_msg.axes = [0.0] * (max_axis_index + 1)
self.joy_msg.buttons = [0] * (max_button_index + 1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not super clear what is going on here (especially the +1), would be nice with a comment or abstraction. Ties back to the comment in config file perhaps

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants