Skip to content
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

Port to ROS 2 #8

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
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
13 changes: 0 additions & 13 deletions CHANGELOG.rst

This file was deleted.

57 changes: 0 additions & 57 deletions CMakeLists.txt

This file was deleted.

52 changes: 48 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,51 @@
= Description =
# ros2-keyboard

This node uses the SDL library to grab keypresses. To do so, it opens a window where input is received. This window needs to becurrently focused, otherwise this node will not receive any key presses.
This node uses the SDL library to grab keypresses.
To do so, it opens a window where input is received.
This window needs to becurrently focused, otherwise this node will not receive any key presses.

= Topics =
# Install

~keydown and ~keyup (keyboard/Key)
1. Create a new workspace.
2. `$ cd /path/to/your_ws/src`
3. Clone this repository
- (ssh) `$ git clone [email protected]:cmower/ros2-keyboard.git`
- (https) `$ git clone https://github.com/cmower/ros2-keyboard.git`
4. Install SDL 1.2 with the command `sudo apt install libsdl1.2-dev`.
5. `$ cd /path/to/your_ws`
6. `$ colcon build`

# Nodes

## `keyboard`

Publishes keyboard events when the window is focused.

* Published topics: `keydown` and `keyup` (`keyboard_msgs/Key`)
* Parameters
* `allow_repeat` (`bool`): Enables or disables the keyboard repeat rate. Default is `false`.
* `repeat_delay` (`int`): How long the key must be pressed before it begins repeating. Default is `SDL_DEFAULT_REPEAT_DELAY`.
* `repeat_interval` (`int`): Key replay speed. Default is `SDL_DEFAULT_REPEAT_INTERVAL`.

## `keyboard_to_joy.py`

Converts keyboard events to a joy message.

* Published topic: `joy` (`sensor_msgs/Joy`)
* Subscribed topics: `keydown` and `keyup` (`keyboard_msgs/Key`)
* Parameters
* `config_file_name` (`str`): File name for the configuration file. See [here](keyboard/config/example_config.yaml) for an example.
* `sampling_frequency` (`int`): Rate (Hz) at which `joy` messages are published. Default is `50`.

### Example:

In one terminal, start the `keyboard` node.
```shell
ros2 run keyboard keyboard
```

In a second terminal, start the `keyboard_to_joy.py` node.
```shell
ros2 run keyboard keyboard_to_joy.py --ros-args \
-p config_file_name:=`ros2 pkg prefix keyboard`/share/keyboard/config/example_config.yaml
```
69 changes: 69 additions & 0 deletions keyboard/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
cmake_minimum_required(VERSION 3.5)
project(keyboard)

# Default to C99
if(NOT CMAKE_C_STANDARD)
set(CMAKE_C_STANDARD 99)
endif()

# Default to C++14
if(NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 14)
endif()

# Default to release build
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
endif()

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()

# find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
find_package(SDL REQUIRED)
find_package(keyboard_msgs REQUIRED)

add_executable(keyboard src/keyboard.cpp)
ament_target_dependencies(keyboard rclcpp std_msgs keyboard_msgs)
target_include_directories(keyboard
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
PRIVATE
${SDL_INCLUDE_DIR}
)

target_link_libraries(keyboard
SDL
)


install(TARGETS keyboard
DESTINATION lib/${PROJECT_NAME})

install(
PROGRAMS
scripts/keyboard_to_joy.py
DESTINATION lib/${PROJECT_NAME}
)

install(DIRECTORY config
DESTINATION share/${PROJECT_NAME}
)

if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
# the following line skips the linter which checks for copyrights
# uncomment the line when a copyright and license is not present in all source files
#set(ament_cmake_copyright_FOUND TRUE)
# the following line skips cpplint (only works in a git repo)
# uncomment the line when this package is not in a git repo
#set(ament_cmake_cpplint_FOUND TRUE)
ament_lint_auto_find_test_dependencies()
endif()

ament_package()
6 changes: 6 additions & 0 deletions keyboard/config/example_config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
axes:
- ["KEY_LEFT", "KEY_RIGHT"]
- ["KEY_UP", "KEY_DOWN"]

