Skip to content

Keyboard joy#652

Merged
kluge7 merged 22 commits into
mainfrom
feat/keyboard-joy
Jan 29, 2026
Merged

Keyboard joy#652
kluge7 merged 22 commits into
mainfrom
feat/keyboard-joy

Conversation

@kluge7

@kluge7 kluge7 commented Jan 4, 2026

Copy link
Copy Markdown
Contributor

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).

Copilot AI left a comment

Copy link
Copy Markdown

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.

Comment thread mission/keyboard_joy/README.md
Comment thread mission/keyboard_joy/config/key_mappings.yaml Outdated
Comment thread mission/keyboard_joy/keyboard_joy/keyboard_joy_node.py
Comment thread mission/keyboard_joy/keyboard_joy/keyboard_joy_node.py Outdated
Comment thread mission/keyboard_joy/keyboard_joy/keyboard_joy_node.py Outdated
Comment thread mission/keyboard_joy/keyboard_joy/keyboard_joy_node.py Outdated
Comment thread mission/keyboard_joy/keyboard_joy/keyboard_joy_node.py
Comment thread mission/keyboard_joy/keyboard_joy/keyboard_joy_node.py Outdated
Comment thread mission/keyboard_joy/test/test_keyboard_joy_node.py Outdated
Comment thread mission/keyboard_joy/keyboard_joy/keyboard_joy_node.py
@kluge7 kluge7 force-pushed the feat/keyboard-joy branch from 0588283 to 8893e9c Compare January 4, 2026 14:30
@codecov

codecov Bot commented Jan 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 65.90909% with 90 lines in your changes missing coverage. Please review.
✅ Project coverage is 34.64%. Comparing base (7482370) to head (d5344fb).
⚠️ Report is 72 commits behind head on main.

Files with missing lines Patch % Lines
...ion/keyboard_joy/keyboard_joy/keyboard_joy_node.py 0.00% 66 Missing ⚠️
...ion/keyboard_joy/keyboard_joy/keyboard_joy_core.py 80.61% 16 Missing and 3 partials ⚠️
mission/keyboard_joy/setup.py 0.00% 5 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #652      +/-   ##
==========================================
+ Coverage   31.37%   34.64%   +3.26%     
==========================================
  Files          39       43       +4     
  Lines        2559     2823     +264     
  Branches      713      749      +36     
==========================================
+ Hits          803      978     +175     
- Misses       1577     1663      +86     
- Partials      179      182       +3     
Flag Coverage Δ
unittests 34.64% <65.90%> (+3.26%) ⬆️

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

Files with missing lines Coverage Δ
...ission/keyboard_joy/test/test_keyboard_joy_core.py 100.00% <100.00%> (ø)
mission/keyboard_joy/setup.py 0.00% <0.00%> (ø)
...ion/keyboard_joy/keyboard_joy/keyboard_joy_core.py 80.61% <80.61%> (ø)
...ion/keyboard_joy/keyboard_joy/keyboard_joy_node.py 0.00% <0.00%> (ø)

... and 1 file with indirect coverage changes

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

@Q3rkses

Q3rkses commented Jan 5, 2026

Copy link
Copy Markdown
Contributor

bro why bootleg chatgpt talking

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

@Q3rkses Q3rkses left a comment

Copy link
Copy Markdown
Contributor

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
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

yes

@Andeshog Andeshog left a comment

Copy link
Copy Markdown
Contributor

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 thread mission/keyboard_joy/config/key_mappings.yaml Outdated
Comment thread mission/keyboard_joy/config/key_mappings.yaml
Comment thread mission/keyboard_joy/keyboard_joy/keyboard_joy_node.py Outdated
Comment thread mission/keyboard_joy/keyboard_joy/keyboard_joy_node.py Outdated
Comment thread mission/keyboard_joy/keyboard_joy/keyboard_joy_node.py Outdated
Comment thread mission/keyboard_joy/keyboard_joy/keyboard_joy_node.py Outdated
Comment thread mission/keyboard_joy/keyboard_joy/keyboard_joy_node.py Outdated

