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

Feature/joystickinterface #96

Merged
merged 39 commits into from
Oct 9, 2023
Merged
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
c77dc6f
Created 2d wrench message function
Sep 17, 2023
a03bb13
Automated autoyapf fixes
invalid-email-address Sep 17, 2023
ebf9edc
Created a joystick subscriber
Sep 24, 2023
7d9f22b
Automated autoyapf fixes
invalid-email-address Sep 24, 2023
78e8ae2
Made a functioning subscriber and shell for callback function
Sep 28, 2023
cb79638
Automated autoyapf fixes
invalid-email-address Sep 28, 2023
29f51c6
Updated the callback function to publish wrench message from input
Aldokan Sep 28, 2023
2eb1f89
Automated autoyapf fixes
invalid-email-address Sep 28, 2023
ced1213
Added a unit test for the wrench message publisher
Aldokan Sep 28, 2023
5d1f00f
Automated autoyapf fixes
invalid-email-address Sep 28, 2023
ba88939
Made callback function for joystick and declared parameters
Aldokan Oct 1, 2023
9783172
Automated autoyapf fixes
invalid-email-address Oct 1, 2023
755714a
added in progress build and config files with YAML
LeRatDuFA Oct 1, 2023
b974b46
Update params.yaml
LeRatDuFA Oct 1, 2023
1dd82b2
Updated the main function and made it so you can get paramters from yaml
Oct 1, 2023
5ab6bb7
Updated joystick_interface_launch.yaml
Aldokan Oct 1, 2023
9d6952e
Fixed main function so that initiation of rclpy only happens there
Aldokan Oct 1, 2023
c0e3c27
Automated autoyapf fixes
invalid-email-address Oct 1, 2023
d610d55
Updated so that it initiates rclpy and shutsdown in each test
Aldokan Oct 1, 2023
453a057
CMakeLists.txt now works with python
Aldokan Oct 1, 2023
a9e874f
Now works with python
Aldokan Oct 1, 2023
7287111
Some cleaning
LeRatDuFA Oct 1, 2023
066dcea
Automated autoyapf fixes
invalid-email-address Oct 1, 2023
dff3ad1
Restructuring to work with ament_cmake_python
Oct 1, 2023
d2869df
Restructuring to work with ament_cmake_python
Oct 1, 2023
e568970
test change for it to work with the new params file
LeRatDuFA Oct 1, 2023
f686526
Merge remote-tracking branch 'refs/remotes/origin/feature/joystickint…
Oct 1, 2023
09b0871
Merge remote-tracking branch 'refs/remotes/origin/feature/joystickint…
Oct 1, 2023
fa36893
Automated autoyapf fixes
invalid-email-address Oct 1, 2023
82a75e7
Right trigger and Left trigger now does positive/negative thrust/torq…
Oct 8, 2023
c4062e6
Right trigger and Left trigger now does positive/negative thrust/torq…
Oct 8, 2023
460a9ed
Automated autoyapf fixes
invalid-email-address Oct 8, 2023
006c97b
Cleanup
Oct 8, 2023
5eafe4e
Formatting
Oct 8, 2023
5f6d6da
Automated autoyapf fixes
invalid-email-address Oct 8, 2023
f511965
Merge remote-tracking branch 'origin/development' into feature/joysti…
Oct 8, 2023
0ad57b8
Merge remote-tracking branch 'refs/remotes/origin/feature/joystickint…
Oct 8, 2023
9decedb
Formatting
Oct 8, 2023
49795b0
Corrected pull requests
Oct 8, 2023
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
5 changes: 0 additions & 5 deletions asv_setup/config/params/gnc.yaml

This file was deleted.

38 changes: 15 additions & 23 deletions asv_setup/launch/pc.yaml
Original file line number Diff line number Diff line change
@@ -1,31 +1,23 @@
launch:
- set_environment_variable:
- set_env:
name: ROSCONSOLE_FORMAT
value: "[${severity}] [${time}] [${node}]: ${message}"

- declare_namespace:
namespace: joystick
with_arguments:
- ns_joystick

