-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbraitenberg.py
More file actions
224 lines (179 loc) · 7.4 KB
/
Copy pathbraitenberg.py
File metadata and controls
224 lines (179 loc) · 7.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
#!/usr/bin/env python3
import cv2
import numpy as np
import os
import rospy
import yaml
from duckietown.dtros import DTROS, NodeType, TopicType, DTParam, ParamType
from sensor_msgs.msg import CompressedImage
from duckietown_msgs.msg import WheelsCmdStamped
class BraitenbergNode(DTROS):
"""Braitenberg Behaviour
This node implements Braitenberg vehicle behavior on a Duckiebot.
Args:
node_name (:obj:`str`): a unique, descriptive name for the node
that ROS will use
Configuration:
~gain (:obj:`float`): scaling factor applied to the desired
velocity, taken from the robot-specific kinematics
calibration
~trim (:obj:`float`): trimming factor that is typically used
to offset differences in the behaviour of the left and
right motors, it is recommended to use a value that results
in the robot moving in a straight line when forward command
is given, taken from the robot-specific kinematics calibration
~baseline (:obj:`float`): the distance between the two wheels
of the robot, taken from the robot-specific kinematics
calibration
~radius (:obj:`float`): radius of the wheel, taken from the
robot-specific kinematics calibration
~k (:obj:`float`): motor constant, assumed equal for both
motors, taken from the robot-specific kinematics calibration
~limit (:obj:`float`): limits the final commands sent to the
motors, taken from the robot-specific kinematics calibration
Subscriber:
~image/compressed (:obj:`CompressedImage`): The acquired camera
images
Publisher:
~wheels_cmd (:obj:`duckietown_msgs.msg.WheelsCmdStamped`): The
wheel commands that the motors will execute
"""
def __init__(self, node_name):
# Initialize the DTROS parent class
super(BraitenbergNode, self).__init__(node_name=node_name,
node_type=NodeType.BEHAVIOR)
self.veh_name = rospy.get_namespace().strip("/")
# Set parameters using a robot-specific yaml file if such exists
self.readParamFromFile()
# Get static parameters
self._baseline = rospy.get_param('~baseline')
self._radius = rospy.get_param('~radius')
self._k = rospy.get_param('~k')
# Get editable parameters
self._gain = DTParam(
'~gain',
param_type=ParamType.FLOAT,
min_value=0.0,
max_value=3.0
)
self._trim = DTParam(
'~trim',
param_type=ParamType.FLOAT,
min_value=0.0,
max_value=3.0
)
self._limit = DTParam(
'~limit',
param_type=ParamType.FLOAT,
min_value=0.0,
max_value=1.0
)
# Wait for the automatic gain control
# of the camera to settle, before we stop it
rospy.sleep(2.0)
rospy.set_param('/%s/camera_node/exposure_mode'
self.veh_name, 'off')
self.log("Initialized")
def speedToCmd(self, speed_l, speed_r):
"""Applies the robot-specific gain and trim to the
output velocities
Applies the motor constant k to convert the deisred wheel speeds
to wheel commands. Additionally, applies the gain and trim from
the robot-specific kinematics configuration.
Args:
speed_l (:obj:`float`): Desired speed for the left
wheel (e.g between 0 and 1)
speed_r (:obj:`float`): Desired speed for the right
wheel (e.g between 0 and 1)
Returns:
The respective left and right wheel commands that need to be
packed in a `WheelsCmdStamped` message
"""
# assuming same motor constants k for both motors
k_r = self._k
k_l = self._k
# adjusting k by gain and trim
k_r_inv = (self._gain.value + self._trim.value) / k_r
k_l_inv = (self._gain.value - self._trim.value) / k_l
# conversion from motor rotation rate to duty cycle
u_r = speed_r * k_r_inv
u_l = speed_l * k_l_inv
# limiting output to limit, which is 1.0 for the duckiebot
u_r_limited = self.trim(u_r,
-self._limit.value,
self._limit.value)
u_l_limited = self.trim(u_l,
-self._limit.value,
self._limit.value)
return u_l_limited, u_r_limited
def readParamFromFile(self):
"""
Reads the saved parameters from
`/data/config/calibrations/kinematics/DUCKIEBOTNAME.yaml` or
uses the default values if the file doesn't exist. Adjsuts
the ROS paramaters for the node with the new values.
"""
# Check file existence
fname = self.getFilePath(self.veh_name)
# Use the default values from the config folder if a
# robot-specific file does not exist.
if not os.path.isfile(fname):
self.log("Kinematics calibration file %s does not "
"exist! Using the default file." % fname, type='warn')
fname = self.getFilePath('default')
with open(fname, 'r') as in_file:
try:
yaml_dict = yaml.load(in_file)
except yaml.YAMLError as exc:
self.log("YAML syntax error. File: %s fname. Exc: %s"
%(fname, exc), type='fatal')
rospy.signal_shutdown()
return
# Set parameters using value in yaml file
if yaml_dict is None:
# Empty yaml file
return
for param_name in ["gain", "trim", "baseline", "k", "radius", "limit"]:
param_value = yaml_dict.get(param_name)
if param_name is not None:
rospy.set_param("~"+param_name, param_value)
else:
# Skip if not defined, use default value instead.
pass
def getFilePath(self, name):
"""
Returns the path to the robot-specific configuration file,
i.e. `/data/config/calibrations/kinematics/DUCKIEBOTNAME.yaml`.
Args:
name (:obj:`str`): the Duckiebot name
Returns:
:obj:`str`: the full path to the robot-specific
calibration file
"""
cali_file_folder = '/data/config/calibrations/kinematics/'
cali_file = cali_file_folder + name + ".yaml"
return cali_file
def trim(self, value, low, high):
"""
Trims a value to be between some bounds.
Args:
value: the value to be trimmed
low: the minimum bound
high: the maximum bound
Returns:
the trimmed value
"""
return max(min(value, high), low)
def on_shutdown(self):
"""Shutdown procedure.
Publishes a zero velocity command at shutdown."""
# MAKE SURE THAT THE LAST WHEEL COMMAND YOU PUBLISH IS ZERO,
# OTHERWISE YOUR DUCKIEBOT WILL CONTINUE MOVING AFTER
# THE NODE IS STOPPED
# PUT YOUR CODE HERE
super(BraitenbergNode, self).on_shutdown()
if __name__ == '__main__':
# Initialize the node
camera_node = BraitenbergNode(node_name='braitenberg')
# Keep it spinning to keep the node alive
rospy.spin()