Skip to content

Commit e5bb6cc

Browse files
Mypy nav2 loopback sim (#5052)
* Configured nav2_loopback_sim to be compatible with mypy. Signed-off-by: Leander Stephen D'Souza <leanderdsouza1234@gmail.com> * Added nav2_loopback_sim to the workflow. Signed-off-by: Leander Stephen D'Souza <leanderdsouza1234@gmail.com> * Separated packages from list for mypy workflow. Signed-off-by: Leander Stephen D'Souza <leanderdsouza1234@gmail.com> --------- Signed-off-by: Leander Stephen D'Souza <leanderdsouza1234@gmail.com>
1 parent c792d6b commit e5bb6cc

File tree

8 files changed

+56
-46
lines changed

8 files changed

+56
-46
lines changed

‎.github/workflows/lint.yml‎

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,6 @@ jobs:
2525
runs-on: ubuntu-latest
2626
container:
2727
image: rostooling/setup-ros-docker:ubuntu-noble-ros-rolling-ros-base-latest
28-
strategy:
29-
matrix:
30-
mypy_packages:
31-
- "nav2_smac_planner"
32-
- "nav2_common"
33-
- "nav2_bringup"
34-
- "nav2_collision_monitor"
35-
- "nav2_costmap_2d"
36-
- "opennav_docking"
37-
- "nav2_lifecycle_manager"
3828
steps:
3929
- uses: actions/checkout@v4
4030

@@ -45,7 +35,15 @@ jobs:
4535
with:
4636
linter: mypy
4737
distribution: rolling
48-
package-name: "${{ join(matrix.mypy_packages, ' ') }}"
38+
package-name: >-
39+
nav2_smac_planner
40+
nav2_common
41+
nav2_bringup
42+
nav2_collision_monitor
43+
nav2_costmap_2d
44+
opennav_docking
45+
nav2_lifecycle_manager
46+
nav2_loopback_sim
4947
arguments: --config tools/pyproject.toml
5048

5149
pre-commit:

‎nav2_loopback_sim/launch/loopback_simulation.launch.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from launch_ros.actions import Node
2222

2323

24-
def generate_launch_description():
24+
def generate_launch_description() -> LaunchDescription:
2525
bringup_dir = get_package_share_directory('nav2_bringup')
2626
params_file = LaunchConfiguration('params_file')
2727
declare_params_file_cmd = DeclareLaunchArgument(

‎nav2_loopback_sim/nav2_loopback_sim/loopback_simulator.py‎

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,19 @@
1414

1515

1616
import math
17+
from typing import Optional
1718

1819
from geometry_msgs.msg import (PoseWithCovarianceStamped, Quaternion, TransformStamped, Twist,
1920
TwistStamped, Vector3)
2021
from nav2_simple_commander.line_iterator import LineIterator
2122
from nav_msgs.msg import Odometry
2223
from nav_msgs.srv import GetMap
24+
import numpy as np
2325
import rclpy
2426
from rclpy.duration import Duration
2527
from rclpy.node import Node
2628
from rclpy.qos import DurabilityPolicy, QoSProfile, ReliabilityPolicy
29+
from rclpy.timer import Timer
2730
from rosgraph_msgs.msg import Clock
2831
from sensor_msgs.msg import LaserScan
2932
from tf2_ros import Buffer, TransformBroadcaster, TransformListener
@@ -44,15 +47,15 @@
4447

4548
class LoopbackSimulator(Node):
4649

47-
def __init__(self):
50+
def __init__(self) -> None:
4851
super().__init__(node_name='loopback_simulator')
4952
self.curr_cmd_vel = None
5053
self.curr_cmd_vel_time = self.get_clock().now()
51-
self.initial_pose = None
52-
self.timer = None
54+
self.initial_pose: PoseWithCovarianceStamped = None
55+
self.timer: Optional[Timer] = None
5356
self.setupTimer = None
5457
self.map = None
55-
self.mat_base_to_laser = None
58+
self.mat_base_to_laser: Optional[np.ndarray[np.float64, np.dtype[np.float64]]] = None
5659

5760
self.declare_parameter('update_duration', 0.01)
5861
self.update_dur = self.get_parameter('update_duration').get_parameter_value().double_value
@@ -126,44 +129,44 @@ def __init__(self):
126129

127130
self.info('Loopback simulator initialized')
128131

129-
def getBaseToLaserTf(self):
132+
def getBaseToLaserTf(self) -> None:
130133
try:
131134
# Wait for transform and lookup
132135
transform = self.tf_buffer.lookup_transform(
133136
self.base_frame_id, self.scan_frame_id, rclpy.time.Time())
134137
self.mat_base_to_laser = transformStampedToMatrix(transform)
135138

136139
except Exception as ex:
137-
self.get_logger().error('Transform lookup failed: %s' % str(ex))
140+
self.get_logger().error(f'Transform lookup failed: {str(ex)}')
138141

139-
def setupTimerCallback(self):
142+
def setupTimerCallback(self) -> None:
140143
# Publish initial identity odom transform & laser scan to warm up system
141144
self.tf_broadcaster.sendTransform(self.t_odom_to_base_link)
142145
if self.mat_base_to_laser is None:
143146
self.getBaseToLaserTf()
144147

145-
def clockTimerCallback(self):
148+
def clockTimerCallback(self) -> None:
146149
msg = Clock()
147150
msg.clock = self.get_clock().now().to_msg()
148151
self.clock_pub.publish(msg)
149152

150-
def cmdVelCallback(self, msg):
153+
def cmdVelCallback(self, msg: Twist) -> None:
151154
self.debug('Received cmd_vel')
152155
if self.initial_pose is None:
153156
# Don't consider velocities before the initial pose is set
154157
return
155158
self.curr_cmd_vel = msg
156159
self.curr_cmd_vel_time = self.get_clock().now()
157160

158-
def cmdVelStampedCallback(self, msg):
161+
def cmdVelStampedCallback(self, msg: TwistStamped) -> None:
159162
self.debug('Received cmd_vel')
160163
if self.initial_pose is None:
161164
# Don't consider velocities before the initial pose is set
162165
return
163166
self.curr_cmd_vel = msg.twist
164167
self.curr_cmd_vel_time = rclpy.time.Time.from_msg(msg.header.stamp)
165168

166-
def initialPoseCallback(self, msg):
169+
def initialPoseCallback(self, msg: PoseWithCovarianceStamped) -> None:
167170
self.info('Received initial pose!')
168171
if self.initial_pose is None:
169172
# Initialize transforms (map->odom as input pose, odom->base_link start from identity)
@@ -202,7 +205,7 @@ def initialPoseCallback(self, msg):
202205
tf_transformations.concatenate_matrices(mat_map_to_base_link, mat_base_link_to_odom)
203206
self.t_map_to_odom.transform = matrixToTransform(mat_map_to_odom)
204207

205-
def timerCallback(self):
208+
def timerCallback(self) -> None:
206209
# If no data, just republish existing transforms without change
207210
one_sec = Duration(seconds=1)
208211
if self.curr_cmd_vel is None or self.get_clock().now() - self.curr_cmd_vel_time > one_sec:
@@ -227,7 +230,7 @@ def timerCallback(self):
227230
self.publishTransforms(self.t_map_to_odom, self.t_odom_to_base_link)
228231
self.publishOdometry(self.t_odom_to_base_link)
229232

230-
def publishLaserScan(self):
233+
def publishLaserScan(self) -> None:
231234
# Publish a bogus laser scan for collision monitor
232235
self.scan_msg = LaserScan()
233236
self.scan_msg.header.stamp = (self.get_clock().now()).to_msg()
@@ -247,15 +250,16 @@ def publishLaserScan(self):
247250
self.getLaserScan(num_samples)
248251
self.scan_pub.publish(self.scan_msg)
249252

250-
def publishTransforms(self, map_to_odom, odom_to_base_link):
253+
def publishTransforms(self, map_to_odom: TransformStamped,
254+
odom_to_base_link: TransformStamped) -> None:
251255
map_to_odom.header.stamp = \
252256
(self.get_clock().now() + Duration(seconds=self.update_dur)).to_msg()
253257
odom_to_base_link.header.stamp = self.get_clock().now().to_msg()
254258
if self.publish_map_odom_tf:
255259
self.tf_broadcaster.sendTransform(map_to_odom)
256260
self.tf_broadcaster.sendTransform(odom_to_base_link)
257261

258-
def publishOdometry(self, odom_to_base_link):
262+
def publishOdometry(self, odom_to_base_link: TransformStamped) -> None:
259263
odom = Odometry()
260264
odom.header.stamp = self.get_clock().now().to_msg()
261265
odom.header.frame_id = 'odom'
@@ -266,22 +270,23 @@ def publishOdometry(self, odom_to_base_link):
266270
odom.twist.twist = self.curr_cmd_vel
267271
self.odom_pub.publish(odom)
268272

269-
def info(self, msg):
273+
def info(self, msg: str) -> None:
270274
self.get_logger().info(msg)
271275
return
272276

273-
def debug(self, msg):
277+
def debug(self, msg: str) -> None:
274278
self.get_logger().debug(msg)
275279
return
276280

277-
def getMap(self):
281+
def getMap(self) -> None:
278282
request = GetMap.Request()
279283
if self.map_client.wait_for_service(timeout_sec=5.0):
280284
# Request to get map
281285
future = self.map_client.call_async(request)
282286
rclpy.spin_until_future_complete(self, future)
283-
if future.result() is not None:
284-
self.map = future.result().map
287+
result = future.result()
288+
if result is not None:
289+
self.map = result.map
285290
self.get_logger().info('Laser scan will be populated using map data')
286291
else:
287292
self.get_logger().warn(
@@ -294,7 +299,7 @@ def getMap(self):
294299
'Laser scan will be populated using max range'
295300
)
296301

297-
def getLaserPose(self):
302+
def getLaserPose(self) -> tuple[float, float, float]:
298303
mat_map_to_odom = transformStampedToMatrix(self.t_map_to_odom)
299304
mat_odom_to_base = transformStampedToMatrix(self.t_odom_to_base_link)
300305

@@ -316,7 +321,7 @@ def getLaserPose(self):
316321

317322
return x, y, theta
318323

319-
def getLaserScan(self, num_samples):
324+
def getLaserScan(self, num_samples: int) -> None:
320325
if self.map is None or self.initial_pose is None or self.mat_base_to_laser is None:
321326
self.scan_msg.ranges = [self.scan_msg.range_max - 0.1] * num_samples
322327
return
@@ -358,7 +363,7 @@ def getLaserScan(self, num_samples):
358363
line_iterator.advance()
359364

360365

361-
def main():
366+
def main() -> None:
362367
rclpy.init()
363368
loopback_simulator = LoopbackSimulator()
364369
rclpy.spin(loopback_simulator)

‎nav2_loopback_sim/nav2_loopback_sim/utils.py‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import math
1717

1818
from geometry_msgs.msg import Quaternion, Transform
19+
from nav_msgs.msg import OccupancyGrid
1920
import numpy as np
2021
import tf_transformations
2122

@@ -24,7 +25,7 @@
2425
"""
2526

2627

27-
def addYawToQuat(quaternion, yaw_to_add):
28+
def addYawToQuat(quaternion: Quaternion, yaw_to_add: float) -> Quaternion:
2829
q = [quaternion.x, quaternion.y, quaternion.z, quaternion.w]
2930
q2 = tf_transformations.quaternion_from_euler(0.0, 0.0, yaw_to_add)
3031
q_new = tf_transformations.quaternion_multiply(q, q2)
@@ -36,7 +37,7 @@ def addYawToQuat(quaternion, yaw_to_add):
3637
return new_quaternion
3738

3839

39-
def transformStampedToMatrix(transform):
40+
def transformStampedToMatrix(transform: Transform) -> np.ndarray[np.float64, np.dtype[np.float64]]:
4041
translation = transform.transform.translation
4142
rotation = transform.transform.rotation
4243
matrix = np.eye(4)
@@ -53,7 +54,7 @@ def transformStampedToMatrix(transform):
5354
return matrix
5455

5556

56-
def matrixToTransform(matrix):
57+
def matrixToTransform(matrix: np.ndarray[np.float64, np.dtype[np.float64]]) -> Transform:
5758
transform = Transform()
5859
transform.translation.x = matrix[0, 3]
5960
transform.translation.y = matrix[1, 3]
@@ -66,11 +67,11 @@ def matrixToTransform(matrix):
6667
return transform
6768

6869

69-
def worldToMap(world_x, world_y, map_msg):
70+
def worldToMap(world_x: float, world_y: float, map_msg: OccupancyGrid) -> tuple[int, int]:
7071
map_x = int(math.floor((world_x - map_msg.info.origin.position.x) / map_msg.info.resolution))
7172
map_y = int(math.floor((world_y - map_msg.info.origin.position.y) / map_msg.info.resolution))
7273
return map_x, map_y
7374

7475

75-
def getMapOccupancy(x, y, map_msg):
76+
def getMapOccupancy(x: int, y: int, map_msg: OccupancyGrid): # type: ignore[no-untyped-def]
7677
return map_msg.data[y * map_msg.info.width + x]

‎nav2_loopback_sim/test/test_copyright.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,6 @@
1818

1919
@pytest.mark.copyright
2020
@pytest.mark.linter
21-
def test_copyright():
21+
def test_copyright() -> None:
2222
rc = main(argv=['.', 'test'])
2323
assert rc == 0, 'Found errors'

‎nav2_loopback_sim/test/test_flake8.py‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@
1818

1919
@pytest.mark.flake8
2020
@pytest.mark.linter
21-
def test_flake8():
21+
def test_flake8() -> None:
2222
rc, errors = main_with_errors(argv=[])
23-
assert rc == 0, 'Found %d code style errors / warnings:\n' % len(
24-
errors
25-
) + '\n'.join(errors)
23+
assert rc == 0, f'Found {len(errors)} code style errors / warnings:\n' + \
24+
'\n'.join(errors)

‎nav2_loopback_sim/test/test_pep257.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,6 @@
1818

1919
@pytest.mark.linter
2020
@pytest.mark.pep257
21-
def test_pep257():
21+
def test_pep257() -> None:
2222
rc = main(argv=['.', 'test'])
2323
assert rc == 0, 'Found code style errors / warnings'

‎tools/pyproject.toml‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,18 @@ module = [
2525
"ament_index_python.*",
2626
"nav2_common.*",
2727
"nav2_msgs.*",
28+
"nav2_simple_commander.*",
2829
"launch_testing.*",
2930
"action_msgs.*",
3031
"geometry_msgs.*",
3132
"sensor_msgs.*",
3233
"tf2_ros.*",
34+
"nav_msgs.*",
35+
"rosgraph_msgs.*",
36+
"tf_transformations.*",
37+
"ament_pep257.*",
38+
"ament_flake8.*",
39+
"ament_copyright.*",
3340
]
3441

3542
ignore_missing_imports = true

0 commit comments

Comments
 (0)