- rosparam_load_file:
param_file: asv_setup/config/params/gnc.yaml

- namespace:
namespace: ${arg.ns_joystick}
prefix: joy
launch:
- group:
- node:
package: joy
executable: joy_node
pkg: joy
exec: joy_node
name: joystick_driver
output: screen
parameters:
_deadzone: 0.15
_autorepeat_rate: 100
param:
- name: _deadzone
value: 0.15
- name: _autorepeat_rate
value: 100
remap:
-
from: "/joy"
to: "/joystick/joy"

- node:
package: joystick_interface
executable: joystick_interface.py
name: joystick_interface
output: screen
- include:
file: "vortex-asv/mission/joystick_interface/launch/joystick_interface_launch.yaml"
44 changes: 44 additions & 0 deletions mission/joystick_interface/CMakeLists.txt
alekskl01 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
cmake_minimum_required(VERSION 3.8)
project(joystick_interface)

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(ament_cmake_python REQUIRED)

# uncomment the following section in order to fill in
# further dependencies manually.
# find_package(<dependency> REQUIRED)
find_package(rclpy REQUIRED)
find_package(sensor_msgs REQUIRED)
find_package(geometry_msgs REQUIRED)

#ament_python_install_package("joystick_interface")
ament_python_install_package(${PROJECT_NAME})

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


if(BUILD_TESTING)
find_package(ament_cmake_pytest REQUIRED)
set(_pytest_tests
joystick_interface/test/test_joystick_interface.py
# Add other test files here
)
foreach(_test_path ${_pytest_tests})
get_filename_component(_test_name ${_test_path} NAME_WE)
ament_add_pytest_test(${_test_name} ${_test_path}
APPEND_ENV PYTHONPATH=${CMAKE_CURRENT_BINARY_DIR}
TIMEOUT 60
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)
endforeach()
endif()

ament_package()
7 changes: 7 additions & 0 deletions mission/joystick_interface/config/params.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/joystick_interface:
ros__parameters:
surge: 50.0
sway: 50.0
yaw: 50.0


Empty file.
204 changes: 204 additions & 0 deletions mission/joystick_interface/joystick_interface/joystick_interface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from geometry_msgs.msg import Wrench
from sensor_msgs.msg import Joy
from std_msgs.msg import Bool


class states:
XBOX_MODE = 1
AUTONOMOUS_MODE = 2
NO_GO = 3 # Do nothing


class JoystickInterface(Node):

def __init__(self):
super().__init__('joystick_interface_node')
self.get_logger().info("Joystick interface is up and running")

self.last_button_press_time = 0
self.debounce_duration = 0.25
self.state = states.NO_GO

self.joystick_buttons_map = [
"A",
"B",
"X",
"Y",
"LB",
"RB",
"back",
"start",
"power",
"stick_button_left",
"stick_button_right",
]

self.joystick_axes_map = [
"horizontal_axis_left_stick", #Translation (Left and Right)
"vertical_axis_left_stick", #Translation (Forwards and Backwards)
"LT", #Negative thrust/torque multiplier
"horizontal_axis_right_stick", #Rotation
"vertical_axis_right_stick",
"RT", #Positive thrust/torque multiplier
"dpad_horizontal",
"dpad_vertical",
]

self.joy_subscriber = self.create_subscription(Joy, "/joystick/joy",
self.joystick_cb, 1)
self.wrench_publisher = self.create_publisher(Wrench,
"/thrust/wrench_input",
1)

self.declare_parameter('surge', 100.0)
Copy link
Contributor

Choose a reason for hiding this comment

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

should not be necessary to declare, as they are declared in the config file.

self.declare_parameter('sway', 100.0)
self.declare_parameter('yaw', 100.0)

#Gets the scaling factors from the yaml file
self.joystick_surge_scaling = self.get_parameter('surge').value
self.joystick_sway_scaling = self.get_parameter('sway').value
self.joystick_yaw_scaling = self.get_parameter('yaw').value

#Killswitch publisher
self.software_killswitch_signal_publisher = self.create_publisher(
Bool, "/softWareKillSwitch", 10)
self.software_killswitch_signal_publisher.publish(
Bool(data=False)) #Killswitch is not active

#Operational mode publisher
self.operational_mode_signal_publisher = self.create_publisher(
Bool, "/softWareOperationMode", 10)
# Signal that we are not in autonomous mode
self.operational_mode_signal_publisher.publish(Bool(data=True))

#Controller publisher
self.enable_controller_publisher = self.create_publisher(
Bool, "/controller/lqr/enable", 10)

#does a linear conversion from trigger inputs (1 to -1) to (1 to 2)
def right_trigger_linear_converter(self, rt_input):
output_value = (rt_input + 1) * (-0.5) + 2
return output_value

#does a linear conversion from trigger input (1 to -1) to (1 to 0.5)
def left_trigger_linear_converter(self, lt_input):
ouput_value = lt_input * 0.25 + 0.75
return ouput_value

def create_2d_wrench_message(self, x, y, yaw):
wrench_msg = Wrench()
wrench_msg.force.x = x
wrench_msg.force.y = y
wrench_msg.torque.z = yaw
return wrench_msg

def publish_wrench_message(self, wrench):
self.wrench_publisher.publish(wrench)

def transition_to_xbox_mode(self):
# We want to turn off controller when moving to xbox mode
self.enable_controller_publisher.publish(Bool(data=False))
# signal that we enter xbox mode
self.operational_mode_signal_publisher.publish(Bool(data=True))
self.state = states.XBOX_MODE

def transition_to_autonomous_mode(self):
# We want to publish zero force once when transitioning
wrench_msg = self.create_2d_wrench_message(0.0, 0.0, 0.0)
self.publish_wrench_message(wrench_msg)
# signal that we are turning on autonomous mode
self.operational_mode_signal_publisher.publish(Bool(data=False))
self.state = states.AUTONOMOUS_MODE

def joystick_cb(self, msg):
current_time = self.get_clock().now().to_msg()._sec

#Input from controller to joystick_interface
buttons = {}
axes = {}

for i in range(len(msg.buttons)):
buttons[self.joystick_buttons_map[i]] = msg.buttons[i]

for i in range(len(msg.axes)):
axes[self.joystick_axes_map[i]] = msg.axes[i]

xbox_control_mode_button = buttons["A"]
software_killswitch_button = buttons["B"]
software_control_mode_button = buttons["X"]
left_trigger = axes['LT']
right_trigger = axes['RT']
right_trigger = self.right_trigger_linear_converter(right_trigger)
left_trigger = self.left_trigger_linear_converter(left_trigger)

surge = axes[
"vertical_axis_left_stick"] * self.joystick_surge_scaling * left_trigger * right_trigger
sway = axes[
"horizontal_axis_left_stick"] * self.joystick_sway_scaling * left_trigger * right_trigger
yaw = axes[
"horizontal_axis_right_stick"] * self.joystick_yaw_scaling * left_trigger * right_trigger

# Debounce for the buttons
if current_time - self.last_button_press_time < self.debounce_duration:
software_control_mode_button = False
xbox_control_mode_button = False
software_killswitch_button = False

# If any button is pressed, update the last button press time
if software_control_mode_button or xbox_control_mode_button or software_killswitch_button:
self.last_button_press_time = current_time

# Toggle ks on and off
if self.state == states.NO_GO and software_killswitch_button:
# signal that killswitch is not blocking
self.software_killswitch_signal_publisher.publish(Bool(data=True))
self.transition_to_xbox_mode()
return

if software_killswitch_button:
self.get_logger().info("SW killswitch", throttle_duration_sec=1)
# signal that killswitch is blocking
self.software_killswitch_signal_publisher.publish(Bool(data=False))
# Turn off controller in sw killswitch
self.enable_controller_publisher.publish(Bool(data=False))
# Publish a zero wrench message when sw killing
wrench_msg = self.create_2d_wrench_message(0.0, 0.0, 0.0)
self.publish_wrench_message(wrench_msg)
self.state = states.NO_GO
return wrench_msg