buttons:
- "KEY_SPACE"
28 changes: 28 additions & 0 deletions keyboard/package.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?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</name>
<version>1.0.0</version>
<description>Publishes keyboard key presses to ROS 2.</description>

<author email="[email protected]">v01d</author>

<maintainer email="[email protected]">cmower</maintainer>
<author email="[email protected]">cmower</author>


<license>GPLv2</license>

<buildtool_depend>ament_cmake</buildtool_depend>

<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>

<depend>rclcpp</depend>
<depend>std_msgs</depend>
<depend>keyboard_msgs</depend>

<export>
<build_type>ament_cmake</build_type>
</export>
</package>
140 changes: 140 additions & 0 deletions keyboard/scripts/keyboard_to_joy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
#!/usr/bin/env python3

import abc

import rclpy
from rclpy import qos
from rclpy.node import Node

from sensor_msgs.msg import Joy
from keyboard_msgs.msg import Key

import yaml


class JoyPart(abc.ABC):
def __init__(self, init_value):
self._value = init_value

@abc.abstractmethod
def down(self, code):
pass

@abc.abstractmethod
def up(self, code):
pass

def get(self):
return self._value


class Button(JoyPart):
def __init__(self, key_str):
super().__init__(0)
self.code = getattr(Key, key_str)

def down(self, code):
if code == self.code:
self._value = 1

def up(self, code):
if code == self.code:
self._value = 0


class Axis(JoyPart):
def __init__(self, key_neg_str, key_pos_str):
super().__init__(0.0)
self.code_neg = getattr(Key, key_neg_str)
self.code_pos = getattr(Key, key_pos_str)

def down(self, code):
if code == self.code_neg:
self._value -= 1.0

elif code == self.code_pos:
self._value += 1.0

def up(self, code):
if code == self.code_neg:
self._value += 1.0

elif code == self.code_pos:
self._value -= 1.0


class KeyboardToJoyNode(Node):
def __init__(self):
# Initialize ROS node
super().__init__("keyboard_to_joy_node")

# Get parameters
self.declare_parameter("config_file_name")
config_file_name = (
self.get_parameter("config_file_name").get_parameter_value().string_value
)

self.declare_parameter("sampling_frequency", 50)
hz = (
self.get_parameter("sampling_frequency").get_parameter_value().integer_value
)

# Load config file
with open(config_file_name, "rb") as configfile:
self.config = yaml.load(configfile, Loader=yaml.FullLoader)

self.buttons = [Button(key_str) for key_str in self.config.get("buttons", [])]
self.axes = [
Axis(key_neg_str, key_pos_str)
for key_neg_str, key_pos_str in self.config.get("axes", [])
]

# Setup publisher
self.joy = Joy()
self.joy_pub = self.create_publisher(Joy, "joy", qos.qos_profile_system_default)

# Keyboard callback
self.keydown_sub = self.create_subscription(
Key, "keydown", self.keydown_callback, qos.qos_profile_system_default
)
self.keyup_sub = self.create_subscription(
Key, "keyup", self.keyup_callback, qos.qos_profile_system_default
)

# Start timer
dt = 1.0 / float(hz)
self.create_timer(dt, self.main_loop)

def keydown_callback(self, msg):
for part in self.axes + self.buttons:
part.down(msg.code)

def keyup_callback(self, msg):
for part in self.axes + self.buttons:
part.up(msg.code)

def main_loop(self):
msg = Joy(
axes=[a.get() for a in self.axes], buttons=[b.get() for b in self.buttons]
)
msg.header.stamp = self.get_clock().now().to_msg()
self.joy_pub.publish(msg)


def main(args=None):
# Start node, and spin
rclpy.init(args=args)
node = KeyboardToJoyNode()

try:
rclpy.spin(node)
except KeyboardInterrupt:
pass
finally:
# Clean up and shutdown
node.destroy_node()
rclpy.shutdown()


if __name__ == "__main__":
main()
Loading