11"""
22Script to autotune the PIDs of a joint position controller using Ziegler-Nichols method
33
4+ Run this script
5+
46
57Bridge topics from Gazebo to ROS 2
68
1012
1113import math
1214import numpy as np
15+ import os
1316import time
1417from typing import List , Tuple
1518
16- from gz .msgs11 .clock_pb2 import Clock
17- from gz .msgs11 .double_pb2 import Double
18- from gz .msgs11 .model_pb2 import Model
19- from gz .transport14 import Node
19+ from argparse import ArgumentParser
20+
21+ # from ardupilot_gazebo.scripts.pid_params import set_param, InvalidParameterError
22+ from pid_params import set_param , InvalidParameterError
23+
24+ # World and model defaults
25+ DEFAULT_WORLD = "servo"
26+ DEFAULT_MODEL = "servo"
27+ DEFAULT_JOINT = "servo_arm_joint"
28+ DEFAULT_CONTROLLER = "JointPositionController"
29+ DEFAULT_TIMEOUT_MS = 1000
30+
31+ # Supported Gazebo versions
32+ GZ_VERSION_GARDEN = "garden"
33+ GZ_VERSION_HARMONIC = "harmonic"
34+ GZ_VERSION_IONIC = "ionic"
35+
36+
37+ def gz_version ():
38+ """Return the environment variable GZ_VERSION if set, else default to 'harmonic'"""
39+ return os .environ .get ("GZ_VERSION" , GZ_VERSION_HARMONIC )
2040
21- from ardupilot_gazebo .scripts .pid_params import set_param , InvalidParameterError
41+
42+ if gz_version () == GZ_VERSION_GARDEN :
43+ from gz .msgs9 .clock_pb2 import Clock
44+ from gz .msgs9 .double_pb2 import Double
45+ from gz .msgs9 .model_pb2 import Model
46+ elif gz_version () == GZ_VERSION_HARMONIC :
47+ from gz .msgs10 .clock_pb2 import Clock
48+ from gz .msgs10 .double_pb2 import Double
49+ from gz .msgs10 .model_pb2 import Model
50+ elif gz_version () == GZ_VERSION_IONIC :
51+ from gz .msgs11 .clock_pb2 import Clock
52+ from gz .msgs11 .double_pb2 import Double
53+ from gz .msgs11 .model_pb2 import Model
54+
55+
56+ # Importing gz.transport into the module global scope causes an odd
57+ # multiprocessing conflict with dronecan. This is a workaround.
58+ def gz_node ():
59+ if gz_version () == GZ_VERSION_GARDEN :
60+ from gz .transport12 import Node
61+ elif gz_version () == GZ_VERSION_HARMONIC :
62+ from gz .transport13 import Node
63+ elif gz_version () == GZ_VERSION_IONIC :
64+ from gz .transport14 import Node
65+
66+ return Node ()
2267
2368
2469class ClockSubscriber :
2570 def __init__ (self ):
2671 super ().__init__ ()
27- self .node = Node ()
72+ self .node = gz_node ()
2873 self .last_clock = Clock ()
2974 self .topic = "/clock"
3075
@@ -51,7 +96,7 @@ def __init__(self, world_name, model_name):
5196 self ._world_name = world_name
5297 self ._model_name = model_name
5398
54- self .node = Node ()
99+ self .node = gz_node ()
55100 self .last_model = Model ()
56101
57102 self .subscribe (world_name , model_name )
@@ -93,7 +138,7 @@ class JointCommandPublisher:
93138 def __init__ (self , topic : str ):
94139 super ().__init__ ()
95140 self .topic = topic
96- self .node = Node ()
141+ self .node = gz_node ()
97142 self .pub = self .node .advertise (self .topic , Double )
98143
99144 def set_position (self , position : float ):
@@ -102,7 +147,9 @@ def set_position(self, position: float):
102147 self .pub .publish (msg )
103148
104149
105- class JointAutotuner :
150+ class JointAutotunerGenerated :
151+ """ML generated auto tuner (not effective)"""
152+
106153 def __init__ (
107154 self , world_name : str , model_name : str , joint_name : str , timeout_ms : int = 2000
108155 ):
@@ -114,7 +161,7 @@ def __init__(
114161 self .registry = f"/world/{ world_name } "
115162 self .prefix = f"JointPositionController.{ world_name } .{ model_name } .{ joint_name } ."
116163
117- self .node = Node ()
164+ self .node = gz_node ()
118165 self .sample_time = 0.01 # 10ms
119166
120167 self .joint_state_sub = JointStateSubscriber (world_name , model_name )
@@ -211,27 +258,44 @@ def autotune(self) -> Tuple[float, float, float]:
211258def main ():
212259 print ("Running autotune" )
213260
214- # Configuration
215- world_name = "default"
216- model_name = "joint_position_controller_demo"
217- joint_name = "j1"
218- timeout_ms = 1000
261+ # Command line args
262+ parser = ArgumentParser (description = "Get controller PIDs" )
263+ parser .add_argument ("--world" , default = DEFAULT_WORLD , type = str , help = "world name" )
264+ parser .add_argument ("--model" , default = DEFAULT_MODEL , type = str , help = "model name" )
265+ parser .add_argument ("--joint" , default = DEFAULT_JOINT , type = str , help = "joint name" )
266+ parser .add_argument (
267+ "--controller" ,
268+ default = DEFAULT_CONTROLLER ,
269+ type = str ,
270+ help = "joint controller system" ,
271+ )
272+ parser .add_argument (
273+ "--timeout_ms" , default = DEFAULT_TIMEOUT_MS , type = str , help = "timeout (ms)"
274+ )
275+ args = parser .parse_args ()
276+
277+ timeout_ms = args .timeout_ms
278+
279+ world_name = args .world
280+ model_name = args .model
281+ joint_name = args .joint
282+ system_name = args .controller
283+ joint_cmd_topic = f"/model/{ args .model } " f"/joint/{ args .joint } " f"/0/cmd_pos"
219284
220285 # Create and run autotuner
221286 # tuner = JointAutotuner(world_name, model_name, joint_name, timeout_ms)
222287 # tuner.autotune()
223288
224289 # Create subscribers
225- clock_sub = ClockSubscriber ()
226290 joint_sub = JointStateSubscriber (world_name , model_name )
227291
228292 # Create publishers
229- joint_cmd = JointCommandPublisher ("/rotor_cmd" )
293+ joint_cmd = JointCommandPublisher (joint_cmd_topic )
230294
231295 # Dwell period
232296 period = 2.0
233- limit_max = math .radians (60 .0 )
234- limit_min = - math .radians (60 .0 )
297+ limit_max = math .radians (10 .0 )
298+ limit_min = - math .radians (0 .0 )
235299
236300 # control the twitch freqency
237301 start_time = time .monotonic ()
@@ -240,7 +304,7 @@ def main():
240304 last_twitch_time = start_time
241305
242306 # control the update rate
243- update_rate = 100 .0
307+ update_rate = 5 .0
244308 update_period = 1.0 / update_rate
245309
246310 pos_tgt = 0.0
0 commit comments