Skip to content

Commit f0ea147

Browse files
committed
Merge branch 'main' into connection-lost-behavior
2 parents 66dd9f6 + 3009662 commit f0ea147

17 files changed

+791
-123
lines changed

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,24 @@ View the gripper in RViz:
107107
ros2 launch robotiq_description view_gripper.launch.py
108108
```
109109

110+
## Foxglove usage (user interface)
111+
112+
You can find the json configuration file in utils/ui/capra_ui.json. You need to load it into foxglove. On the device that you want to be connected (a.k.a. the jetson), you need to run the following command :
113+
114+
115+
You can make it as a service and start at boot :
116+
117+
```bash
118+
./utils/install.sh
119+
```
120+
121+
Otherwise, to start use the user interface with a development laptop, you can run it by only launching the launchfile :
122+
123+
```bash
124+
ros2 launch rove_launch_handler launch_handler.py
125+
```
126+
127+
110128
## Adding New Packages
111129

112130
To add a package for Rove, create it using the ROS2 command ([Creating Your First ROS2 Package](https://docs.ros.org/en/foxy/Tutorials/Beginner-Client-Libraries/Creating-Your-First-ROS2-Package.html)). Name it starting with `rove_` to ensure Git tracking. For non-Rove specific packages, create a separate repository and add it to `rove.repos`.

src/rove_description/urdf/flipper.urdf.xacro

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,13 @@
4242

4343

4444
<link name="flipper_${suffix}">
45+
<visual>
46+
<origin xyz="0 0 0" rpy="${mesh_rotation}" />
47+
<geometry>
48+
<mesh filename="https://raw.githubusercontent.com/clubcapra/rove/main/src/rove_description/meshes/flipper.stl"/>
49+
</geometry>
50+
<material name="rubber_color" />
51+
</visual>
4552
<visual>
4653
<origin xyz="0 0 0" rpy="${mesh_rotation}" />
4754
<geometry>

src/rove_description/urdf/rove.urdf.xacro

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@
2020
<!-- <xacro:include filename="$(find ovis_description)/urdf/ovis.urdf.xacro" /> -->
2121

2222
<link name="base_link">
23+
<visual>
24+
<origin xyz="0 0 0" rpy="0 0 0"/>
25+
<geometry>
26+
<mesh filename="https://raw.githubusercontent.com/clubcapra/rove/main/src/rove_description/meshes/base.stl"/>
27+
</geometry>
28+
<material name="frame_color"/>
29+
</visual>
2330
<visual>
2431
<origin xyz="0 0 0" rpy="0 0 0"/>
2532
<geometry>
@@ -40,11 +47,10 @@
4047
</inertial>
4148
</link>
4249

43-
4450
<!-- <xacro:rove_ros2_control/> -->
4551
<!-- Put these at -1 to mock the motors if working without the motors -->
52+
<!-- <xacro:odrive_control fl_id="21" rl_id="22" fr_id="23" rr_id="24"/> -->
4653
<xacro:odrive_control fl_id="21" rl_id="22" fr_id="23" rr_id="24"/>
47-
<!-- <xacro:odrive_control fl_id="-1" rl_id="-1" fr_id="-1" rr_id="-1"/> -->
4854

4955

5056
<!-- Insert ovis on top of rove -->

src/rove_description/urdf/track.urdf.xacro

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,13 @@
3232
</joint>
3333

3434
<link name="track_${suffix}">
35+
<visual>
36+
<origin xyz="0 0 0"/>
37+
<geometry>
38+
<mesh filename="https://raw.githubusercontent.com/clubcapra/rove/main/src/rove_description/meshes/track.stl"/>
39+
</geometry>
40+
<material name="rubber_color" />
41+
</visual>
3542
<visual>
3643
<origin xyz="0 0 0" rpy="0 0 0"/>
3744
<geometry>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
cmake_minimum_required(VERSION 3.5)
2+
project(rove_launch_handler)
3+
4+
find_package(ament_cmake REQUIRED)
5+
find_package(ament_cmake_python REQUIRED)
6+
find_package(rclcpp REQUIRED)
7+
find_package(rosidl_default_generators REQUIRED)
8+
find_package(rclpy REQUIRED)
9+
10+
# Convert message files to be used in python
11+
rosidl_generate_interfaces(${PROJECT_NAME}
12+
"srv/LaunchRequest.srv"
13+
"srv/LaunchListRequest.srv"
14+
)
15+
16+
# Install Python executables
17+
install(PROGRAMS
18+
scripts/launch_handler.py
19+
DESTINATION lib/${PROJECT_NAME}
20+
)
21+
22+
install(
23+
DIRECTORY launch srv
24+
DESTINATION share/${PROJECT_NAME}
25+
)
26+
27+
ament_export_dependencies(rosidl_default_runtime)
28+
29+
ament_package()
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from launch import LaunchDescription
2+
from launch_ros.actions import Node
3+
from launch.actions import IncludeLaunchDescription
4+
from launch_xml.launch_description_sources import XMLLaunchDescriptionSource
5+
from ament_index_python.packages import get_package_share_directory
6+
import os
7+
8+
def generate_launch_description():
9+
rosbridge_launch_file = os.path.join(
10+
get_package_share_directory('foxglove_bridge'),
11+
'launch',
12+
'foxglove_bridge_launch.xml'
13+
)
14+
15+
return LaunchDescription([
16+
IncludeLaunchDescription(
17+
XMLLaunchDescriptionSource(rosbridge_launch_file)
18+
),
19+
Node(
20+
package='rove_launch_handler',
21+
executable='launch_handler.py',
22+
name='launch_handler',
23+
output='screen'
24+
)
25+
])

src/rove_launch_handler/package.xml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?xml version="1.0"?>
2+
<package format="3">
3+
<name>rove_launch_handler</name>
4+
<version>0.0.0</version>
5+
<description>The rove_launch_handler package</description>
6+
<maintainer email="[email protected]">Capra</maintainer>
7+
<license>MIT</license>
8+
9+
<buildtool_depend>ament_cmake</buildtool_depend>
10+
11+
<build_depend>rclpy</build_depend>
12+
<build_depend>rosidl_default_generators</build_depend>
13+
14+
<exec_depend>rclpy</exec_depend>
15+
<exec_depend>rosidl_default_runtime</exec_depend>
16+
17+
<test_depend>ament_lint_auto</test_depend>
18+
<test_depend>ament_lint_common</test_depend>
19+
20+
<member_of_group>rosidl_interface_packages</member_of_group>
21+
22+
<export>
23+
<build_type>ament_cmake</build_type>
24+
</export>
25+
</package>

src/rove_launch_handler/resource/rove_launch_handler

Whitespace-only changes.
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
#!/usr/bin/env python3
2+
3+
import time
4+
import rclpy
5+
from rclpy.node import Node
6+
import signal
7+
import subprocess
8+
import os
9+
from rove_launch_handler.srv import LaunchRequest, LaunchListRequest
10+
11+
launched_files = {}
12+
13+
class LaunchFile:
14+
def __init__(self, package, file_name, pid):
15+
self.package = package
16+
self.file_name = file_name
17+
self.pid = pid
18+
19+
class LaunchMsg:
20+
def __init__(self):
21+
self.message = ""
22+
self.is_launched = False
23+
self.file_name = ""
24+
25+
def launch_file(package, file_name):
26+
command = f"ros2 launch {package} {file_name}"
27+
28+
p = subprocess.Popen(command, shell=True, preexec_fn=os.setsid)
29+
30+
launch_msg = LaunchMsg()
31+
# Sleep to make sure the launch command has time to fail if there's an error
32+
time.sleep(1)
33+
state = p.poll()
34+
launch_msg.file_name = file_name
35+
if state is None:
36+
launch_msg.message = f"{file_name} was launched"
37+
launched_files[file_name] = LaunchFile(package, file_name, p.pid)
38+
launch_msg.is_launched = True
39+
else:
40+
launch_msg.message = f"{file_name} was not launched"
41+
launch_msg.is_launched = False
42+
return launch_msg
43+
44+
def kill_launch_file(file_name):
45+
launch_msg = LaunchMsg()
46+
launch_msg.file_name = file_name
47+
if file_name in launched_files:
48+
pid = launched_files[file_name].pid
49+
try:
50+
os.killpg(os.getpgid(pid), signal.SIGINT)
51+
del launched_files[file_name]
52+
launch_msg.message = f"{file_name} was killed"
53+
launch_msg.is_launched = False
54+
except ProcessLookupError as e:
55+
launch_msg.message = f"Failed to kill {file_name}: {str(e)}"
56+
launch_msg.is_launched = False
57+
else:
58+
launch_msg.message = f"{file_name} was not launched"
59+
launch_msg.is_launched = False
60+
return launch_msg
61+
62+
def kill_all():
63+
for file_name in list(launched_files.keys()):
64+
kill_launch_file(file_name)
65+
66+
class LaunchHandlerService(Node):
67+
def __init__(self):
68+
super().__init__('launch_handler_service')
69+
self.srv_launch = self.create_service(LaunchRequest, 'launchHandler/launchFile', self.launch_callback)
70+
self.srv_list = self.create_service(LaunchListRequest, 'launchHandler/getAllLaunchedFiles', self.get_launched_files)
71+
self.get_logger().info("LaunchHandlerService node has been started.")
72+
73+
def launch_callback(self, request, response):
74+
package = request.package
75+
file_name = request.file_name
76+
if file_name not in launched_files:
77+
launch_msg = launch_file(package, file_name)
78+
else:
79+
launch_msg = kill_launch_file(file_name)
80+
response.message = launch_msg.message
81+
response.is_launched = launch_msg.is_launched
82+
response.file_name = launch_msg.file_name
83+
return response
84+
85+
def get_launched_files(self, request, response):
86+
response.packages = [lf.package for lf in launched_files.values()]
87+
response.files = list(launched_files.keys())
88+
return response
89+
90+
def main(args=None):
91+
rclpy.init(args=args)
92+
node = LaunchHandlerService()
93+
rclpy.get_default_context().on_shutdown(kill_all)
94+
rclpy.spin(node)
95+
node.destroy_node()
96+
rclpy.shutdown()
97+
98+
if __name__ == '__main__':
99+
main()
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[Unit]
2+
Descript=Robot launch script
3+
After=network.target
4+
5+
[Service]
6+
Environment="ROS_LOG_DIR=/var/log/ros2; ROS_DOMAIN_ID=96"
7+
ExecStart=/bin/bash -c 'source /home/simon/Workspace/capra/rove/install/setup.bash; ros2 launch rove_launch_handler launch_handler.launch.py;'
8+
RemainAfterExit=no
9+
Restart=on-failure
10+
RestartSec=2s
11+
12+
[Install]
13+
WantedBy=multi-user.target
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
---
2+
string[] packages
3+
string[] files
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
string package
2+
string file_name
3+
---
4+
string message
5+
bool is_launched
6+
string file_name
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Copyright 2015 Open Source Robotics Foundation, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from ament_copyright.main import main
16+
import pytest
17+
18+
19+
# Remove the `skip` decorator once the source file(s) have a copyright header
20+
@pytest.mark.skip(reason='No copyright header has been placed in the generated source file.')
21+
@pytest.mark.copyright
22+
@pytest.mark.linter
23+
def test_copyright():
24+
rc = main(argv=['.', 'test'])
25+
assert rc == 0, 'Found errors'
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Copyright 2017 Open Source Robotics Foundation, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from ament_flake8.main import main_with_errors
16+
import pytest
17+
18+
19+
@pytest.mark.flake8
20+
@pytest.mark.linter
21+
def test_flake8():
22+
rc, errors = main_with_errors(argv=[])
23+
assert rc == 0, \
24+
'Found %d code style errors / warnings:\n' % len(errors) + \
25+
'\n'.join(errors)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Copyright 2015 Open Source Robotics Foundation, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from ament_pep257.main import main
16+
import pytest
17+
18+
19+
@pytest.mark.linter
20+
@pytest.mark.pep257
21+
def test_pep257():
22+
rc = main(argv=['.', 'test'])
23+
assert rc == 0, 'Found code style errors / warnings'

utils/install.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/usr/bin/env bash
2+
sudo mkdir -p /var/log/ros2
3+
sudo chmod 775 /var/log/ros2
4+
sudo cp ./src/rove_launch_handler/scripts/launch_script.service /lib/systemd/system/launch_script.service
5+
sudo systemctl daemon-reload
6+
sudo systemctl enable launch_script.service
7+
sudo systemctl start launch_script.service

0 commit comments

Comments
 (0)