@Andeshog Andeshog left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Fine as is, but if you managed to separate the logic from ros, the code would be much easier to test (ref. when you removed the previously written tests). The only "ros stuff" is periodically publishing a joy message, so not a compelling reason to pack all the logic into a ros node

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 14 changed files in this pull request and generated 12 comments.


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

Comment thread mission/keyboard_joy/keyboard_joy/keyboard_joy_core.py
Comment thread mission/keyboard_joy/launch/keyboard_joy_node.launch.py
Comment on lines +143 to +153
delta = (
self.axis_increment_step
if target > 0
else -self.axis_increment_step
)
next_val = current + delta

if (delta > 0 and next_val > target) or (
delta < 0 and next_val < target
):
next_val = target

Copilot AI Jan 29, 2026

Copy link

Choose a reason for hiding this comment

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

Logic assumes target value is never zero, but this is not validated. The update_active_axes method calculates delta based on whether target > 0 (lines 143-147), which doesn't handle the case where target == 0. If a user creates an AxisBinding with val=0.0, the axis would increment toward negative infinity instead of staying at zero.

Consider adding validation in the AxisBinding or init to ensure axis values are non-zero, or fix the delta calculation to handle zero targets correctly.

Suggested change
delta = (
self.axis_increment_step
if target > 0
else -self.axis_increment_step
)
next_val = current + delta
if (delta > 0 and next_val > target) or (
delta < 0 and next_val < target
):
next_val = target
diff = target - current
if diff == 0.0:
continue
step = self.axis_increment_step
if abs(diff) <= step:
next_val = target
elif diff > 0:
next_val = current + step
else:
next_val = current - step

Copilot uses AI. Check for mistakes.
Comment on lines +50 to +56
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)

Copilot AI Jan 29, 2026

Copy link

Choose a reason for hiding this comment

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

Potential resource leak if node initialization fails after starting listener. The keyboard listener is started on line 50, but if any subsequent initialization steps fail (e.g., create_timer on lines 52-53), the listener will not be stopped. The listener thread will continue running even though the node failed to initialize.

Consider wrapping the initialization in a try-except block that ensures the listener is stopped if initialization fails after it's started, or defer starting the listener until after all other initialization is complete.

Suggested change
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()

Copilot uses AI. Check for mistakes.
mode=AxisMode(str(mode)),
)

button_mappings = keymap.get("buttons", {}) or {}

Copilot AI Jan 29, 2026

Copy link

Choose a reason for hiding this comment

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

Button mappings loaded from YAML are not type-validated. The button_mappings dictionary values should be integers, but there's no conversion or validation when loading from YAML (line 77). This could lead to runtime errors later when the values are used with int() conversions in the press and release methods.

Consider adding validation or explicit type conversion similar to how axis mappings are handled.

Suggested change
button_mappings = keymap.get("buttons", {}) or {}
raw_buttons = keymap.get("buttons", {}) or {}
button_mappings: dict[str, int] = {}
for key, button in raw_buttons.items():
try:
button_mappings[key] = int(button)
except (TypeError, ValueError) as exc:
raise ValueError(
f"Invalid button mapping for key {key!r}: {button!r}"
) from exc

Copilot uses AI. Check for mistakes.
Comment thread mission/keyboard_joy/keyboard_joy/keyboard_joy_core.py
Comment thread mission/keyboard_joy/keyboard_joy/keyboard_joy_node.py
Comment thread mission/keyboard_joy/keyboard_joy/keyboard_joy_core.py
Comment thread mission/keyboard_joy/keyboard_joy/keyboard_joy_node.py Outdated
Comment thread mission/keyboard_joy/keyboard_joy/keyboard_joy_node.py
@kluge7 kluge7 merged commit 61d07b4 into main Jan 29, 2026
13 checks passed
@kluge7 kluge7 deleted the feat/keyboard-joy branch January 29, 2026 19:00
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 2.6.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants