11#!/usr/bin/env python3
22
33import rclpy
4+ from rclpy .executors import ExternalShutdownException
45from rclpy .node import Node
5- from std_msgs .msg import Float64
66from sensor_msgs .msg import JointState
7- import copy
8- import threading
7+ from mockup_msgs .srv import SetJointState
98
109
1110# Class that stores the information for each mockup config
1211class MockupConfig :
13- def __init__ (self , topic_name , min_position , max_position , initial_position , joint_name ):
14- self .topic_name = topic_name
12+ def __init__ (self , min_position , max_position , initial_position , joint_name ):
1513 self .joint_name = joint_name
1614 self .min_position = min_position
1715 self .max_position = max_position
1816 self .position = initial_position
17+ self .velocity = 0.0
18+ self .effort = 0.0
19+
20+ def set_position (self , desired_position ):
21+ self .position = max (self .min_position , min (desired_position , self .max_position ))
1922
2023
2124class MockupStateManager (Node ):
@@ -34,99 +37,98 @@ def __init__(self):
3437 # create the joint state publisher
3538 self .publisher_ = self .create_publisher (JointState , "joint_states" , 10 )
3639
37- subscriptions = []
38- for index , mockup_config in enumerate (self .mockup_configs ):
39- # this was a bit funky for copying in index
40- # see https://github.com/ros2/rclpy/issues/629#issuecomment-1542151499 for reference
41- subscriptions .append (
42- self .create_subscription (
43- Float64 ,
44- self .prefix + mockup_config .topic_name ,
45- lambda msg , idx = index : self .position_cb (msg , idx ),
46- 10 ,
47- )
48- )
40+ self .set_joint_state_service = self .create_service (SetJointState , "~/set_joint_state" , self .set_joint_state_cb )
4941
5042 # create the timer for joint state publisher callback
5143 timer_period_sec = 0.5 # unit: seconds
5244 self .timer = self .create_timer (timer_period_sec , self .joint_state_cb )
5345
54- self .lock = threading .Lock ()
55-
5646 def load_mockup_configs (self ):
5747 """loads the parameters provided with each of the relevaant joints and populates self.mockup_configs"""
5848 # get the list of topic names first
5949
60- topic_params = self .get_parameters_by_prefix ("topics " )
50+ joints_params = self .get_parameters_by_prefix ("joints " )
6151
62- topic_names = {key .split ("." )[0 ] for key in topic_params .keys ()}
52+ joints = {key .split ("." )[0 ] for key in joints_params .keys ()}
6353
6454 # puopulate self.mockup_configs based on loaded parameters
65- self .mockup_configs = []
66- for topic in topic_names :
67- self .get_logger ().info (f"Loading: { self .prefix + topic } " )
55+ self .mockup_configs = dict ()
56+ for joint in joints :
57+ self .get_logger ().info (f"Loading: { self .prefix + joint } " )
6858
69- joint_name = topic_params [ f" { topic } .joint_name" ]. value
70- min_position = topic_params [f"{ topic } .min_position" ].value
71- max_position = topic_params [f"{ topic } .max_position" ].value
72- initial_position = topic_params [f"{ topic } .initial_position" ].value
59+ joint_name = joint
60+ min_position = joints_params [f"{ joint } .min_position" ].value
61+ max_position = joints_params [f"{ joint } .max_position" ].value
62+ initial_position = joints_params [f"{ joint } .initial_position" ].value
7363
7464 # add mockups to the member variable
75- self .mockup_configs . append ( MockupConfig (topic , min_position , max_position , initial_position , joint_name ) )
65+ self .mockup_configs [ joint_name ] = MockupConfig (min_position , max_position , initial_position , joint_name )
7666
7767 def joint_state_cb (self ):
7868 """Publisher for the manager which publishes the joint state info."""
7969 msg = JointState ()
8070 msg .header .stamp = self .get_clock ().now ().to_msg ()
81- # with mutex locking, add all of the joint states
82- with self .lock :
83- for mockup_config in self .mockup_configs :
84- msg .name .append (self .prefix + mockup_config .joint_name )
85- msg .position .append (mockup_config .position )
86- msg .velocity .append (0.0 )
87- msg .effort .append (0.0 )
71+ # add all of the joint states
72+ for mockup_config in self .mockup_configs .values ():
73+ msg .name .append (self .prefix + mockup_config .joint_name )
74+ msg .position .append (mockup_config .position )
75+ msg .velocity .append (mockup_config .position )
76+ msg .effort .append (mockup_config .effort )
8877
8978 self .publisher_ .publish (msg )
9079
91- self .get_logger ().debug (
92- f"This is the hatch joint state message: { msg } "
93- ) # this will fill in the string with formatted msg data
80+ def set_joint_state_cb (self , req : SetJointState .Request , res : SetJointState .Response ):
81+ """
82+ Callback for the setting internal joint states which will then be published.
83+ """
9484
95- def position_cb (self , msg : Float64 , index ):
96- """callback for the position setting
85+ # Check first to make sure that data is the right size.
86+ # Any non-empty lists must be of the same size of names
87+ error_msg = ""
88+ msg_size = len (req .joint_state .name )
89+ valid = True
90+
91+ for field in ("position" , "velocity" , "effort" ):
92+ values = getattr (req .joint_state , field )
93+ if values and (len (values ) != msg_size ):
94+ error_msg += (
95+ f"The size of `{ field } ` ({ len (values )} ) does not match the size of"
96+ "`name` ({msg_size}) in SetJointState. "
97+ )
98+ valid = False
9799
98- Args:
99- msg (Float64): This message transmits the joint position (in radians or meters)
100- index (int): the index that we are accessing (to ineracte with self.mockup_configs
101- """
102- self . get_logger (). debug (
103- f"This is the new position of { self . prefix + self . mockup_configs [ index ]. joint_name } : { msg } "
104- )
105- if self . mockup_configs [ index ]. min_position <= msg . data and msg . data < = self .mockup_configs [index ]. max_position :
106- with self . lock :
107- self . mockup_configs [ index ] .position = msg . data
108- elif self . mockup_configs [ index ]. min_position >= msg . data :
109- with self . lock :
110- self . mockup_configs [ index ]. position = copy . deepcopy ( self . mockup_configs [ index ]. min_position )
111- elif self . mockup_configs [ index ]. max_position <= msg . data :
112- with self . lock :
113- self . mockup_configs [ index ]. position = copy . deepcopy ( self . mockup_configs [ index ]. max_position )
114- else : # I don't think this will ever happen 0.o
115- raise Exception ( self . mockup_configs [ index ]. joint_name + " joint angle out of range" )
100+ # return early if not valid
101+ if not valid :
102+ res . message = error_msg
103+ res . success = False
104+ return res
105+
106+ for i , name in enumerate ( req . joint_state . name ):
107+ mockup_config = self .mockup_configs [name ]
108+ # position is special because we have to clamp it, so it uses a member function
109+ if req . joint_state .position :
110+ mockup_config . set_position ( req . joint_state . position [ i ])
111+ if req . joint_state . velocity :
112+ mockup_config . velocity = req . joint_state . velocity [ i ]
113+ if req . joint_state . effort :
114+ mockup_config . effort = req . joint_state . effort [ i ]
115+
116+ res . success = True
117+ return res
116118
117119
118120def main (args = None ):
119121 rclpy .init (args = args )
120122
121123 mockup_state_manager = MockupStateManager ()
122124
123- rclpy . spin ( mockup_state_manager )
124-
125- # Destroy the node explicitly
126- # (optional - otherwise it will be done automatically
127- # when the garbage collector destroys the node object)
128- mockup_state_manager .destroy_node ()
129- rclpy .shutdown ()
125+ try :
126+ rclpy . spin ( mockup_state_manager )
127+ except ( KeyboardInterrupt , ExternalShutdownException ):
128+ pass
129+ finally :
130+ mockup_state_manager .destroy_node ()
131+ rclpy .try_shutdown ()
130132
131133
132134if __name__ == "__main__" :
0 commit comments