{"id":51100640,"url":"https://github.com/robotnikautomation/roscon2025_rbwatcher_workshop","last_synced_at":"2026-06-24T10:01:52.905Z","repository":{"id":321769570,"uuid":"1085827963","full_name":"RobotnikAutomation/roscon2025_rbwatcher_workshop","owner":"RobotnikAutomation","description":"ROSCon Spain 2025 - RB-Watcher ROS2 digital twin in an electrical substation for autonomous inspection using the Nav2 stack.","archived":false,"fork":false,"pushed_at":"2025-10-31T11:07:05.000Z","size":4349,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"jazzy-devel","last_synced_at":"2025-10-31T13:10:22.317Z","etag":null,"topics":["gazebo","groot","nav2","robotnik","ros-control","ros2","roscon"],"latest_commit_sha":null,"homepage":"https://robotnik.eu","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/RobotnikAutomation.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-10-29T15:08:26.000Z","updated_at":"2025-10-30T12:25:27.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/RobotnikAutomation/roscon2025_rbwatcher_workshop","commit_stats":null,"previous_names":["robotnikautomation/roscon2025_rbwatcher_workshop"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/RobotnikAutomation/roscon2025_rbwatcher_workshop","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RobotnikAutomation%2Froscon2025_rbwatcher_workshop","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RobotnikAutomation%2Froscon2025_rbwatcher_workshop/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RobotnikAutomation%2Froscon2025_rbwatcher_workshop/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RobotnikAutomation%2Froscon2025_rbwatcher_workshop/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/RobotnikAutomation","download_url":"https://codeload.github.com/RobotnikAutomation/roscon2025_rbwatcher_workshop/tar.gz/refs/heads/jazzy-devel","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RobotnikAutomation%2Froscon2025_rbwatcher_workshop/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34724756,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-24T02:00:07.484Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["gazebo","groot","nav2","robotnik","ros-control","ros2","roscon"],"created_at":"2026-06-24T10:01:49.575Z","updated_at":"2026-06-24T10:01:52.900Z","avatar_url":"https://github.com/RobotnikAutomation.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# roscon2025_rbwatcher_workshop\n\nRB-Watcher ROS 2 digital twin for autonomous inspection at an electrical substation. The project combines Gazebo Harmonic simulation, the Nav2 navigation stack, and BehaviorTree.CPP to demonstrate guided patrol and reactive PTZ tracking workflows.\n\n![Gazebo overview](docs/gz-view.png)\n\n---\n\n## Table of Contents\n\n1. [Overview](#overview)\n2. [Workshop Objective](#workshop-objective)\n3. [Requirements](#requirements)\n4. [Workspace Setup](#workspace-setup)\n5. [Build Instructions](#build-instructions)\n6. [Repository Structure](#repository-structure)\n7. [Behavior Tree Tooling](#behavior-tree-tooling)\n9. [Workshop Tasks](#workshop-tasks)\n   - [Task 1 – Launch the Simulation](#task-1--launch-the-simulation)\n   - [Task 2 – Controllers and Sensors](#task-2--controllers-and-sensors)\n   - [Task 3 – Basic Control](#task-3--basic-control)\n   - [Task 4 – Mapping](#task-4--mapping)\n   - [Task 5 – Localization \u0026 Navigation](#task-5--localization--navigation)\n   - [Task 6 – Perception](#task-6--perception)\n   - [Task 7 – PTZ Person Tracking](#task-7--ptz-person-tracking)\n   - [Task 8 – Autonomous Inspection with Behavior Trees](#task-8--autonomous-inspection-with-behavior-trees)\n10. [Open Exercises](#open-exercises)\n\n---\n\n## Overview\n\nThe RB-Watcher is a mobile platform designed for persistent inspection and surveillance missions. This workshop package provides:\n\n- A Gazebo Harmonic simulation of the RB-Watcher operating in an electrical substation.\n- Launch and configuration assets to exercise Nav2, perception, and PTZ tracking components.\n- A BehaviorTree.CPP action server (`rbwatcher_behaviors`) showcasing patrol-and-track missions using custom tree nodes.\n\n---\n\n## Workshop Objective\n\nThe guided exercises build toward a complete autonomous inspection workflow. Participants first stand up simulation, then generate an actionable map, localize and navigate with Nav2, plug in perception, and finally orchestrate everything with BehaviorTree-driven mission logic. By Task 8 you will understand how routing, detection, and PTZ tracking interlock inside a maintainable, modular autonomy stack.\n\n---\n\n## Requirements\n\n- Ubuntu 24.04 with ROS 2 Jazzy and Gazebo Harmonic pre-installed.\n- Desktop-class GPU recommended for running the Gazebo world and RViz simultaneously.\n- Basic familiarity with ROS 2 CLI tools and the `colcon` build system.\n\n---\n\n## Workspace Setup\n\n```bash\nmkdir -p ~/workspaces/roscon_ws/src\ncd ~/workspaces/roscon_ws/src\ngit clone --recurse-submodules -b jazzy-devel https://github.com/RobotnikAutomation/roscon2025_rbwatcher_workshop.git\n```\n\nInstall system dependencies and resolve ROS package requirements:\n\n```bash\ncd ~/workspaces/roscon_ws\nsudo apt-get update\nrosdep update\nrosdep install --from-paths src --ignore-src -r -y\nsudo apt install -y $(find -name '*ros-jazzy-robotnik*.deb')\n```\n\n---\n\n## Build Instructions\n\n```bash\ncd ~/workspaces/roscon_ws\ncolcon build --symlink-install\nsource install/setup.bash\n```\n\nRe-source `install/setup.bash` in new terminals or add it to your shell profile.\n\n---\n\n## Repository Structure\n\nKey packages in this workspace:\n\n- `robotnik_gazebo_ignition`: Robotnik's Gazebo Harmonic simulation.\n- `electrical_substation_world`: Gazebo Harmonic environment used throughout the workshop.\n- `rbwatcher_description`: URDF/SDF assets, meshes, and robot configuration for RB-Watcher.\n- `rbwatcher_behaviors`: BehaviorTree.CPP action server plus custom Nav2 and PTZ tracker BT nodes.\n- `simple_person_detector`: Simulated perception pipeline that publishes person detections.\n- `ptz_tracker`: PTZ camera controller node that tracks detected persons using a joint trajectory controller.\n\nRefer to each package’s README for deeper details or configuration options.\n\n---\n\n## Behavior Tree Tooling\n\nVisualize and edit trees with [Groot2](https://www.behaviortree.dev/groot/). The snippet below downloads the latest AppImage directly from GitHub releases:\n\n```bash\ncd ~/Downloads\nwget \u003cGroot2 AppImage URL\u003e\nchmod +x Groot2-*.AppImage\n./Groot2-*.AppImage\n```\n\n![Groot screenshot](docs/groot-view.png)\n\n---\n\n## Workshop Tasks\n\n### Task 1 – Launch the Simulation\n\nLaunch the electrical substation world:\n\n```bash\nros2 launch robotnik_gazebo_ignition spawn_world.launch.py \\\n  world_path:=$(ros2 pkg prefix electrical_substation_world)/share/electrical_substation_world/worlds/electrical_substation.world \\\n  gui:=true\n```\n\nTips:\n\n- Set `gui:=false` for a headless Gazebo session.\n- Override `world_path` to experiment with alternative scenes.\n- Reduce graphics load on low-powered machines:\n\n```bash\nexport LOW_PERFORMANCE_SIMULATION=true\n```\n\nSpawn the RB-Watcher robot:\n\n```bash\nros2 launch robotnik_gazebo_ignition spawn_robot.launch.py \\\n  robot:=rbwatcher x:=-19 y:=6 run_rviz:=true\n```\n\n![Robot in Gazebo](docs/world\u0026robot.png)\n\nAdditional tweaks:\n\n- Toggle RViz autostart via `run_rviz:=false`.\n- Adjust initial pose with `x`/`y` parameters.\n- Spawn extra robots by providing a unique `robot_id` so that namespaces stay isolated.\n\n```bash\nros2 launch robotnik_gazebo_ignition spawn_robot.launch.py \\\n  robot:=rbwatcher x:=-19 y:=4 robot_id:=robot_2 run_rviz:=false\n```\n\n\u003e ⚠️ **Namespace reminder:** All subsequent command snippets assume the default namespace `/robot`. If you launch with a custom `robot_id` such as `robot_2`, replace `/robot` with your namespace (for example `/robot_2`) in every topic or action name that follows.\n\n---\n\n### Task 2 – Controllers and Sensors\n\nInspect command topics and sensor streams:\n\n- Base odometry:\n\n  ```bash\n  ros2 topic echo /robot/robotnik_base_control/odom\n  ```\n\n- PTZ RGB-D camera (joint trajectory controlled):\n\n  ```bash\n  ros2 run rqt_image_view rqt_image_view /robot/top_ptz_rgbd_camera/color/image_raw\n  ```\n\n- Front RGB camera:\n\n  ```bash\n  ros2 run rqt_image_view rqt_image_view /robot/front_rgbd_camera/color/image_raw\n  ```\n\n- 3D LIDAR point cloud (visualize in RViz):\n\n  ![3D Lidar in RViz](docs/rviz-3d-lidar.png)\n\n- IMU stream:\n\n  ```bash\n  ros2 topic echo /robot/imu/data\n  ```\n\n---\n\n### Task 3 – Basic Control\n\n- Teleoperate the base using keyboard commands:\n\n  ```bash\n  ros2 run teleop_twist_keyboard teleop_twist_keyboard \\\n    --ros-args -r cmd_vel:=/robot/robotnik_base_control/cmd_vel -p stamped:=true\n  ```\n\n- Alternatively, drive from RViz using the teleop panel:\n\n  ![RViz teleop](docs/rviz-teleop.png)\n\n- Command the PTZ camera with the joint trajectory controller plugin:\n\n  ```bash\n  ros2 run rqt_joint_trajectory_controller rqt_joint_trajectory_controller --ros-args --remap __ns:=/robot\n  ```\n\n  ![PTZ controller](docs/rqt-joint-trajectory-controller.png)\n\n---\n\n### Task 4 – Mapping\n\n*Context:* Nav2’s 2D planners and `slam_toolbox` consume `sensor_msgs/LaserScan`. The helper launch files flatten the robot’s 3D point cloud into a planar scan so SLAM and costmaps stay happy.\n\n1. Project the 3D LIDAR into a planar scan:\n\n   ```bash\n   ros2 launch robotnik_simulation_bringup laser_filters.launch.py\n   ```\n\n   ![PointCloud to LaserScan](docs/pc-to-laserscan.png)\n\n2. Run `slam_toolbox` and drive the robot to build a 2D map:\n\n   ```bash\n   ros2 launch robotnik_simulation_localization mapping_2d.launch.py\n   ```\n\n   ![SLAM in RViz](docs/mapping-2d.png)\n\n3. Save the resulting occupancy grid:\n\n   ```bash\n   ros2 run nav2_map_server map_saver_cli -f ~/map\n   ```\n\n   ![Saved map](docs/saved-map.png)\n\n---\n\n### Task 5 – Localization \u0026 Navigation\n\n*Context:* `NavigateThroughPoses` is a fire-and-forget mission—Nav2 visits each pose once and exits. `FollowWaypoints` suits patrols: it loops, tracks progress, and plays nicely with higher-level supervisors that pause/resume routes.\n\n**Localization**\n\n```bash\nros2 launch robotnik_simulation_localization localization.launch.py\n```\n\n![AMCL localization](docs/localization.png)\n\nSet the initial pose in RViz (2D Pose Estimate) and validate pose tracking while teleoperating the robot.\n\n**Navigation**\n\nLaunch Nav2:\n\n```bash\nros2 launch robotnik_simulation_navigation navigation.launch.py\n```\n\n![Nav2 overview](docs/navigation.png)\n\nSend goals via RViz or CLI:\n\n```bash\nros2 action send_goal /robot/navigate_to_pose nav2_msgs/action/NavigateToPose '{\n  \"pose\": {\n    \"header\": {\"frame_id\": \"robot_map\"},\n    \"pose\": {\n      \"position\": {\"x\": 1.5, \"y\": 0.5, \"z\": 0.0},\n      \"orientation\": {\"x\": 0.0, \"y\": 0.0, \"z\": 0.0, \"w\": 1.0}\n    }\n  }\n}'\n```\n\n![Navigation goal](docs/navigation-goal.png)\n\nWaypoint following examples:\n\nNavigate Through Poses\n\n```bash\nros2 action send_goal /robot/navigate_through_poses nav2_msgs/action/NavigateThroughPoses '{\n  \"poses\": [\n    {\n      \"header\": { \"frame_id\": \"robot_map\" },\n      \"pose\": {\n        \"position\": { \"x\": 0.0, \"y\": 0.0, \"z\": 0.0 },\n        \"orientation\": { \"x\": 0.0, \"y\": 0.0, \"z\": 0.0, \"w\": 1.0 }\n      }\n    },\n    {\n      \"header\": { \"frame_id\": \"robot_map\" },\n      \"pose\": {\n        \"position\": { \"x\": 6.0, \"y\": 0.0, \"z\": 0.0 },\n        \"orientation\": { \"x\": 0.0, \"y\": 0.0, \"z\": -0.707, \"w\": 0.707 }\n      }\n    },\n    {\n      \"header\": { \"frame_id\": \"robot_map\" },\n      \"pose\": {\n        \"position\": { \"x\": 6.0, \"y\": -6.0, \"z\": 0.0 },\n        \"orientation\": { \"x\": 0.0, \"y\": 0.0, \"z\": 1.0, \"w\": 0.0 }\n      }\n    },\n    {\n      \"header\": { \"frame_id\": \"robot_map\" },\n      \"pose\": {\n        \"position\": { \"x\": 0.0, \"y\": -6.0, \"z\": 0.0 },\n        \"orientation\": { \"x\": 0.0, \"y\": 0.0, \"z\": 0.707, \"w\": 0.707 }\n      }\n    }\n  ]\n}'\n```\n\nFollow Waypoints\n\n```bash\nros2 action send_goal /robot/follow_waypoints nav2_msgs/action/FollowWaypoints '{\n  \"number_of_loops\": 3,\n  \"poses\": [\n    {\n      \"header\": { \"frame_id\": \"robot_map\" },\n      \"pose\": {\n        \"position\": { \"x\": 0.0, \"y\": 0.0, \"z\": 0.0 },\n        \"orientation\": { \"x\": 0.0, \"y\": 0.0, \"z\": 0.0, \"w\": 1.0 }\n      }\n    },\n    {\n      \"header\": { \"frame_id\": \"robot_map\" },\n      \"pose\": {\n        \"position\": { \"x\": 6.0, \"y\": 0.0, \"z\": 0.0 },\n        \"orientation\": { \"x\": 0.0, \"y\": 0.0, \"z\": -0.707, \"w\": 0.707 }\n      }\n    },\n    {\n      \"header\": { \"frame_id\": \"robot_map\" },\n      \"pose\": {\n        \"position\": { \"x\": 6.0, \"y\": -6.0, \"z\": 0.0 },\n        \"orientation\": { \"x\": 0.0, \"y\": 0.0, \"z\": 1.0, \"w\": 0.0 }\n      }\n    },\n    {\n      \"header\": { \"frame_id\": \"robot_map\" },\n      \"pose\": {\n        \"position\": { \"x\": 0.0, \"y\": -6.0, \"z\": 0.0 },\n        \"orientation\": { \"x\": 0.0, \"y\": 0.0, \"z\": 0.707, \"w\": 0.707 }\n      }\n    }\n  ]\n}'\n```\n\n![Waypoints in RViz](docs/nav2-waypoints.png)\n\n---\n\n### Task 6 – Perception\n\nStart the simple person detector package:\n\n```bash\nros2 launch simple_person_detector simple_person_detector.launch.py\n```\n\n![Person detector](docs/person-detection.png)\n\nMonitor detection results:\n\n```bash\nros2 topic echo /person_detector/detected\nros2 topic echo /person_detector/detection_array\n```\n\n---\n\n### Task 7 – PTZ Person Tracking\n\nBring up the PTZ tracker node:\n\n```bash\nros2 launch ptz_tracker ptz_tracker.launch.py\n```\n\nSend a goal to start tracking:\n\n```bash\nros2 action send_goal /ptz_tracker/start_tracking ptz_tracker_interfaces/action/TrackTarget '{start: true}'\n```\n\nThe PTZ head attempts to keep the detected person centered until the action is canceled or detections stop.\n\n---\n\n### Task 8 – Autonomous Inspection with Behavior Trees\n\n*Context:* You could script the mission in one giant node, but Behavior Trees keep patrol, detection, tracking, and recovery logic modular. They make it easier to visualize state transitions, add fallbacks, and recover from failures.\n\nThe example tree implements a patrol-and-track policy:\n\n1. Patrol a navigation route using Nav2 waypoints.\n2. Interrupt patrol when a person is detected.\n3. Delegate to the PTZ tracker while detections persist.\n4. Resume patrol after the person leaves the scene.\n\n![Patrol tree in Groot](docs/groot2-patrol.png)\n\nStart the action server:\n\n```bash\nros2 launch rbwatcher_behaviors behavior_tree_action_server.launch.py\n```\n\nSubmit the default mission:\n\n```bash\nros2 action send_goal /execute_behavior_tree rbwatcher_behaviors/action/ExecuteBehaviorTree '{\n  target_waypoints: [\n    { header: { frame_id: \"robot_map\" }, pose: { position: { x: 0.0, y: 0.0, z: 0.0 }, orientation: { x: 0.0, y: 0.0, z: 0.0, w: 1.0 } } },\n    { header: { frame_id: \"robot_map\" }, pose: { position: { x: 6.0, y: 0.0, z: 0.0 }, orientation: { x: 0.0, y: 0.0, z: -0.707, w: 0.707 } } },\n    { header: { frame_id: \"robot_map\" }, pose: { position: { x: 6.0, y: -6.0, z: 0.0 }, orientation: { x: 0.0, y: 0.0, z: -0.707, w: 0.707 } } },\n    { header: { frame_id: \"robot_map\" }, pose: { position: { x: 0.0, y: -6.0, z: 0.0 }, orientation: { x: 0.0, y: 0.0, z: -0.707, w: 0.707 } } },        \n  ],\n  waypoint_loops: 1,\n  waypoint_start_index: 0,\n  tree_xml: \"\",\n  tree_path: \"config/default_tree.xml\"\n}'\n```\n\n![Detection vs patrol](docs/detection-patrol.png)\n\nThe `tree_path` can point to custom XML if you author alternative mission plans in Groot.\n\n---\n\n## Open Exercises\n\nReady to extend the workshop on your own? These drills progress from quick Behavior Tree tweaks to full autonomy experiments.\n\n### Level 1 – Behavior Tree Tweaks\n\n- **Inspection Patrol Remix:** Edit `config/default_tree.xml` in Groot2 so `FollowWaypoints` alternates between two inspection points instead of looping four. Bonus: wrap each waypoint with a `Delay` decorator (10 s) to simulate on-site inspection time.\n- **Detection Hold-Off:** Keep PTZ tracking while detections persist, but only resume patrol after `IsPersonDetected` has been false for 15 s. Combine timing decorators (for example `Delay` or `Timeout`) with a `RetryUntilSuccessful` guard to enforce the cool-down period.\n\n### Level 2 – New Behavior Tree Nodes\n\n- **Fixed PTZ Inspection:** Implement a `MovePTZToPreset` SyncActionNode that drives the PTZ trajectory controller to a preset (pan = 1.0, tilt = –0.5) and holds for 5 s. Insert it into the main tree so the robot pauses at Waypoint 1, executes the preset scan, then advances to Waypoint 2.\n\n### Level 3 – Perception-Driven Logic\n\n- **Range-Aware Detection:** Use `/person_detector/detection_array` to halt the patrol only when a detection is within 5 m. Extend the Bool condition or craft a new BT condition that checks the reported range before interrupting.\n\n### Level 4 – Navigation \u0026 Simulation Challenges\n\n- **Dynamic Obstacles:** Spawn moving obstacles in Gazebo and rerun the patrol. Tune `robotnik_simulation_navigation/config/nav2_params.yaml` (for example `controller_server.max_vel_x` or `global_costmap.inflation_radius`) to make the robot more cautious or assertive and observe the change.\n\n---\n\nHappy patrolling! For issues or PRs, open a ticket on the repository and include environment details plus relevant logs.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frobotnikautomation%2Froscon2025_rbwatcher_workshop","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frobotnikautomation%2Froscon2025_rbwatcher_workshop","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frobotnikautomation%2Froscon2025_rbwatcher_workshop/lists"}