#Msg published from joystick_interface to thrust allocation
wrench_msg = self.create_2d_wrench_message(surge, sway, yaw)

if self.state == states.XBOX_MODE:
self.get_logger().info("XBOX mode", throttle_duration_sec=1)
self.publish_wrench_message(wrench_msg)

if software_control_mode_button:
self.transition_to_autonomous_mode()

if self.state == states.AUTONOMOUS_MODE:
self.get_logger().info("autonomous mode", throttle_duration_sec=1)

if xbox_control_mode_button:
self.transition_to_xbox_mode()

return wrench_msg


def main():
rclpy.init()

joystick_interface = JoystickInterface()
print(joystick_interface.joystick_surge_scaling)
rclpy.spin(joystick_interface)

joystick_interface.destroy_node()
rclpy.shutdown()


if __name__ == "__main__":
main()
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
from joystick_interface.joystick_interface import JoystickInterface
from joystick_interface.joystick_interface import states
import rclpy
from sensor_msgs.msg import Joy
from sensor_msgs.msg import Joy


class TestJoystickInterface:

#test that the linear conversion from (1 to -1) to (1 to 2) is working
def test_right_trigger_linear_converter(self):
rclpy.init()
joystick = JoystickInterface()
assert joystick.right_trigger_linear_converter(1) == 1
assert joystick.right_trigger_linear_converter(0) == 1.5
assert joystick.right_trigger_linear_converter(-1) == 2
rclpy.shutdown()

#test that the linear conversion from (1 to -1) to (1 to 0.5) is working
def test_left_trigger_linear_converter(self):
rclpy.init()
joystick = JoystickInterface()
assert joystick.left_trigger_linear_converter(1) == 1
assert joystick.left_trigger_linear_converter(0) == 0.75
assert joystick.left_trigger_linear_converter(-1) == 0.5
rclpy.shutdown()

#test that the 2d wrench msg is created successfully
def test_2d_wrench_msg(self):
rclpy.init()
msg = JoystickInterface().create_2d_wrench_message(2.0, 3.0, 4.0)
assert msg.force.x == 2.0
assert msg.force.y == 3.0
assert msg.torque.z == 4.0
rclpy.shutdown()

#Test that the callback function will be able to interpret the joy msg
def test_input_from_controller_into_wrench_msg(self):
rclpy.init()
joy_msg = Joy()
joy_msg.axes = [-1.0, -1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0]
joy_msg.buttons = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
wrench_msg = JoystickInterface().joystick_cb(joy_msg)
assert wrench_msg.force.x == -100.0
assert wrench_msg.force.y == -100.0
assert wrench_msg.torque.z == 0.0
rclpy.shutdown()

#When the killswitch button is activated in the buttons list, it should output a wrench msg with only zeros
def test_killswitch_button(self):
rclpy.init()
joystick = JoystickInterface()
joystick.state = states.XBOX_MODE
joy_msg = Joy()
joy_msg.axes = [-1.0, -1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0]
joy_msg.buttons = [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
wrench_msg = joystick.joystick_cb(joy_msg)
assert wrench_msg.force.x == 0.0
assert wrench_msg.force.y == 0.0
assert wrench_msg.torque.z == 0.0
rclpy.shutdown()

#When we move into XBOX mode it should still be able to return this wrench message
def test_moving_in_of_xbox_mode(self):
rclpy.init()
joystick = JoystickInterface()
joystick.state = states.XBOX_MODE
joy_msg = Joy()
joy_msg.axes = [-1.0, -1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0]
joy_msg.buttons = [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
wrench_msg = joystick.joystick_cb(joy_msg)
assert wrench_msg.force.x == -100.0
assert wrench_msg.force.y == -100.0
assert wrench_msg.torque.z == 0.0
rclpy.shutdown()
12 changes: 12 additions & 0 deletions mission/joystick_interface/launch/joystick_interface_launch.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
launch:
- node:
pkg: joystick_interface
exec: joystick_interface.py
name: joystick_interface
output: screen
param:
- from: vortex-asv/mission/joystick_interface/config/params.yaml




Loading