Skip to content

Commit 69cf044

Browse files
committed
script for servo testing
1 parent 4659a84 commit 69cf044

File tree

1 file changed

+239
-0
lines changed

1 file changed

+239
-0
lines changed
Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
import rclpy
2+
from controller_manager_msgs.srv import SwitchController
3+
from geometry_msgs.msg import TwistStamped
4+
from rclpy.node import Node
5+
from std_srvs.srv import Trigger
6+
7+
8+
class ServoController:
9+
"""Helper class to manage servo enable/disable with automatic state detection."""
10+
11+
def __init__(self, node: Node):
12+
self.node = node
13+
self.servo_enabled = False
14+
15+
# Service clients
16+
self.start_servo_client = node.create_client(Trigger, "/servo_node/start_servo")
17+
self.stop_servo_client = node.create_client(Trigger, "/servo_node/stop_servo")
18+
self.switch_controller_client = node.create_client(
19+
SwitchController, "/controller_manager/switch_controller"
20+
)
21+
22+
# Wait for services (FIXED: use timeout loop, not spin_until_future_complete)
23+
self.node.get_logger().info("Waiting for servo control services...")
24+
25+
# Wait for start_servo
26+
while rclpy.ok() and not self.start_servo_client.wait_for_service(
27+
timeout_sec=1.0
28+
):
29+
self.node.get_logger().info("Waiting for /servo_node/start_servo...")
30+
31+
# Wait for stop_servo
32+
while rclpy.ok() and not self.stop_servo_client.wait_for_service(
33+
timeout_sec=1.0
34+
):
35+
self.node.get_logger().info("Waiting for /servo_node/stop_servo...")
36+
37+
# Wait for switch_controller
38+
while rclpy.ok() and not self.switch_controller_client.wait_for_service(
39+
timeout_sec=1.0
40+
):
41+
self.node.get_logger().info(
42+
"Waiting for /controller_manager/switch_controller..."
43+
)
44+
45+
self.node.get_logger().info("All servo control services ready!")
46+
47+
def check_servo_state(self) -> bool:
48+
"""Check if servo is currently enabled by calling start_servo service."""
49+
if not self.start_servo_client.wait_for_service(timeout_sec=1.0):
50+
return False
51+
52+
request = Trigger.Request()
53+
future = self.start_servo_client.call_async(request)
54+
rclpy.spin_until_future_complete(self.node, future, timeout_sec=2.0)
55+
56+
if future.result() is not None and future.result().success:
57+
# If start_servo succeeded, servo was already enabled
58+
self.servo_enabled = True
59+
self.node.get_logger().info("Servo already enabled")
60+
return True
61+
else:
62+
self.servo_enabled = False
63+
return False
64+
65+
def enable_servo(self) -> bool:
66+
"""Enable servo mode: switch controllers + start servo."""
67+
self.node.get_logger().info("Enabling servo mode...")
68+
69+
# Switch controllers: deactivate trajectory, activate forward_position
70+
switch_request = SwitchController.Request()
71+
switch_request.start_controllers = ["forward_position_controller"]
72+
switch_request.stop_controllers = ["scaled_joint_trajectory_controller"]
73+
switch_request.strictness = 2 # BEST_EFFORT
74+
75+
future = self.switch_controller_client.call_async(switch_request)
76+
rclpy.spin_until_future_complete(self.node, future, timeout_sec=5.0)
77+
78+
if future.result() is None or not future.result().success:
79+
self.node.get_logger().error("Failed to switch controllers")
80+
return False
81+
82+
self.node.get_logger().info("Controllers switched successfully")
83+
84+
# Start servo service
85+
request = Trigger.Request()
86+
future = self.start_servo_client.call_async(request)
87+
rclpy.spin_until_future_complete(self.node, future, timeout_sec=5.0)
88+
89+
if future.result() is not None and future.result().success:
90+
self.servo_enabled = True
91+
self.node.get_logger().info("Servo started successfully")
92+
return True
93+
else:
94+
self.node.get_logger().error("Failed to start servo")
95+
return False
96+
97+
def disable_servo(self) -> bool:
98+
"""Disable servo mode: stop servo + switch controllers back."""
99+
if not self.servo_enabled:
100+
self.node.get_logger().info("Servo already disabled")
101+
return True
102+
103+
self.node.get_logger().info("Disabling servo mode...")
104+
105+
# Stop servo service first
106+
request = Trigger.Request()
107+
future = self.stop_servo_client.call_async(request)
108+
rclpy.spin_until_future_complete(self.node, future, timeout_sec=5.0)
109+
110+
if future.result() is None or not future.result().success:
111+
self.node.get_logger().error("Failed to stop servo")
112+
return False
113+
114+
# Switch controllers back: activate trajectory, deactivate forward_position
115+
switch_request = SwitchController.Request()
116+
switch_request.start_controllers = ["scaled_joint_trajectory_controller"]
117+
switch_request.stop_controllers = ["forward_position_controller"]
118+
switch_request.strictness = 2 # BEST_EFFORT
119+
120+
future = self.switch_controller_client.call_async(switch_request)
121+
rclpy.spin_until_future_complete(self.node, future, timeout_sec=5.0)
122+
123+
if future.result() is None or not future.result().success:
124+
self.node.get_logger().error("Failed to switch controllers back")
125+
return False
126+
127+
self.servo_enabled = False
128+
self.node.get_logger().info("Servo mode disabled successfully")
129+
return True
130+
131+
132+
class ServoTwistPublisher(Node):
133+
def __init__(self):
134+
super().__init__("servo_twist_publisher")
135+
136+
# Parameters
137+
self.declare_parameter("frequency_msg_twist", 250.0)
138+
self.declare_parameter("msgs", 250)
139+
self.declare_parameter("x", 1.0)
140+
self.declare_parameter("y", 0.0)
141+
self.declare_parameter("z", 0.0)
142+
self.declare_parameter("rx", 0.0)
143+
self.declare_parameter("ry", 0.0)
144+
self.declare_parameter("rz", 0.0)
145+
146+
self.rate = self.get_parameter("frequency_msg_twist").value
147+
self.num_messages = self.get_parameter("msgs").value
148+
self.linear_x = float(self.get_parameter("x").value)
149+
self.linear_y = float(self.get_parameter("y").value)
150+
self.linear_z = float(self.get_parameter("z").value)
151+
self.angular_x = float(self.get_parameter("rx").value)
152+
self.angular_y = float(self.get_parameter("ry").value)
153+
self.angular_z = float(self.get_parameter("rz").value)
154+
155+
# Publisher
156+
self.publisher = self.create_publisher(
157+
TwistStamped, "/servo_node/delta_twist_cmds", 10
158+
)
159+
160+
# Servo controller
161+
# self.servo_ctrl = ServoController(self)
162+
163+
# Timer
164+
timer_period = 1.0 / self.rate
165+
self.timer = self.create_timer(timer_period, self.timer_callback)
166+
167+
self.msg_count = 0
168+
self.servo_was_already_enabled = False
169+
170+
self.get_logger().info(
171+
f"Ready to publish {self.num_messages} messages at {self.rate} Hz"
172+
)
173+
174+
def timer_callback(self):
175+
if self.msg_count < self.num_messages:
176+
# Publish servo twist command
177+
msg = TwistStamped()
178+
msg.header.stamp = self.get_clock().now().to_msg()
179+
msg.header.frame_id = "base_link"
180+
msg.twist.linear.x = self.linear_x
181+
msg.twist.linear.y = self.linear_y
182+
msg.twist.linear.z = self.linear_z
183+
msg.twist.angular.x = self.angular_x
184+
msg.twist.angular.y = self.angular_y
185+
msg.twist.angular.z = self.angular_z
186+
self.publisher.publish(msg)
187+
self.msg_count += 1
188+
self.get_logger().info(
189+
f"Published msg {self.msg_count}/{self.num_messages}"
190+
)
191+
192+
elif self.msg_count >= self.num_messages:
193+
# Send final zero command
194+
zero_msg = TwistStamped()
195+
zero_msg.header.stamp = self.get_clock().now().to_msg()
196+
zero_msg.header.frame_id = "base_link"
197+
self.publisher.publish(zero_msg)
198+
199+
# Stop timer
200+
self.timer.cancel()
201+
202+
# # Disable servo (unless it was already enabled before we started)
203+
# if not self.servo_was_already_enabled:
204+
# self.servo_ctrl.disable_servo()
205+
206+
self.get_logger().info("Transmission complete!")
207+
exit()
208+
209+
210+
def main(args=None):
211+
rclpy.init(args=args)
212+
213+
node = ServoTwistPublisher()
214+
215+
# Check servo state and enable if needed
216+
# if not node.servo_ctrl.check_servo_state():
217+
# success = node.servo_ctrl.enable_servo()
218+
# if not success:
219+
# node.get_logger().error('Failed to enable servo. Exiting.')
220+
# return
221+
# else:
222+
# node.servo_was_already_enabled = True
223+
# node.get_logger().info('Servo was already enabled')
224+
225+
try:
226+
rclpy.spin(node)
227+
except KeyboardInterrupt:
228+
node.get_logger().info("Interrupted by user")
229+
try:
230+
if not node.servo_was_already_enabled:
231+
node.servo_ctrl.disable_servo()
232+
node.destroy_node()
233+
rclpy.shutdown()
234+
except Exception:
235+
pass
236+
237+
238+
if __name__ == "__main__":
239+
main()

0 commit comments

Comments
 (0)