diff --git a/.circleci/config.yml b/.circleci/config.yml index 6ed07d76e9..822daab957 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -301,10 +301,15 @@ _commands: when: always - store_artifacts: path: << parameters.workspace >>/log/test - - store_test_results: - path: << parameters.workspace >>/test_results - store_artifacts: path: << parameters.workspace >>/test_results + - run: + name: Prepare Test Results + working_directory: << parameters.workspace >> + command: find test_results -name "Test.xml" -type f -delete + when: always + - store_test_results: + path: << parameters.workspace >>/test_results _steps: pre_checkout: &pre_checkout diff --git a/.devcontainer/caddy/Caddyfile b/.devcontainer/caddy/Caddyfile new file mode 100644 index 0000000000..1150579a47 --- /dev/null +++ b/.devcontainer/caddy/Caddyfile @@ -0,0 +1,132 @@ +# Snippet for global matchers and variables +# to logically expression request conditions +# E.g. for conditionally changing redirects +(globals) { + # Use gzip compression for all responses + encode gzip + + # Matcher for http request scheme. E.g. "http" or "https" + @http_scheme { + expression {http.request.scheme}=="https" || {header.X-Forwarded-Scheme}=="https" || {header.X-Forwarded-Proto}=="https" + } + # If any http scheme is "https", then use "wss" + vars @http_scheme WsScheme "wss" + # Else default to "ws" + vars WsScheme "ws" + + # Matcher for forwarded request headers + @host_forwarded { + header X-Forwarded-Host * + } + # If http headers exists, then use them + vars @host_forwarded ReqHost {header.X-Forwarded-Host} + # Else default to host in request + vars ReqHost {http.request.hostport} + + # Matcher for websocket connection upgrade requests + @websockets { + # Avoid case sensitivity issues when matching field values + # E.g. when values are rewritten by Codespace port forwarding + header_regexp Connection (?i)(Upgrade) + header Upgrade websocket + } +} + +# Snippet for redirect with given URL queries values +# to simplify remote development with web apps +# E.g auto redirect websocket URL to match request scheme +(redirect) { + # Configure redirect to match request scheme + vars LayoutUrl "/assets/foxglove/nav2_layout.json" + vars DataSourceUrl "{vars.WsScheme}://{vars.ReqHost}{args.0}/" + redir /autoconnect "{args.0}/?ds=foxglove-websocket&ds.url={vars.DataSourceUrl}" + redir /autolayout "{args.0}/?ds=foxglove-websocket&ds.url={vars.DataSourceUrl}&layoutUrl={vars.LayoutUrl}" +} + +# Snippet for dummy imports +(dummy) { +} + +# Snippet for enabling mobile web app features +# to improve user experience on small screen devices +# E.g. for enabling fullscreen mode on iOS and Android +(mobile) { + # Match for directory redirects to index.html + route / { + # Inject link to manifest just after tag + # https://developer.mozilla.org/docs/Web/Manifest + replace `` `` + } + # Redirect relative handle_path'ed manifest.json to /manifests directory + redir /manifest.json /assets{http.request.orig_uri.path.dir}manifest.json +} + +# Snippet for hosted web app using websockets +# to serve static files and reverse proxying connections +# E.g. for serving GzWeb and Foxglove web apps +(app) { + # handle and strip path prefix from redirect + handle_path {args.0}/* { + # Set root directory for static files + root * {http.vars.root}{args.0} + # Enable mobile web app features + import mobile + # Reverse proxy websockets to backend address + reverse_proxy @websockets {args.1} + # Import custom snippets + import {args.2} {args.0} + } +} + +# Listen for http requests on port 8080 +# regardless of hostname or domain address +# E.g. whatever Codespaces assigns to host +:8080 { + # Include global matchers and variables + import globals + root * {$ROOT_SRV:/srv} + file_server browse + + # Handle root content + # I.e. assets internal to workspace + handle /* { + # Template manifest.json files + templates */manifest.json { + mime application/json + } + } + + # Handle nav2 web app + # I.e. main landing page + handle_path /nav2/* { + root * {http.vars.root}/nav2 + import mobile + # Render markdown files as html + templates + } + + # Matcher for requests without browse query + @no_browse { + path / + not query browse=true + } + # Redirect to nav2 web app by default + redir @no_browse /nav2/ + + # Import app snippets for web apps + import app "/gzweb" "localhost:9090" "dummy" + import app "/foxglove" "localhost:8765" "redirect" + + # Handle glances web app + redir /glances /glances/ + handle_path /glances/* { + import mobile + # Reverse proxy to glances backend + reverse_proxy * "localhost:61208" + } + + # For debugging + # log { + # output file /var/log/caddy/server.log + # } +} diff --git a/.devcontainer/caddy/srv/assets/foxglove/manifest.json b/.devcontainer/caddy/srv/assets/foxglove/manifest.json new file mode 100644 index 0000000000..0b9b8b84d7 --- /dev/null +++ b/.devcontainer/caddy/srv/assets/foxglove/manifest.json @@ -0,0 +1,40 @@ +{ + "name": "Foxglove: {{placeholder "http.vars.ReqHost"}}", + "short_name": "Foxglove: {{placeholder "http.vars.ReqHost"}}", + "icons": [ + { + "src": "/media/icons/foxglove/any_icon_x512.webp", + "sizes": "512x512", + "type": "image/webp", + "purpose": "any" + }, + { + "src": "/media/icons/foxglove/maskable_icon_x512.webp", + "sizes": "512x512", + "type": "image/webp", + "purpose": "maskable" + } + ], + "id": "/foxglove/", + "start_url": "/foxglove/autoconnect", + "theme_color": "#6F3BE8", + "background_color": "#6F3BE8", + "display": "fullscreen", + "shortcuts" : [ + { + "name": "Auto Connect", + "url": "/foxglove/autoconnect", + "description": "Auto connect to default data source" + }, + { + "name": "Auto Layout", + "url": "/foxglove/autolayout", + "description": "Auto connect using default layout" + }, + { + "name": "Manual Connect", + "url": "/foxglove/", + "description": "Manually connect to data source" + } + ] +} diff --git a/.devcontainer/caddy/srv/assets/foxglove/nav2_layout.json b/.devcontainer/caddy/srv/assets/foxglove/nav2_layout.json new file mode 100644 index 0000000000..f92821fac4 --- /dev/null +++ b/.devcontainer/caddy/srv/assets/foxglove/nav2_layout.json @@ -0,0 +1,463 @@ +{ + "configById": { + "3D!18i6zy7": { + "layers": { + "845139cb-26bc-40b3-8161-8ab60af4baf5": { + "visible": true, + "frameLocked": true, + "label": "Grid", + "instanceId": "845139cb-26bc-40b3-8161-8ab60af4baf5", + "layerId": "foxglove.Grid", + "size": 10, + "divisions": 10, + "lineWidth": 1, + "color": "#A0A0A4ff", + "position": [ + 0, + 0, + 0 + ], + "rotation": [ + 0, + 0, + 0 + ], + "order": 1 + } + }, + "cameraState": { + "perspective": true, + "distance": 21.05263157877764, + "phi": 38.925517117715195, + "thetaOffset": -138.92710744521386, + "targetOffset": [ + -2.6847696124888896, + 0.2191229688744439, + 3.6086809432821955e-16 + ], + "target": [ + 0, + 0, + 0 + ], + "targetOrientation": [ + 0, + 0, + 0, + 1 + ], + "fovy": 45, + "near": 0.5, + "far": 5000 + }, + "followMode": "follow-pose", + "scene": { + "transforms": { + "showLabel": false, + "editable": false, + "labelSize": 0.049999999999999975, + "enablePreloading": false, + "lineWidth": 2 + } + }, + "transforms": { + "frame:camera_link": { + "visible": false + }, + "frame:camera_depth_frame": { + "visible": false + }, + "frame:camera_depth_optical_frame": { + "visible": false + }, + "frame:camera_rgb_frame": { + "visible": false + }, + "frame:camera_rgb_optical_frame": { + "visible": false + }, + "frame:imu_link": { + "visible": false + }, + "frame:caster_back_right_link": { + "visible": false + }, + "frame:caster_back_left_link": { + "visible": false + }, + "frame:odom": { + "visible": true + }, + "frame:base_footprint": { + "visible": true + }, + "frame:wheel_left_link": { + "visible": false + }, + "frame:wheel_right_link": { + "visible": false + }, + "frame:base_link": { + "visible": false + }, + "frame:base_scan": { + "visible": false + }, + "frame:map": { + "visible": true + } + }, + "topics": { + "/scan": { + "visible": true, + "colorField": "intensity", + "colorMode": "flat", + "colorMap": "turbo", + "pointSize": 5, + "flatColor": "#ff0000" + }, + "/global_costmap/costmap": { + "visible": true, + "maxColor": "#E800174d", + "unknownColor": "#5700ff4d", + "minColor": "#ffffff4d", + "invalidColor": "#ff00ff4d", + "colorMode": "costmap", + "alpha": 0.3 + }, + "/global_costmap/obstacle_layer": { + "visible": false + }, + "/global_costmap/voxel_marked_cloud": { + "visible": false + }, + "/goal_pose": { + "visible": false + }, + "/local_costmap/costmap": { + "visible": false + }, + "/local_costmap/voxel_layer": { + "visible": false + }, + "/local_costmap/clearing_endpoints": { + "visible": false, + "colorField": "x", + "colorMode": "colormap", + "colorMap": "turbo" + }, + "/map": { + "visible": true, + "minColor": "#ffffff", + "maxColor": "#000000", + "unknownColor": "#708986ff", + "frameLocked": false, + "colorMode": "map" + }, + "/amcl_pose": { + "visible": false + }, + "/local_plan": { + "visible": true, + "lineWidth": 0.01, + "gradient": [ + "#c8ff00c7", + "#00c8ffba" + ] + }, + "/plan": { + "visible": false, + "gradient": [ + "rgba(124, 107, 255, 1)", + "#ff6b6b" + ], + "lineWidth": 1 + }, + "/plan_smoothed": { + "visible": false + }, + "/received_global_plan": { + "visible": true, + "gradient": [ + "#ff0000c7", + "#6b70ffc2" + ], + "lineWidth": 0.02, + "type": "line", + "arrowScale": [ + 0.02, + 0.0015, + 0.0015 + ] + }, + "/transformed_global_plan": { + "visible": false + }, + "/robot_description": { + "visible": false + }, + "/cost_cloud": { + "visible": false + }, + "/initialpose": { + "visible": false + } + }, + "publish": { + "type": "pose_estimate", + "poseTopic": "/move_base_simple/goal", + "pointTopic": "", + "poseEstimateTopic": "/initialpose", + "poseEstimateXDeviation": 0.5, + "poseEstimateYDeviation": 0.5, + "poseEstimateThetaDeviation": 0.26179939 + }, + "followTf": "map" + }, + "DiagnosticSummary!3bo4e39": { + "minLevel": 0, + "pinnedIds": [], + "hardwareIdFilter": "", + "topicToRender": "/diagnostics", + "sortByLevel": true + }, + "RosOut!1iib9dq": { + "searchTerms": [], + "minLogLevel": 1 + }, + "3D!2agiaqk": { + "layers": { + "845139cb-26bc-40b3-8161-8ab60af4baf5": { + "visible": false, + "frameLocked": true, + "label": "Grid", + "instanceId": "845139cb-26bc-40b3-8161-8ab60af4baf5", + "layerId": "foxglove.Grid", + "size": 10, + "divisions": 10, + "lineWidth": 1, + "color": "#A0A0A4ff", + "position": [ + 0, + 0, + 0 + ], + "rotation": [ + 0, + 0, + 0 + ], + "order": 1 + } + }, + "cameraState": { + "perspective": true, + "distance": 4.078136514883917, + "phi": 56.068374260572, + "thetaOffset": 92.50000000000723, + "targetOffset": [ + 0.03816360663426963, + 0.15755079607173259, + 7.341598429161142e-18 + ], + "target": [ + 0, + 0, + 0 + ], + "targetOrientation": [ + 0, + 0, + 0, + 1 + ], + "fovy": 45, + "near": 0.5, + "far": 5000 + }, + "followMode": "follow-pose", + "scene": { + "transforms": { + "showLabel": true, + "editable": false, + "labelSize": 0.049999999999999975, + "enablePreloading": false, + "lineWidth": 2 + } + }, + "transforms": { + "frame:camera_link": { + "visible": false + }, + "frame:camera_depth_frame": { + "visible": false + }, + "frame:camera_depth_optical_frame": { + "visible": false + }, + "frame:camera_rgb_frame": { + "visible": false + }, + "frame:camera_rgb_optical_frame": { + "visible": false + }, + "frame:imu_link": { + "visible": false + }, + "frame:caster_back_right_link": { + "visible": false + }, + "frame:caster_back_left_link": { + "visible": false + }, + "frame:odom": { + "visible": true + }, + "frame:base_footprint": { + "visible": true + }, + "frame:wheel_left_link": { + "visible": false + }, + "frame:wheel_right_link": { + "visible": false + }, + "frame:base_link": { + "visible": false + }, + "frame:base_scan": { + "visible": false + }, + "frame:map": { + "visible": true + } + }, + "topics": { + "/scan": { + "visible": true, + "colorField": "intensity", + "colorMode": "flat", + "colorMap": "turbo", + "pointSize": 5, + "flatColor": "#ff0000" + }, + "/global_costmap/costmap": { + "visible": true, + "maxColor": "#E800174d", + "unknownColor": "#5700ff4d", + "minColor": "#ffffff4d", + "invalidColor": "#ff00ff4d", + "colorMode": "costmap", + "alpha": 0.3 + }, + "/global_costmap/obstacle_layer": { + "visible": false + }, + "/global_costmap/voxel_marked_cloud": { + "visible": false + }, + "/goal_pose": { + "visible": false + }, + "/local_costmap/costmap": { + "visible": false + }, + "/local_costmap/voxel_layer": { + "visible": false + }, + "/local_costmap/clearing_endpoints": { + "visible": false, + "colorField": "x", + "colorMode": "colormap", + "colorMap": "turbo" + }, + "/map": { + "visible": true, + "minColor": "#ffffff", + "maxColor": "#000000", + "unknownColor": "#708986ff", + "frameLocked": false, + "colorMode": "map" + }, + "/amcl_pose": { + "visible": false + }, + "/local_plan": { + "visible": true, + "lineWidth": 0.01, + "gradient": [ + "#c8ff00c7", + "#00c8ffba" + ] + }, + "/plan": { + "visible": false, + "gradient": [ + "rgba(124, 107, 255, 1)", + "#ff6b6b" + ], + "lineWidth": 1 + }, + "/plan_smoothed": { + "visible": false + }, + "/received_global_plan": { + "visible": true, + "gradient": [ + "#ff0000c7", + "#6b70ffc2" + ], + "lineWidth": 0.02, + "type": "line", + "arrowScale": [ + 0.02, + 0.0015, + 0.0015 + ] + }, + "/transformed_global_plan": { + "visible": false + }, + "/robot_description": { + "visible": true + }, + "/cost_cloud": { + "visible": false + }, + "/initialpose": { + "visible": false + } + }, + "publish": { + "type": "pose_estimate", + "poseTopic": "/move_base_simple/goal", + "pointTopic": "", + "poseEstimateTopic": "/initialpose", + "poseEstimateXDeviation": 0.5, + "poseEstimateYDeviation": 0.5, + "poseEstimateThetaDeviation": 0.26179939 + }, + "followTf": "base_link" + } + }, + "globalVariables": {}, + "userNodes": {}, + "playbackConfig": { + "speed": 1 + }, + "layout": { + "first": "3D!18i6zy7", + "second": { + "first": "DiagnosticSummary!3bo4e39", + "second": { + "first": "RosOut!1iib9dq", + "second": "3D!2agiaqk", + "direction": "column" + }, + "direction": "column", + "splitPercentage": 28.227360308285164 + }, + "direction": "row", + "splitPercentage": 74.87855655794587 + } + } \ No newline at end of file diff --git a/.devcontainer/caddy/srv/assets/glances/manifest.json b/.devcontainer/caddy/srv/assets/glances/manifest.json new file mode 100644 index 0000000000..78d27cb0bd --- /dev/null +++ b/.devcontainer/caddy/srv/assets/glances/manifest.json @@ -0,0 +1,40 @@ +{ + "name": "Glances: {{placeholder "http.vars.ReqHost"}}", + "short_name": "Glances: {{placeholder "http.vars.ReqHost"}}", + "icons": [ + { + "src": "/media/icons/glances/any_icon_x512.webp", + "sizes": "512x512", + "type": "image/webp", + "purpose": "any" + }, + { + "src": "/media/icons/glances/maskable_icon_x512.webp", + "sizes": "512x512", + "type": "image/webp", + "purpose": "maskable" + } + ], + "id": "/glances/", + "start_url": "/glances/", + "theme_color": "#2C363F", + "background_color": "#2C363F", + "display": "fullscreen", + "shortcuts" : [ + { + "name": "Refresh 1sec", + "url": "/glances/1", + "description": "Refresh page every 1 second" + }, + { + "name": "Refresh 5sec", + "url": "/glances/5", + "description": "Refresh page every 5 seconds" + }, + { + "name": "Refresh 10sec", + "url": "/glances/10", + "description": "Refresh page every 10 seconds" + } + ] +} diff --git a/.devcontainer/caddy/srv/assets/gzweb/manifest.json b/.devcontainer/caddy/srv/assets/gzweb/manifest.json new file mode 100644 index 0000000000..0596075f15 --- /dev/null +++ b/.devcontainer/caddy/srv/assets/gzweb/manifest.json @@ -0,0 +1,23 @@ +{ + "name": "Gzweb: {{placeholder "http.vars.ReqHost"}}", + "short_name": "Gzweb: {{placeholder "http.vars.ReqHost"}}", + "icons": [ + { + "src": "/media/icons/gzweb/any_icon_x512.webp", + "sizes": "512x512", + "type": "image/webp", + "purpose": "any" + }, + { + "src": "/media/icons/gzweb/maskable_icon_x512.webp", + "sizes": "512x512", + "type": "image/webp", + "purpose": "maskable" + } + ], + "id": "/gzweb/", + "start_url": "/gzweb/", + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "fullscreen" +} diff --git a/.devcontainer/caddy/srv/assets/nav2/manifest.json b/.devcontainer/caddy/srv/assets/nav2/manifest.json new file mode 100644 index 0000000000..f482a47e80 --- /dev/null +++ b/.devcontainer/caddy/srv/assets/nav2/manifest.json @@ -0,0 +1,23 @@ +{ + "name": "Nav2: {{placeholder "http.vars.ReqHost"}}", + "short_name": "Nav2: {{placeholder "http.vars.ReqHost"}}", + "icons": [ + { + "src": "/media/icons/nav2/any_icon_x512.webp", + "sizes": "512x512", + "type": "image/webp", + "purpose": "any" + }, + { + "src": "/media/icons/nav2/maskable_icon_x512.webp", + "sizes": "512x512", + "type": "image/webp", + "purpose": "maskable" + } + ], + "id": "/nav2/", + "start_url": "/nav2/", + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} diff --git a/.devcontainer/caddy/srv/nav2/github-markdown.css b/.devcontainer/caddy/srv/nav2/github-markdown.css new file mode 100644 index 0000000000..049cae6b29 --- /dev/null +++ b/.devcontainer/caddy/srv/nav2/github-markdown.css @@ -0,0 +1,1102 @@ +@media (prefers-color-scheme: dark) { + .markdown-body { + color-scheme: dark; + --color-prettylights-syntax-comment: #8b949e; + --color-prettylights-syntax-constant: #79c0ff; + --color-prettylights-syntax-entity: #d2a8ff; + --color-prettylights-syntax-storage-modifier-import: #c9d1d9; + --color-prettylights-syntax-entity-tag: #7ee787; + --color-prettylights-syntax-keyword: #ff7b72; + --color-prettylights-syntax-string: #a5d6ff; + --color-prettylights-syntax-variable: #ffa657; + --color-prettylights-syntax-brackethighlighter-unmatched: #f85149; + --color-prettylights-syntax-invalid-illegal-text: #f0f6fc; + --color-prettylights-syntax-invalid-illegal-bg: #8e1519; + --color-prettylights-syntax-carriage-return-text: #f0f6fc; + --color-prettylights-syntax-carriage-return-bg: #b62324; + --color-prettylights-syntax-string-regexp: #7ee787; + --color-prettylights-syntax-markup-list: #f2cc60; + --color-prettylights-syntax-markup-heading: #1f6feb; + --color-prettylights-syntax-markup-italic: #c9d1d9; + --color-prettylights-syntax-markup-bold: #c9d1d9; + --color-prettylights-syntax-markup-deleted-text: #ffdcd7; + --color-prettylights-syntax-markup-deleted-bg: #67060c; + --color-prettylights-syntax-markup-inserted-text: #aff5b4; + --color-prettylights-syntax-markup-inserted-bg: #033a16; + --color-prettylights-syntax-markup-changed-text: #ffdfb6; + --color-prettylights-syntax-markup-changed-bg: #5a1e02; + --color-prettylights-syntax-markup-ignored-text: #c9d1d9; + --color-prettylights-syntax-markup-ignored-bg: #1158c7; + --color-prettylights-syntax-meta-diff-range: #d2a8ff; + --color-prettylights-syntax-brackethighlighter-angle: #8b949e; + --color-prettylights-syntax-sublimelinter-gutter-mark: #484f58; + --color-prettylights-syntax-constant-other-reference-link: #a5d6ff; + --color-fg-default: #c9d1d9; + --color-fg-muted: #8b949e; + --color-fg-subtle: #6e7681; + --color-canvas-default: #0d1117; + --color-canvas-subtle: #161b22; + --color-border-default: #30363d; + --color-border-muted: #21262d; + --color-neutral-muted: rgba(110,118,129,0.4); + --color-accent-fg: #58a6ff; + --color-accent-emphasis: #1f6feb; + --color-attention-subtle: rgba(187,128,9,0.15); + --color-danger-fg: #f85149; + } + } + + @media (prefers-color-scheme: light) { + .markdown-body { + color-scheme: light; + --color-prettylights-syntax-comment: #6e7781; + --color-prettylights-syntax-constant: #0550ae; + --color-prettylights-syntax-entity: #8250df; + --color-prettylights-syntax-storage-modifier-import: #24292f; + --color-prettylights-syntax-entity-tag: #116329; + --color-prettylights-syntax-keyword: #cf222e; + --color-prettylights-syntax-string: #0a3069; + --color-prettylights-syntax-variable: #953800; + --color-prettylights-syntax-brackethighlighter-unmatched: #82071e; + --color-prettylights-syntax-invalid-illegal-text: #f6f8fa; + --color-prettylights-syntax-invalid-illegal-bg: #82071e; + --color-prettylights-syntax-carriage-return-text: #f6f8fa; + --color-prettylights-syntax-carriage-return-bg: #cf222e; + --color-prettylights-syntax-string-regexp: #116329; + --color-prettylights-syntax-markup-list: #3b2300; + --color-prettylights-syntax-markup-heading: #0550ae; + --color-prettylights-syntax-markup-italic: #24292f; + --color-prettylights-syntax-markup-bold: #24292f; + --color-prettylights-syntax-markup-deleted-text: #82071e; + --color-prettylights-syntax-markup-deleted-bg: #ffebe9; + --color-prettylights-syntax-markup-inserted-text: #116329; + --color-prettylights-syntax-markup-inserted-bg: #dafbe1; + --color-prettylights-syntax-markup-changed-text: #953800; + --color-prettylights-syntax-markup-changed-bg: #ffd8b5; + --color-prettylights-syntax-markup-ignored-text: #eaeef2; + --color-prettylights-syntax-markup-ignored-bg: #0550ae; + --color-prettylights-syntax-meta-diff-range: #8250df; + --color-prettylights-syntax-brackethighlighter-angle: #57606a; + --color-prettylights-syntax-sublimelinter-gutter-mark: #8c959f; + --color-prettylights-syntax-constant-other-reference-link: #0a3069; + --color-fg-default: #24292f; + --color-fg-muted: #57606a; + --color-fg-subtle: #6e7781; + --color-canvas-default: #ffffff; + --color-canvas-subtle: #f6f8fa; + --color-border-default: #d0d7de; + --color-border-muted: hsla(210,18%,87%,1); + --color-neutral-muted: rgba(175,184,193,0.2); + --color-accent-fg: #0969da; + --color-accent-emphasis: #0969da; + --color-attention-subtle: #fff8c5; + --color-danger-fg: #cf222e; + } + } + + .markdown-body { + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; + margin: 0; + color: var(--color-fg-default); + background-color: var(--color-canvas-default); + font-family: -apple-system,BlinkMacSystemFont,"Segoe UI","Noto Sans",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"; + font-size: 16px; + line-height: 1.5; + word-wrap: break-word; + } + + .markdown-body .octicon { + display: inline-block; + fill: currentColor; + vertical-align: text-bottom; + } + + .markdown-body h1:hover .anchor .octicon-link:before, + .markdown-body h2:hover .anchor .octicon-link:before, + .markdown-body h3:hover .anchor .octicon-link:before, + .markdown-body h4:hover .anchor .octicon-link:before, + .markdown-body h5:hover .anchor .octicon-link:before, + .markdown-body h6:hover .anchor .octicon-link:before { + width: 16px; + height: 16px; + content: ' '; + display: inline-block; + background-color: currentColor; + -webkit-mask-image: url("data:image/svg+xml,"); + mask-image: url("data:image/svg+xml,"); + } + + .markdown-body details, + .markdown-body figcaption, + .markdown-body figure { + display: block; + } + + .markdown-body summary { + display: list-item; + } + + .markdown-body [hidden] { + display: none !important; + } + + .markdown-body a { + background-color: transparent; + color: var(--color-accent-fg); + text-decoration: none; + } + + .markdown-body abbr[title] { + border-bottom: none; + text-decoration: underline dotted; + } + + .markdown-body b, + .markdown-body strong { + font-weight: var(--base-text-weight-semibold, 600); + } + + .markdown-body dfn { + font-style: italic; + } + + .markdown-body h1 { + margin: .67em 0; + font-weight: var(--base-text-weight-semibold, 600); + padding-bottom: .3em; + font-size: 2em; + border-bottom: 1px solid var(--color-border-muted); + } + + .markdown-body mark { + background-color: var(--color-attention-subtle); + color: var(--color-fg-default); + } + + .markdown-body small { + font-size: 90%; + } + + .markdown-body sub, + .markdown-body sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; + } + + .markdown-body sub { + bottom: -0.25em; + } + + .markdown-body sup { + top: -0.5em; + } + + .markdown-body img { + border-style: none; + max-width: 100%; + box-sizing: content-box; + background-color: var(--color-canvas-default); + } + + .markdown-body code, + .markdown-body kbd, + .markdown-body pre, + .markdown-body samp { + font-family: monospace; + font-size: 1em; + } + + .markdown-body figure { + margin: 1em 40px; + } + + .markdown-body hr { + box-sizing: content-box; + overflow: hidden; + background: transparent; + border-bottom: 1px solid var(--color-border-muted); + height: .25em; + padding: 0; + margin: 24px 0; + background-color: var(--color-border-default); + border: 0; + } + + .markdown-body input { + font: inherit; + margin: 0; + overflow: visible; + font-family: inherit; + font-size: inherit; + line-height: inherit; + } + + .markdown-body [type=button], + .markdown-body [type=reset], + .markdown-body [type=submit] { + -webkit-appearance: button; + } + + .markdown-body [type=checkbox], + .markdown-body [type=radio] { + box-sizing: border-box; + padding: 0; + } + + .markdown-body [type=number]::-webkit-inner-spin-button, + .markdown-body [type=number]::-webkit-outer-spin-button { + height: auto; + } + + .markdown-body [type=search]::-webkit-search-cancel-button, + .markdown-body [type=search]::-webkit-search-decoration { + -webkit-appearance: none; + } + + .markdown-body ::-webkit-input-placeholder { + color: inherit; + opacity: .54; + } + + .markdown-body ::-webkit-file-upload-button { + -webkit-appearance: button; + font: inherit; + } + + .markdown-body a:hover { + text-decoration: underline; + } + + .markdown-body ::placeholder { + color: var(--color-fg-subtle); + opacity: 1; + } + + .markdown-body hr::before { + display: table; + content: ""; + } + + .markdown-body hr::after { + display: table; + clear: both; + content: ""; + } + + .markdown-body table { + border-spacing: 0; + border-collapse: collapse; + display: block; + width: max-content; + max-width: 100%; + overflow: auto; + } + + .markdown-body td, + .markdown-body th { + padding: 0; + } + + .markdown-body details summary { + cursor: pointer; + } + + .markdown-body details:not([open])>*:not(summary) { + display: none !important; + } + + .markdown-body a:focus, + .markdown-body [role=button]:focus, + .markdown-body input[type=radio]:focus, + .markdown-body input[type=checkbox]:focus { + outline: 2px solid var(--color-accent-fg); + outline-offset: -2px; + box-shadow: none; + } + + .markdown-body a:focus:not(:focus-visible), + .markdown-body [role=button]:focus:not(:focus-visible), + .markdown-body input[type=radio]:focus:not(:focus-visible), + .markdown-body input[type=checkbox]:focus:not(:focus-visible) { + outline: solid 1px transparent; + } + + .markdown-body a:focus-visible, + .markdown-body [role=button]:focus-visible, + .markdown-body input[type=radio]:focus-visible, + .markdown-body input[type=checkbox]:focus-visible { + outline: 2px solid var(--color-accent-fg); + outline-offset: -2px; + box-shadow: none; + } + + .markdown-body a:not([class]):focus, + .markdown-body a:not([class]):focus-visible, + .markdown-body input[type=radio]:focus, + .markdown-body input[type=radio]:focus-visible, + .markdown-body input[type=checkbox]:focus, + .markdown-body input[type=checkbox]:focus-visible { + outline-offset: 0; + } + + .markdown-body kbd { + display: inline-block; + padding: 3px 5px; + font: 11px ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace; + line-height: 10px; + color: var(--color-fg-default); + vertical-align: middle; + background-color: var(--color-canvas-subtle); + border: solid 1px var(--color-neutral-muted); + border-bottom-color: var(--color-neutral-muted); + border-radius: 6px; + box-shadow: inset 0 -1px 0 var(--color-neutral-muted); + } + + .markdown-body h1, + .markdown-body h2, + .markdown-body h3, + .markdown-body h4, + .markdown-body h5, + .markdown-body h6 { + margin-top: 24px; + margin-bottom: 16px; + font-weight: var(--base-text-weight-semibold, 600); + line-height: 1.25; + } + + .markdown-body h2 { + font-weight: var(--base-text-weight-semibold, 600); + padding-bottom: .3em; + font-size: 1.5em; + border-bottom: 1px solid var(--color-border-muted); + } + + .markdown-body h3 { + font-weight: var(--base-text-weight-semibold, 600); + font-size: 1.25em; + } + + .markdown-body h4 { + font-weight: var(--base-text-weight-semibold, 600); + font-size: 1em; + } + + .markdown-body h5 { + font-weight: var(--base-text-weight-semibold, 600); + font-size: .875em; + } + + .markdown-body h6 { + font-weight: var(--base-text-weight-semibold, 600); + font-size: .85em; + color: var(--color-fg-muted); + } + + .markdown-body p { + margin-top: 0; + margin-bottom: 10px; + } + + .markdown-body blockquote { + margin: 0; + padding: 0 1em; + color: var(--color-fg-muted); + border-left: .25em solid var(--color-border-default); + } + + .markdown-body ul, + .markdown-body ol { + margin-top: 0; + margin-bottom: 0; + padding-left: 2em; + } + + .markdown-body ol ol, + .markdown-body ul ol { + list-style-type: lower-roman; + } + + .markdown-body ul ul ol, + .markdown-body ul ol ol, + .markdown-body ol ul ol, + .markdown-body ol ol ol { + list-style-type: lower-alpha; + } + + .markdown-body dd { + margin-left: 0; + } + + .markdown-body tt, + .markdown-body code, + .markdown-body samp { + font-family: ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace; + font-size: 12px; + } + + .markdown-body pre { + margin-top: 0; + margin-bottom: 0; + font-family: ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace; + font-size: 12px; + word-wrap: normal; + } + + .markdown-body .octicon { + display: inline-block; + overflow: visible !important; + vertical-align: text-bottom; + fill: currentColor; + } + + .markdown-body input::-webkit-outer-spin-button, + .markdown-body input::-webkit-inner-spin-button { + margin: 0; + -webkit-appearance: none; + appearance: none; + } + + .markdown-body::before { + display: table; + content: ""; + } + + .markdown-body::after { + display: table; + clear: both; + content: ""; + } + + .markdown-body>*:first-child { + margin-top: 0 !important; + } + + .markdown-body>*:last-child { + margin-bottom: 0 !important; + } + + .markdown-body a:not([href]) { + color: inherit; + text-decoration: none; + } + + .markdown-body .absent { + color: var(--color-danger-fg); + } + + .markdown-body .anchor { + float: left; + padding-right: 4px; + margin-left: -20px; + line-height: 1; + } + + .markdown-body .anchor:focus { + outline: none; + } + + .markdown-body p, + .markdown-body blockquote, + .markdown-body ul, + .markdown-body ol, + .markdown-body dl, + .markdown-body table, + .markdown-body pre, + .markdown-body details { + margin-top: 0; + margin-bottom: 16px; + } + + .markdown-body blockquote>:first-child { + margin-top: 0; + } + + .markdown-body blockquote>:last-child { + margin-bottom: 0; + } + + .markdown-body h1 .octicon-link, + .markdown-body h2 .octicon-link, + .markdown-body h3 .octicon-link, + .markdown-body h4 .octicon-link, + .markdown-body h5 .octicon-link, + .markdown-body h6 .octicon-link { + color: var(--color-fg-default); + vertical-align: middle; + visibility: hidden; + } + + .markdown-body h1:hover .anchor, + .markdown-body h2:hover .anchor, + .markdown-body h3:hover .anchor, + .markdown-body h4:hover .anchor, + .markdown-body h5:hover .anchor, + .markdown-body h6:hover .anchor { + text-decoration: none; + } + + .markdown-body h1:hover .anchor .octicon-link, + .markdown-body h2:hover .anchor .octicon-link, + .markdown-body h3:hover .anchor .octicon-link, + .markdown-body h4:hover .anchor .octicon-link, + .markdown-body h5:hover .anchor .octicon-link, + .markdown-body h6:hover .anchor .octicon-link { + visibility: visible; + } + + .markdown-body h1 tt, + .markdown-body h1 code, + .markdown-body h2 tt, + .markdown-body h2 code, + .markdown-body h3 tt, + .markdown-body h3 code, + .markdown-body h4 tt, + .markdown-body h4 code, + .markdown-body h5 tt, + .markdown-body h5 code, + .markdown-body h6 tt, + .markdown-body h6 code { + padding: 0 .2em; + font-size: inherit; + } + + .markdown-body summary h1, + .markdown-body summary h2, + .markdown-body summary h3, + .markdown-body summary h4, + .markdown-body summary h5, + .markdown-body summary h6 { + display: inline-block; + } + + .markdown-body summary h1 .anchor, + .markdown-body summary h2 .anchor, + .markdown-body summary h3 .anchor, + .markdown-body summary h4 .anchor, + .markdown-body summary h5 .anchor, + .markdown-body summary h6 .anchor { + margin-left: -40px; + } + + .markdown-body summary h1, + .markdown-body summary h2 { + padding-bottom: 0; + border-bottom: 0; + } + + .markdown-body ul.no-list, + .markdown-body ol.no-list { + padding: 0; + list-style-type: none; + } + + .markdown-body ol[type=a] { + list-style-type: lower-alpha; + } + + .markdown-body ol[type=A] { + list-style-type: upper-alpha; + } + + .markdown-body ol[type=i] { + list-style-type: lower-roman; + } + + .markdown-body ol[type=I] { + list-style-type: upper-roman; + } + + .markdown-body ol[type="1"] { + list-style-type: decimal; + } + + .markdown-body div>ol:not([type]) { + list-style-type: decimal; + } + + .markdown-body ul ul, + .markdown-body ul ol, + .markdown-body ol ol, + .markdown-body ol ul { + margin-top: 0; + margin-bottom: 0; + } + + .markdown-body li>p { + margin-top: 16px; + } + + .markdown-body li+li { + margin-top: .25em; + } + + .markdown-body dl { + padding: 0; + } + + .markdown-body dl dt { + padding: 0; + margin-top: 16px; + font-size: 1em; + font-style: italic; + font-weight: var(--base-text-weight-semibold, 600); + } + + .markdown-body dl dd { + padding: 0 16px; + margin-bottom: 16px; + } + + .markdown-body table th { + font-weight: var(--base-text-weight-semibold, 600); + } + + .markdown-body table th, + .markdown-body table td { + padding: 6px 13px; + border: 1px solid var(--color-border-default); + } + + .markdown-body table tr { + background-color: var(--color-canvas-default); + border-top: 1px solid var(--color-border-muted); + } + + .markdown-body table tr:nth-child(2n) { + background-color: var(--color-canvas-subtle); + } + + .markdown-body table img { + background-color: transparent; + } + + .markdown-body img[align=right] { + padding-left: 20px; + } + + .markdown-body img[align=left] { + padding-right: 20px; + } + + .markdown-body .emoji { + max-width: none; + vertical-align: text-top; + background-color: transparent; + } + + .markdown-body span.frame { + display: block; + overflow: hidden; + } + + .markdown-body span.frame>span { + display: block; + float: left; + width: auto; + padding: 7px; + margin: 13px 0 0; + overflow: hidden; + border: 1px solid var(--color-border-default); + } + + .markdown-body span.frame span img { + display: block; + float: left; + } + + .markdown-body span.frame span span { + display: block; + padding: 5px 0 0; + clear: both; + color: var(--color-fg-default); + } + + .markdown-body span.align-center { + display: block; + overflow: hidden; + clear: both; + } + + .markdown-body span.align-center>span { + display: block; + margin: 13px auto 0; + overflow: hidden; + text-align: center; + } + + .markdown-body span.align-center span img { + margin: 0 auto; + text-align: center; + } + + .markdown-body span.align-right { + display: block; + overflow: hidden; + clear: both; + } + + .markdown-body span.align-right>span { + display: block; + margin: 13px 0 0; + overflow: hidden; + text-align: right; + } + + .markdown-body span.align-right span img { + margin: 0; + text-align: right; + } + + .markdown-body span.float-left { + display: block; + float: left; + margin-right: 13px; + overflow: hidden; + } + + .markdown-body span.float-left span { + margin: 13px 0 0; + } + + .markdown-body span.float-right { + display: block; + float: right; + margin-left: 13px; + overflow: hidden; + } + + .markdown-body span.float-right>span { + display: block; + margin: 13px auto 0; + overflow: hidden; + text-align: right; + } + + .markdown-body code, + .markdown-body tt { + padding: .2em .4em; + margin: 0; + font-size: 85%; + white-space: break-spaces; + background-color: var(--color-neutral-muted); + border-radius: 6px; + } + + .markdown-body code br, + .markdown-body tt br { + display: none; + } + + .markdown-body del code { + text-decoration: inherit; + } + + .markdown-body samp { + font-size: 85%; + } + + .markdown-body pre code { + font-size: 100%; + } + + .markdown-body pre>code { + padding: 0; + margin: 0; + word-break: normal; + white-space: pre; + background: transparent; + border: 0; + } + + .markdown-body .highlight { + margin-bottom: 16px; + } + + .markdown-body .highlight pre { + margin-bottom: 0; + word-break: normal; + } + + .markdown-body .highlight pre, + .markdown-body pre { + padding: 16px; + overflow: auto; + font-size: 85%; + line-height: 1.45; + background-color: var(--color-canvas-subtle); + border-radius: 6px; + } + + .markdown-body pre code, + .markdown-body pre tt { + display: inline; + max-width: auto; + padding: 0; + margin: 0; + overflow: visible; + line-height: inherit; + word-wrap: normal; + background-color: transparent; + border: 0; + } + + .markdown-body .csv-data td, + .markdown-body .csv-data th { + padding: 5px; + overflow: hidden; + font-size: 12px; + line-height: 1; + text-align: left; + white-space: nowrap; + } + + .markdown-body .csv-data .blob-num { + padding: 10px 8px 9px; + text-align: right; + background: var(--color-canvas-default); + border: 0; + } + + .markdown-body .csv-data tr { + border-top: 0; + } + + .markdown-body .csv-data th { + font-weight: var(--base-text-weight-semibold, 600); + background: var(--color-canvas-subtle); + border-top: 0; + } + + .markdown-body [data-footnote-ref]::before { + content: "["; + } + + .markdown-body [data-footnote-ref]::after { + content: "]"; + } + + .markdown-body .footnotes { + font-size: 12px; + color: var(--color-fg-muted); + border-top: 1px solid var(--color-border-default); + } + + .markdown-body .footnotes ol { + padding-left: 16px; + } + + .markdown-body .footnotes ol ul { + display: inline-block; + padding-left: 16px; + margin-top: 16px; + } + + .markdown-body .footnotes li { + position: relative; + } + + .markdown-body .footnotes li:target::before { + position: absolute; + top: -8px; + right: -8px; + bottom: -8px; + left: -24px; + pointer-events: none; + content: ""; + border: 2px solid var(--color-accent-emphasis); + border-radius: 6px; + } + + .markdown-body .footnotes li:target { + color: var(--color-fg-default); + } + + .markdown-body .footnotes .data-footnote-backref g-emoji { + font-family: monospace; + } + + .markdown-body .pl-c { + color: var(--color-prettylights-syntax-comment); + } + + .markdown-body .pl-c1, + .markdown-body .pl-s .pl-v { + color: var(--color-prettylights-syntax-constant); + } + + .markdown-body .pl-e, + .markdown-body .pl-en { + color: var(--color-prettylights-syntax-entity); + } + + .markdown-body .pl-smi, + .markdown-body .pl-s .pl-s1 { + color: var(--color-prettylights-syntax-storage-modifier-import); + } + + .markdown-body .pl-ent { + color: var(--color-prettylights-syntax-entity-tag); + } + + .markdown-body .pl-k { + color: var(--color-prettylights-syntax-keyword); + } + + .markdown-body .pl-s, + .markdown-body .pl-pds, + .markdown-body .pl-s .pl-pse .pl-s1, + .markdown-body .pl-sr, + .markdown-body .pl-sr .pl-cce, + .markdown-body .pl-sr .pl-sre, + .markdown-body .pl-sr .pl-sra { + color: var(--color-prettylights-syntax-string); + } + + .markdown-body .pl-v, + .markdown-body .pl-smw { + color: var(--color-prettylights-syntax-variable); + } + + .markdown-body .pl-bu { + color: var(--color-prettylights-syntax-brackethighlighter-unmatched); + } + + .markdown-body .pl-ii { + color: var(--color-prettylights-syntax-invalid-illegal-text); + background-color: var(--color-prettylights-syntax-invalid-illegal-bg); + } + + .markdown-body .pl-c2 { + color: var(--color-prettylights-syntax-carriage-return-text); + background-color: var(--color-prettylights-syntax-carriage-return-bg); + } + + .markdown-body .pl-sr .pl-cce { + font-weight: bold; + color: var(--color-prettylights-syntax-string-regexp); + } + + .markdown-body .pl-ml { + color: var(--color-prettylights-syntax-markup-list); + } + + .markdown-body .pl-mh, + .markdown-body .pl-mh .pl-en, + .markdown-body .pl-ms { + font-weight: bold; + color: var(--color-prettylights-syntax-markup-heading); + } + + .markdown-body .pl-mi { + font-style: italic; + color: var(--color-prettylights-syntax-markup-italic); + } + + .markdown-body .pl-mb { + font-weight: bold; + color: var(--color-prettylights-syntax-markup-bold); + } + + .markdown-body .pl-md { + color: var(--color-prettylights-syntax-markup-deleted-text); + background-color: var(--color-prettylights-syntax-markup-deleted-bg); + } + + .markdown-body .pl-mi1 { + color: var(--color-prettylights-syntax-markup-inserted-text); + background-color: var(--color-prettylights-syntax-markup-inserted-bg); + } + + .markdown-body .pl-mc { + color: var(--color-prettylights-syntax-markup-changed-text); + background-color: var(--color-prettylights-syntax-markup-changed-bg); + } + + .markdown-body .pl-mi2 { + color: var(--color-prettylights-syntax-markup-ignored-text); + background-color: var(--color-prettylights-syntax-markup-ignored-bg); + } + + .markdown-body .pl-mdr { + font-weight: bold; + color: var(--color-prettylights-syntax-meta-diff-range); + } + + .markdown-body .pl-ba { + color: var(--color-prettylights-syntax-brackethighlighter-angle); + } + + .markdown-body .pl-sg { + color: var(--color-prettylights-syntax-sublimelinter-gutter-mark); + } + + .markdown-body .pl-corl { + text-decoration: underline; + color: var(--color-prettylights-syntax-constant-other-reference-link); + } + + .markdown-body g-emoji { + display: inline-block; + min-width: 1ch; + font-family: "Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"; + font-size: 1em; + font-style: normal !important; + font-weight: var(--base-text-weight-normal, 400); + line-height: 1; + vertical-align: -0.075em; + } + + .markdown-body g-emoji img { + width: 1em; + height: 1em; + } + + .markdown-body .task-list-item { + list-style-type: none; + } + + .markdown-body .task-list-item label { + font-weight: var(--base-text-weight-normal, 400); + } + + .markdown-body .task-list-item.enabled label { + cursor: pointer; + } + + .markdown-body .task-list-item+.task-list-item { + margin-top: 4px; + } + + .markdown-body .task-list-item .handle { + display: none; + } + + .markdown-body .task-list-item-checkbox { + margin: 0 .2em .25em -1.4em; + vertical-align: middle; + } + + .markdown-body .contains-task-list:dir(rtl) .task-list-item-checkbox { + margin: 0 -1.6em .25em .2em; + } + + .markdown-body .contains-task-list { + position: relative; + } + + .markdown-body .contains-task-list:hover .task-list-item-convert-container, + .markdown-body .contains-task-list:focus-within .task-list-item-convert-container { + display: block; + width: auto; + height: 24px; + overflow: visible; + clip: auto; + } + + .markdown-body ::-webkit-calendar-picker-indicator { + filter: invert(50%); + } \ No newline at end of file diff --git a/.devcontainer/caddy/srv/nav2/index.html b/.devcontainer/caddy/srv/nav2/index.html new file mode 100644 index 0000000000..4bebe27f4c --- /dev/null +++ b/.devcontainer/caddy/srv/nav2/index.html @@ -0,0 +1,36 @@ +{{$pathParts := splitList "/" .OriginalReq.URL.Path}} +{{$markdownFilename := default "index" (slice $pathParts 2 | join "/")}} +{{$markdownFilePath := printf "/%s.md" $markdownFilename}} +{{if not (fileExists $markdownFilePath)}}{{httpError 404}}{{end}} +{{$markdownFile := (include $markdownFilePath | splitFrontMatter)}} +{{$title := default $markdownFilename $markdownFile.Meta.title}} + + + + + + {{$title}} + + + + + + + +
{{markdown $markdownFile.Body}}
+ + diff --git a/.devcontainer/caddy/srv/nav2/index.md b/.devcontainer/caddy/srv/nav2/index.md new file mode 100644 index 0000000000..60e2db1bfb --- /dev/null +++ b/.devcontainer/caddy/srv/nav2/index.md @@ -0,0 +1,51 @@ +{ + "title": "Nav2 App" +} +## Progressive Web Apps + +| PWAs | Shortcuts | +|-|-| +| [](/foxglove/autoconnect)
**Foxglove** | [**Auto Connect**](/foxglove/autoconnect)
[Auto Layout](/foxglove/autolayout)
[Manual](/foxglove/) | +| [](/gzweb/)
**Gzweb** | [**Auto Connect**](/gzweb/) | +| [](/glances/)
**Glances** | [**System Monitor**](/glances/)
[Refresh 1sec](/glances/1)
[Refresh 10sec](/glances/10) | +| [](/nav2/)
**Nav2** | [**App Launcher**](/nav2/)
[File Browser](/?browse=true) | + +## External Resources + +For more related documentation: + +- [Nav2 Documentation](https://navigation.ros.org) + - [Development Guides](https://navigation.ros.org/development_guides) + - [Dev Containers](https://navigation.ros.org/development_guides/devcontainer_docs) + +## Session Info + +Useful information about host server and remote client: + +|Key | Value | +|-|-| +| Host | `{{.Host}}` | +| Remote IP | `{{placeholder "http.request.remote.host"}}` | +| Date | `{{now}}` | + +### Server Diagnostics + +
+Websocket Debug + +For troubleshooting websocket connections: + +|Key | Value | +|-|-| +| `header.X-Forwarded-Host` | `{{placeholder "http.request.header.X-Forwarded-Host"}}` | +| `http.request.hostport` | `{{placeholder "http.request.hostport"}}` | +| `http.vars.ReqHost` | `{{placeholder "http.vars.ReqHost"}}` | + +|Key | Value | +|-|-| +| `http.request.scheme` | `{{placeholder "http.request.scheme"}}` | +| `header.X-Forwarded-Scheme` | `{{placeholder "http.request.header.X-Forwarded-Scheme"}}` | +| `header.X-Forwarded-Proto` | `{{placeholder "http.request.header.X-Forwarded-Proto"}}` | +| `http.vars.WsScheme` | `{{placeholder "http.vars.WsScheme"}}` | + +
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..09aaa5d31d --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,61 @@ +{ + "name": "Nav2", + "build": { + "dockerfile": "../Dockerfile", + "context": "..", + "target": "visualizer", + "cacheFrom": "ghcr.io/ros-planning/navigation2:main" + }, + "runArgs": [ + // "--cap-add=SYS_PTRACE", // enable debugging, e.g. gdb + // "--ipc=host", // shared memory transport with host, e.g. rviz GUIs + // "--network=host", // network access to host interfaces, e.g. eth0 + // "--pid=host", // DDS discovery with host, without --network=host + // "--privileged", // device access to host peripherals, e.g. USB + // "--security-opt=seccomp=unconfined", // enable debugging, e.g. gdb + ], + "workspaceFolder": "/opt/overlay_ws/src/navigation2", + "workspaceMount": "source=${localWorkspaceFolder},target=${containerWorkspaceFolder},type=bind", + "onCreateCommand": ".devcontainer/on-create-command.sh", + "updateContentCommand": ".devcontainer/update-content-command.sh", + "postCreateCommand": ".devcontainer/post-create-command.sh", + "remoteEnv": { + "OVERLAY_MIXINS": "release ccache lld", + "CCACHE_DIR": "/tmp/.ccache" + }, + "mounts": [ + { + "source": "ccache-${devcontainerId}", + "target": "/tmp/.ccache", + "type": "volume" + }, + { + "source": "overlay-${devcontainerId}", + "target": "/opt/overlay_ws", + "type": "volume" + } + ], + "features": { + // "ghcr.io/devcontainers/features/desktop-lite:1": {}, + "ghcr.io/devcontainers/features/github-cli:1": {} + }, + "customizations": { + "codespaces": { + "openFiles": [ + "doc/development/codespaces.md" + ] + }, + "vscode": { + "settings": {}, + "extensions": [ + "althack.ament-task-provider", + "eamodio.gitlens", + "esbenp.prettier-vscode", + "GitHub.copilot", + "ms-iot.vscode-ros", + "streetsidesoftware.code-spell-checker", + "twxs.cmake" + ] + } + } +} diff --git a/.devcontainer/on-create-command.sh b/.devcontainer/on-create-command.sh new file mode 100755 index 0000000000..f86d22bc8d --- /dev/null +++ b/.devcontainer/on-create-command.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Immediately catch all errors +set -eo pipefail + +# Uncomment for debugging +# set -x +# env + +git config --global --add safe.directory "*" + +.devcontainer/update-content-command.sh diff --git a/.devcontainer/post-create-command.sh b/.devcontainer/post-create-command.sh new file mode 100755 index 0000000000..9f64a2c6ab --- /dev/null +++ b/.devcontainer/post-create-command.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +# Immediately catch all errors +set -eo pipefail + +# Uncomment for debugging +# set -x +# env + +# Enable autocomplete for user +cp /etc/skel/.bashrc ~/ + +# Check if srv folder exists +if [ -d "$ROOT_SRV" ]; then + # Setup Nav2 web app + for dir in $OVERLAY_WS/src/navigation2/.devcontainer/caddy/srv/*; \ + do if [ -d "$dir" ]; then ln -s "$dir" $ROOT_SRV; fi done +fi diff --git a/.devcontainer/update-content-command.sh b/.devcontainer/update-content-command.sh new file mode 100755 index 0000000000..2bdbf5cc1a --- /dev/null +++ b/.devcontainer/update-content-command.sh @@ -0,0 +1,60 @@ +#!/bin/bash + +# Immediately catch all errors +set -eo pipefail + +# Uncomment for debugging +# set -x +# env + +cd $OVERLAY_WS + +colcon cache lock + +BUILD_UNFINISHED=$( + colcon list \ + --names-only \ + --packages-skip-build-finished \ + | xargs) +echo BUILD_UNFINISHED: $BUILD_UNFINISHED + +BUILD_FAILED=$( + colcon list \ + --names-only \ + --packages-select-build-failed \ + | xargs) +echo BUILD_FAILED: $BUILD_FAILED + +BUILD_INVALID=$( + colcon list \ + --names-only \ + --packages-select-cache-invalid \ + --packages-select-cache-key build \ + | xargs) +echo BUILD_INVALID: $BUILD_INVALID + +BUILD_PACKAGES="" +if [ -n "$BUILD_UNFINISHED" ] || \ + [ -n "$BUILD_FAILED" ] || \ + [ -n "$BUILD_INVALID" ] +then + BUILD_PACKAGES=$( + colcon list \ + --names-only \ + --packages-above \ + $BUILD_UNFINISHED \ + $BUILD_FAILED \ + $BUILD_INVALID \ + | xargs) +fi +echo BUILD_PACKAGES: $BUILD_PACKAGES + +# colcon clean packages --yes \ +# --packages-select ${BUILD_PACKAGES} \ +# --base-select install + +. $UNDERLAY_WS/install/setup.sh +colcon build \ + --symlink-install \ + --mixin $OVERLAY_MIXINS \ + --packages-select ${BUILD_PACKAGES} diff --git a/.dockerignore b/.dockerignore index 5e31fef9d5..19c4cc591c 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,10 +2,11 @@ # Repo .circleci/ -.github/ +.devcontainer/ +.dockerignore .git/ .github/ -.dockerignore .gitignore -**Dockerfile **.Dockerfile +**Dockerfile +doc/ diff --git a/.github/workflows/update_ci_image.yaml b/.github/workflows/update_ci_image.yaml index 9358ea9629..9067e92374 100644 --- a/.github/workflows/update_ci_image.yaml +++ b/.github/workflows/update_ci_image.yaml @@ -12,6 +12,7 @@ on: - '**/package.xml' - '**/*.repos' - 'Dockerfile' + - '.github/workflows/update_ci_image.yaml' jobs: check_ci_files: @@ -25,8 +26,8 @@ jobs: id: check if: github.event_name == 'push' run: | - echo "::set-output name=trigger::true" - echo "::set-output name=no_cache::false" + echo "trigger=true" >> $GITHUB_OUTPUT + echo "no_cache=false" >> $GITHUB_OUTPUT check_ci_image: name: Check CI Image if: github.event_name == 'schedule' @@ -36,7 +37,7 @@ jobs: trigger: ${{ steps.check.outputs.trigger }} no_cache: ${{ steps.check.outputs.no_cache }} container: - image: ghcr.io/ros-planning/navigation2:main + image: ghcr.io/${{ github.repository }}:${{ github.ref_name }} steps: - name: "Check apt updates" id: check @@ -51,9 +52,9 @@ jobs: cat upgrade.log cat upgrade.log \ | grep "^0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.$" \ - && echo "::set-output name=trigger::false" \ - || echo "::set-output name=trigger::true" - echo "::set-output name=no_cache::true" + && echo "trigger=false" >> $GITHUB_OUTPUT \ + || echo "trigger=true" >> $GITHUB_OUTPUT + echo "no_cache=true" >> $GITHUB_OUTPUT rebuild_ci_image: name: Rebuild CI Image if: always() @@ -62,6 +63,7 @@ jobs: - check_ci_image runs-on: ubuntu-latest steps: + - uses: actions/checkout@v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 - name: Login to Docker Hub @@ -73,8 +75,11 @@ jobs: - name: Set build config id: config run: | - timestamp=$(date --utc +%Y%m%d%H%M%S) - echo "::set-output name=timestamp::${timestamp}" + created=$(date -u +'%Y-%m-%dT%H:%M:%SZ') + echo "created=${created}" >> $GITHUB_OUTPUT + + version=$(grep -oP '(?<=).*?(?=)' navigation2/package.xml) + echo "version=${version}" >> $GITHUB_OUTPUT no_cache=false if [ "${{needs.check_ci_files.outputs.no_cache}}" == 'true' ] || \ @@ -82,7 +87,7 @@ jobs: then no_cache=true fi - echo "::set-output name=no_cache::${no_cache}" + echo "no_cache=${no_cache}" >> $GITHUB_OUTPUT trigger=false if [ "${{needs.check_ci_files.outputs.trigger}}" == 'true' ] || \ @@ -90,21 +95,36 @@ jobs: then trigger=true fi - echo "::set-output name=trigger::${trigger}" - - name: Build and push + echo "trigger=${trigger}" >> $GITHUB_OUTPUT + - name: Build and push ${{ github.ref_name }} if: steps.config.outputs.trigger == 'true' id: docker_build uses: docker/build-push-action@v3 with: + context: . pull: true push: true + provenance: false no-cache: ${{ steps.config.outputs.no_cache }} - cache-from: type=registry,ref=ghcr.io/ros-planning/navigation2:main + cache-from: type=registry,ref=ghcr.io/${{ github.repository }}:${{ github.ref_name }} cache-to: type=inline target: builder tags: | - ghcr.io/ros-planning/navigation2:main - ghcr.io/ros-planning/navigation2:main-${{ steps.config.outputs.timestamp }} + ghcr.io/${{ github.repository }}:${{ github.ref_name }} + ghcr.io/${{ github.repository }}:${{ github.ref_name }}-${{ steps.config.outputs.version }} + labels: | + org.opencontainers.image.authors=${{ github.event.repository.owner.html_url }} + org.opencontainers.image.created=${{ steps.config.outputs.created }} + org.opencontainers.image.description=${{ github.event.repository.description }} + org.opencontainers.image.documentation=${{ github.event.repository.homepage }} + org.opencontainers.image.licenses=${{ github.event.repository.license.spdx_id }} + org.opencontainers.image.ref.name=${{ github.ref }} + org.opencontainers.image.revision=${{ github.sha }} + org.opencontainers.image.source=${{ github.event.repository.clone_url }} + org.opencontainers.image.title=${{ github.event.repository.name }} + org.opencontainers.image.url=${{ github.event.repository.html_url }} + org.opencontainers.image.vendor=${{ github.event.repository.owner.login }} + org.opencontainers.image.version=${{ steps.config.outputs.version }} - name: Image digest if: steps.config.outputs.trigger == 'true' run: echo ${{ steps.docker_build.outputs.digest }} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000000..39bdc83281 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,179 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "Web Server", + "icon": { + "id": "debug-start" + }, + "type": "process", + "command": "caddy", + "args": [ + "run", + "--watch" + ], + "options": { + "cwd": "${workspaceFolder}/.devcontainer/caddy" + }, + "isBackground": true, + "hide": true, + "problemMatcher": [] + }, + { + "label": "Stop Web Server", + "icon": { + "id": "debug-stop" + }, + "type": "shell", + "command": "echo ${input:Terminate Web Server}", + "hide": true, + "problemMatcher": [] + }, + { + "label": "System Monitor", + "icon": { + "id": "debug-start" + }, + "type": "process", + "command": "glances", + "args": [ + "-w" + ], + "isBackground": true, + "hide": true, + "problemMatcher": [] + }, + { + "label": "Stop System Monitor", + "icon": { + "id": "debug-stop" + }, + "type": "shell", + "command": "echo ${input:Terminate System Monitor}", + "hide": true, + "problemMatcher": [] + }, + { + "label": "Gzweb Bridge", + "icon": { + "id": "debug-start" + }, + "type": "process", + "command": "npm", + "args": [ + "start" + ], + "options": { + "cwd": "${env:GZWEB_WS}", + "env": { + "npm_config_port": "9090" + } + }, + "hide": true, + "isBackground": true, + "problemMatcher": [] + }, + { + "label": "Stop Gzweb Bridge", + "icon": { + "id": "debug-stop" + }, + "type": "shell", + "command": "echo ${input:Terminate Gzweb Bridge}", + "hide": true, + "problemMatcher": [] + }, + { + "label": "Foxglove Bridge", + "icon": { + "id": "debug-start" + }, + "type": "shell", + "command": "source ./install/setup.bash && ros2 run foxglove_bridge foxglove_bridge", + "options": { + "cwd": "${env:OVERLAY_WS}", + }, + "isBackground": true, + "hide": true, + "problemMatcher": [] + }, + { + "label": "Stop Foxglove Bridge", + "icon": { + "id": "debug-stop" + }, + "type": "shell", + "command": "echo ${input:Terminate Foxglove Bridge}", + "hide": true, + "problemMatcher": [] + }, + { + "label": "Start Visualizations", + "icon": { + "id": "debug-start" + }, + "dependsOn": [ + "Web Server", + "System Monitor", + "Gzweb Bridge", + "Foxglove Bridge" + ], + // "hide": true, + "problemMatcher": [] + }, + { + "label": "Stop Visualizations", + "icon": { + "id": "debug-stop" + }, + "dependsOn": [ + "Stop Web Server", + "Stop System Monitor", + "Stop Gzweb Bridge", + "Stop Foxglove Bridge" + ], + // "hide": true, + "problemMatcher": [] + }, + { + "label": "Restart Visualizations", + "icon": { + "id": "debug-restart" + }, + "dependsOn": [ + "Stop Visualizations", + "Start Visualizations" + ], + "dependsOrder": "sequence", + "problemMatcher": [] + } + ], + "inputs": [ + { + "id": "Terminate Web Server", + "type": "command", + "command": "workbench.action.tasks.terminate", + "args": "Web Server" + }, + { + "id": "Terminate System Monitor", + "type": "command", + "command": "workbench.action.tasks.terminate", + "args": "System Monitor" + }, + { + "id": "Terminate Gzweb Bridge", + "type": "command", + "command": "workbench.action.tasks.terminate", + "args": "Gzweb Bridge" + }, + { + "id": "Terminate Foxglove Bridge", + "type": "command", + "command": "workbench.action.tasks.terminate", + "args": "Foxglove Bridge" + }, + ] +} diff --git a/Dockerfile b/Dockerfile index ba26fc246f..69c8923019 100644 --- a/Dockerfile +++ b/Dockerfile @@ -125,10 +125,103 @@ RUN sed --in-place \ # test overlay build ARG RUN_TESTS -ARG FAIL_ON_TEST_FAILURE=True +ARG FAIL_ON_TEST_FAILURE RUN if [ -n "$RUN_TESTS" ]; then \ . install/setup.sh && \ colcon test && \ colcon test-result \ || ([ -z "$FAIL_ON_TEST_FAILURE" ] || exit 1) \ fi + +# multi-stage for developing +FROM builder AS dever + +# edit apt for caching +RUN mv /etc/apt/apt.conf.d/docker-clean /etc/apt/ + +# install developer dependencies +RUN apt-get update && \ + apt-get install -y \ + bash-completion \ + gdb \ + wget && \ + pip3 install \ + bottle \ + glances + +# source underlay for shell +RUN echo 'source "$UNDERLAY_WS/install/setup.bash"' >> /etc/bash.bashrc + +# multi-stage for caddy +FROM caddy:builder AS caddyer + +# build custom modules +RUN xcaddy build \ + --with github.com/caddyserver/replace-response + +# multi-stage for visualizing +FROM dever AS visualizer + +ENV ROOT_SRV /srv +RUN mkdir -p $ROOT_SRV + +# install demo dependencies +RUN apt-get update && apt-get install -y \ + ros-$ROS_DISTRO-aws-robomaker-small-warehouse-world \ + ros-$ROS_DISTRO-rviz2 \ + ros-$ROS_DISTRO-turtlebot3-simulations + +# install gzweb dependacies +RUN apt-get install -y --no-install-recommends \ + imagemagick \ + libboost-all-dev \ + libgazebo-dev \ + libgts-dev \ + libjansson-dev \ + libtinyxml-dev \ + nodejs \ + npm \ + psmisc \ + xvfb + +# clone gzweb +ENV GZWEB_WS /opt/gzweb +RUN git clone https://github.com/osrf/gzweb.git $GZWEB_WS + +# setup gzweb +RUN cd $GZWEB_WS && . /usr/share/gazebo/setup.sh && \ + GAZEBO_MODEL_PATH=$GAZEBO_MODEL_PATH:$(find /opt/ros/$ROS_DISTRO/share \ + -mindepth 1 -maxdepth 2 -type d -name "models" | paste -s -d: -) && \ + sed -i "s|var modelList =|var modelList = []; var oldModelList =|g" gz3d/src/gzgui.js && \ + xvfb-run -s "-screen 0 1280x1024x24" ./deploy.sh -m local && \ + ln -s $GZWEB_WS/http/client/assets http/client/assets/models && \ + ln -s $GZWEB_WS/http/client $ROOT_SRV/gzweb + +# patch gzsever +RUN GZSERVER=$(which gzserver) && \ + mv $GZSERVER $GZSERVER.orig && \ + echo '#!/bin/bash' > $GZSERVER && \ + echo 'exec xvfb-run -s "-screen 0 1280x1024x24" gzserver.orig "$@"' >> $GZSERVER && \ + chmod +x $GZSERVER + +# install foxglove dependacies +RUN apt-get install -y --no-install-recommends \ + ros-$ROS_DISTRO-foxglove-bridge + +# setup foxglove +# Use custom fork until PR is merged: +# https://github.com/foxglove/studio/pull/5987 +# COPY --from=ghcr.io/foxglove/studio /src $ROOT_SRV/foxglove +COPY --from=ghcr.io/ruffsl/foxglove_studio@sha256:8a2f2be0a95f24b76b0d7aa536f1c34f3e224022eed607cbf7a164928488332e /src $ROOT_SRV/foxglove + +# install web server +COPY --from=caddyer /usr/bin/caddy /usr/bin/caddy + +# download media files +RUN mkdir -p $ROOT_SRV/media && cd /tmp && \ + export ICONS="icons.tar.gz" && wget https://github.com/ros-planning/navigation2/files/11506823/$ICONS && \ + echo "cae5e2a5230f87b004c8232b579781edb4a72a7431405381403c6f9e9f5f7d41 $ICONS" | sha256sum -c && \ + tar xvz -C $ROOT_SRV/media -f $ICONS && rm $ICONS + +# multi-stage for exporting +FROM tester AS exporter diff --git a/README.md b/README.md index 0acfb8fe5c..488eb28042 100644 --- a/README.md +++ b/README.md @@ -16,10 +16,27 @@ For detailed instructions on how to: - [Navigation Plugins](https://navigation.ros.org/plugins/index.html) - [Migration Guides](https://navigation.ros.org/migration/index.html) - [Container Images for Building Nav2](https://github.com/orgs/ros-planning/packages/container/package/navigation2) -- [Contribute](https://navigation.ros.org/contribute/index.html) +- [Contribute](https://navigation.ros.org/development_guides/involvement_docs/index.html) Please visit our [documentation site](https://navigation.ros.org/). [Please visit our community Slack here](https://join.slack.com/t/navigation2/shared_invite/zt-hu52lnnq-cKYjuhTY~sEMbZXL8p9tOw) (if this link does not work, please contact maintainers to reactivate). +## Our Sponsors + +Please thank our amazing sponsors for their generous support of Nav2 on behalf of the community to allow the project to continue to be professionally maintained, developed, and supported for the long-haul! [Open Navigation LLC](https://www.opennav.org/) provides project leadership, maintenance, development, and support services to the Nav2 & ROS community. + +

+ +

+ +### [Dexory](https://www.dexory.com/) develops robotics and AI logistics solutions to drive better business decisions using a digital twin of warehouses to provide inventory insights. + +### [Polymath Robotics](https://www.polymathrobotics.com/) creates safety-critical navigation systems for industrial vehicles that are radically simple to enable and deploy. + +### [Stereolabs](https://www.stereolabs.com/) produces the high-quality ZED stereo cameras with a complete vision pipeline from neural depth to SLAM, 3D object tracking, AI and more. + +### Confidential is just happy to support Nav2's mission! + + ## Citation If you use the navigation framework, an algorithm from this repository, or ideas from it @@ -54,39 +71,39 @@ please cite this work in your papers! ## Build Status -| Service | Foxy | Galactic | Humble | Main | +| Service | Foxy | Humble | Main | +| :---: | :---: | :---: | :---: | +| ROS Build Farm | [![Build Status](http://build.ros2.org/job/Fdev__navigation2__ubuntu_focal_amd64/badge/icon)](http://build.ros2.org/job/Fdev__navigation2__ubuntu_focal_amd64/) | [![Build Status](https://build.ros2.org/job/Hdev__navigation2__ubuntu_jammy_amd64/badge/icon)](https://build.ros2.org/job/Hdev__navigation2__ubuntu_jammy_amd64/) | N/A | +| Circle CI | N/A | N/A | [![Build Status](https://circleci.com/gh/ros-planning/navigation2/tree/main.svg?style=svg)](https://circleci.com/gh/ros-planning/navigation2/tree/main) | + + +| Package | Foxy Source | Foxy Debian | Humble Source | Humble Debian | | :---: | :---: | :---: | :---: | :---: | -| ROS Build Farm | [![Build Status](http://build.ros2.org/job/Fdev__navigation2__ubuntu_focal_amd64/badge/icon)](http://build.ros2.org/job/Fdev__navigation2__ubuntu_focal_amd64/) | [![Build Status](http://build.ros2.org/job/Gdev__navigation2__ubuntu_focal_amd64/badge/icon)](http://build.ros2.org/job/Gdev__navigation2__ubuntu_focal_amd64/) | [![Build Status](https://build.ros2.org/job/Hdev__navigation2__ubuntu_jammy_amd64/badge/icon)](https://build.ros2.org/job/Hdev__navigation2__ubuntu_jammy_amd64/) | N/A | -| Circle CI | N/A | N/A | N/A | [![Build Status](https://circleci.com/gh/ros-planning/navigation2/tree/main.svg?style=svg)](https://circleci.com/gh/ros-planning/navigation2/tree/main) | - - -| Package | Foxy Source | Foxy Debian | Galactic Source | Galactic Debian | Humble Source | Humble Debian | -| :---: | :---: | :---: | :---: | :---: | :---: | :---: | -| Navigation2 | [![Build Status](http://build.ros2.org/job/Fsrc_uF__navigation2__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Fsrc_uF__navigation2__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Fbin_uF64__navigation2__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Fbin_uF64__navigation2__ubuntu_focal_amd64__binary/) | [![Build Status](http://build.ros2.org/job/Gsrc_uF__navigation2__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Gsrc_uF__navigation2__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Gbin_uF64__navigation2__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Gbin_uF64__navigation2__ubuntu_focal_amd64__binary/) | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__navigation2__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__navigation2__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__navigation2__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__navigation2__ubuntu_jammy_amd64__binary/) | -| nav2_amcl | [![Build Status](http://build.ros2.org/job/Fsrc_uF__nav2_amcl__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Fsrc_uF__nav2_amcl__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Fbin_uF64__nav2_amcl__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Fbin_uF64__nav2_amcl__ubuntu_focal_amd64__binary/) | [![Build Status](http://build.ros2.org/job/Gsrc_uF__nav2_amcl__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Gsrc_uF__nav2_amcl__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Gbin_uF64__nav2_amcl__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Gbin_uF64__nav2_amcl__ubuntu_focal_amd64__binary/) | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_amcl__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_amcl__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_amcl__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_amcl__ubuntu_jammy_amd64__binary/) | -| nav2_behavior_tree | [![Build Status](http://build.ros2.org/job/Fsrc_uF__nav2_behavior_tree__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Fsrc_uF__nav2_behavior_tree__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Fbin_uF64__nav2_behavior_tree__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Fbin_uF64__nav2_behavior_tree__ubuntu_focal_amd64__binary/) | [![Build Status](http://build.ros2.org/job/Gsrc_uF__nav2_behavior_tree__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Gsrc_uF__nav2_behavior_tree__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Gbin_uF64__nav2_behavior_tree__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Gbin_uF64__nav2_behavior_tree__ubuntu_focal_amd64__binary/) | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_behavior_tree__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_behavior_tree__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_behavior_tree__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_behavior_tree__ubuntu_jammy_amd64__binary/) | -| nav2_{recoveries, behaviors} | [![Build Status](http://build.ros2.org/job/Fsrc_uF__nav2_recoveries__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Fsrc_uF__nav2_recoveries__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Fbin_uF64__nav2_recoveries__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Fbin_uF64__nav2_recoveries__ubuntu_focal_amd64__binary/) | [![Build Status](http://build.ros2.org/job/Gsrc_uF__nav2_recoveries__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Gsrc_uF__nav2_recoveries__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Gbin_uF64__nav2_recoveries__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Gbin_uF64__nav2_recoveries__ubuntu_focal_amd64__binary/) | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_behaviors__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_behaviors__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_behaviors__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_behaviors__ubuntu_jammy_amd64__binary/) | -| nav2_bringup | [![Build Status](http://build.ros2.org/job/Fsrc_uF__nav2_bringup__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Fsrc_uF__nav2_bringup__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Fbin_uF64__nav2_bringup__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Fbin_uF64__nav2_bringup__ubuntu_focal_amd64__binary/) | [![Build Status](http://build.ros2.org/job/Gsrc_uF__nav2_bringup__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Gsrc_uF__nav2_bringup__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Gbin_uF64__nav2_bringup__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Gbin_uF64__nav2_bringup__ubuntu_focal_amd64__binary/) | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_bringup__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_bringup__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_bringup__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_bringup__ubuntu_jammy_amd64__binary/) | -| nav2_bt_navigator | [![Build Status](http://build.ros2.org/job/Fsrc_uF__nav2_bt_navigator__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Fsrc_uF__nav2_bt_navigator__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Fbin_uF64__nav2_bt_navigator__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Fbin_uF64__nav2_bt_navigator__ubuntu_focal_amd64__binary/) | [![Build Status](http://build.ros2.org/job/Gsrc_uF__nav2_bt_navigator__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Gsrc_uF__nav2_bt_navigator__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Gbin_uF64__nav2_bt_navigator__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Gbin_uF64__nav2_bt_navigator__ubuntu_focal_amd64__binary/) | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_bt_navigator__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_bt_navigator__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_bt_navigator__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_bt_navigator__ubuntu_jammy_amd64__binary/) | -| nav2_common | [![Build Status](http://build.ros2.org/job/Fsrc_uF__nav2_common__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Fsrc_uF__nav2_common__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Fbin_uF64__nav2_common__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Fbin_uF64__nav2_common__ubuntu_focal_amd64__binary/) | [![Build Status](http://build.ros2.org/job/Gsrc_uF__nav2_common__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Gsrc_uF__nav2_common__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Gbin_uF64__nav2_common__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Gbin_uF64__nav2_common__ubuntu_focal_amd64__binary/) | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_common__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_common__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_common__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_common__ubuntu_jammy_amd64__binary/) | +| Navigation2 | [![Build Status](http://build.ros2.org/job/Fsrc_uF__navigation2__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Fsrc_uF__navigation2__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Fbin_uF64__navigation2__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Fbin_uF64__navigation2__ubuntu_focal_amd64__binary/) | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__navigation2__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__navigation2__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__navigation2__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__navigation2__ubuntu_jammy_amd64__binary/) | +| nav2_amcl | [![Build Status](http://build.ros2.org/job/Fsrc_uF__nav2_amcl__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Fsrc_uF__nav2_amcl__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Fbin_uF64__nav2_amcl__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Fbin_uF64__nav2_amcl__ubuntu_focal_amd64__binary/) | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_amcl__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_amcl__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_amcl__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_amcl__ubuntu_jammy_amd64__binary/) | +| nav2_behavior_tree | [![Build Status](http://build.ros2.org/job/Fsrc_uF__nav2_behavior_tree__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Fsrc_uF__nav2_behavior_tree__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Fbin_uF64__nav2_behavior_tree__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Fbin_uF64__nav2_behavior_tree__ubuntu_focal_amd64__binary/) | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_behavior_tree__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_behavior_tree__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_behavior_tree__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_behavior_tree__ubuntu_jammy_amd64__binary/) | +| nav2_{recoveries, behaviors} | [![Build Status](http://build.ros2.org/job/Fsrc_uF__nav2_recoveries__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Fsrc_uF__nav2_recoveries__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Fbin_uF64__nav2_recoveries__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Fbin_uF64__nav2_recoveries__ubuntu_focal_amd64__binary/) | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_behaviors__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_behaviors__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_behaviors__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_behaviors__ubuntu_jammy_amd64__binary/) | +| nav2_bringup | [![Build Status](http://build.ros2.org/job/Fsrc_uF__nav2_bringup__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Fsrc_uF__nav2_bringup__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Fbin_uF64__nav2_bringup__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Fbin_uF64__nav2_bringup__ubuntu_focal_amd64__binary/) | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_bringup__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_bringup__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_bringup__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_bringup__ubuntu_jammy_amd64__binary/) | +| nav2_bt_navigator | [![Build Status](http://build.ros2.org/job/Fsrc_uF__nav2_bt_navigator__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Fsrc_uF__nav2_bt_navigator__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Fbin_uF64__nav2_bt_navigator__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Fbin_uF64__nav2_bt_navigator__ubuntu_focal_amd64__binary/) | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_bt_navigator__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_bt_navigator__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_bt_navigator__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_bt_navigator__ubuntu_jammy_amd64__binary/) | +| nav2_common | [![Build Status](http://build.ros2.org/job/Fsrc_uF__nav2_common__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Fsrc_uF__nav2_common__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Fbin_uF64__nav2_common__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Fbin_uF64__nav2_common__ubuntu_focal_amd64__binary/) | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_common__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_common__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_common__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_common__ubuntu_jammy_amd64__binary/) | | nav2_constrained_smoother | N/A | N/A | N/A | N/A | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_constrained_smoother__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_constrained_smoother__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_constrained_smoother__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_constrained_smoother__ubuntu_jammy_amd64__binary/) | -| nav2_controller | [![Build Status](http://build.ros2.org/job/Fsrc_uF__nav2_controller__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Fsrc_uF__nav2_controller__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Fbin_uF64__nav2_controller__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Fbin_uF64__nav2_controller__ubuntu_focal_amd64__binary/) | [![Build Status](http://build.ros2.org/job/Gsrc_uF__nav2_controller__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Gsrc_uF__nav2_controller__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Gbin_uF64__nav2_controller__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Gbin_uF64__nav2_controller__ubuntu_focal_amd64__binary/) | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_controller__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_controller__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_controller__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_controller__ubuntu_jammy_amd64__binary/) | -| nav2_core | [![Build Status](http://build.ros2.org/job/Fsrc_uF__nav2_core__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Fsrc_uF__nav2_core__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Fbin_uF64__nav2_core__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Fbin_uF64__nav2_core__ubuntu_focal_amd64__binary/) | [![Build Status](http://build.ros2.org/job/Gsrc_uF__nav2_core__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Gsrc_uF__nav2_core__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Gbin_uF64__nav2_core__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Gbin_uF64__nav2_core__ubuntu_focal_amd64__binary/) | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_core__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_core__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_core__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_core__ubuntu_jammy_amd64__binary/) | -| nav2_costmap_2d | [![Build Status](http://build.ros2.org/job/Fsrc_uF__nav2_costmap_2d__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Fsrc_uF__nav2_costmap_2d__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Fbin_uF64__nav2_costmap_2d__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Fbin_uF64__nav2_costmap_2d__ubuntu_focal_amd64__binary/) | [![Build Status](http://build.ros2.org/job/Gsrc_uF__nav2_costmap_2d__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Gsrc_uF__nav2_costmap_2d__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Gbin_uF64__nav2_costmap_2d__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Gbin_uF64__nav2_costmap_2d__ubuntu_focal_amd64__binary/) | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_costmap_2d__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_costmap_2d__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_costmap_2d__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_costmap_2d__ubuntu_jammy_amd64__binary/) | -| nav2_dwb_controller | [![Build Status](http://build.ros2.org/job/Fsrc_uF__nav2_dwb_controller__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Fsrc_uF__nav2_dwb_controller__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Fbin_uF64__nav2_dwb_controller__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Fbin_uF64__nav2_dwb_controller__ubuntu_focal_amd64__binary/) | [![Build Status](http://build.ros2.org/job/Gsrc_uF__nav2_dwb_controller__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Gsrc_uF__nav2_dwb_controller__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Gbin_uF64__nav2_dwb_controller__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Gbin_uF64__nav2_dwb_controller__ubuntu_focal_amd64__binary/) | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_dwb_controller__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_dwb_controller__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_dwb_controller__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_dwb_controller__ubuntu_jammy_amd64__binary/) | -| nav2_lifecycle_manager | [![Build Status](http://build.ros2.org/job/Fsrc_uF__nav2_lifecycle_manager__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Fsrc_uF__nav2_lifecycle_manager__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Fbin_uF64__nav2_lifecycle_manager__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Fbin_uF64__nav2_lifecycle_manager__ubuntu_focal_amd64__binary/) | [![Build Status](http://build.ros2.org/job/Gsrc_uF__nav2_lifecycle_manager__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Gsrc_uF__nav2_lifecycle_manager__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Gbin_uF64__nav2_lifecycle_manager__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Gbin_uF64__nav2_lifecycle_manager__ubuntu_focal_amd64__binary/) | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_lifecycle_manager__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_lifecycle_manager__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_lifecycle_manager__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_lifecycle_manager__ubuntu_jammy_amd64__binary/) | -| nav2_map_server | [![Build Status](http://build.ros2.org/job/Fsrc_uF__nav2_map_server__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Fsrc_uF__nav2_map_server__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Fbin_uF64__nav2_map_server__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Fbin_uF64__nav2_map_server__ubuntu_focal_amd64__binary/) | [![Build Status](http://build.ros2.org/job/Gsrc_uF__nav2_map_server__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Gsrc_uF__nav2_map_server__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Gbin_uF64__nav2_map_server__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Gbin_uF64__nav2_map_server__ubuntu_focal_amd64__binary/) | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_map_server__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_map_server__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_map_server__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_map_server__ubuntu_jammy_amd64__binary/) | -| nav2_msgs | [![Build Status](http://build.ros2.org/job/Fsrc_uF__nav2_msgs__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Fsrc_uF__nav2_msgs__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Fbin_uF64__nav2_msgs__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Fbin_uF64__nav2_msgs__ubuntu_focal_amd64__binary/) | [![Build Status](http://build.ros2.org/job/Gsrc_uF__nav2_msgs__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Gsrc_uF__nav2_msgs__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Gbin_uF64__nav2_msgs__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Gbin_uF64__nav2_msgs__ubuntu_focal_amd64__binary/) | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_msgs__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_msgs__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_msgs__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_msgs__ubuntu_jammy_amd64__binary/) | -| nav2_navfn_planner | [![Build Status](http://build.ros2.org/job/Fsrc_uF__nav2_navfn_planner__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Fsrc_uF__nav2_navfn_planner__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Fbin_uF64__nav2_navfn_planner__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Fbin_uF64__nav2_navfn_planner__ubuntu_focal_amd64__binary/) | [![Build Status](http://build.ros2.org/job/Gsrc_uF__nav2_navfn_planner__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Gsrc_uF__nav2_navfn_planner__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Gbin_uF64__nav2_navfn_planner__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Gbin_uF64__nav2_navfn_planner__ubuntu_focal_amd64__binary/) | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_navfn_planner__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_navfn_planner__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_navfn_planner__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_navfn_planner__ubuntu_jammy_amd64__binary/) | -| nav2_planner | [![Build Status](http://build.ros2.org/job/Fsrc_uF__nav2_planner__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Fsrc_uF__nav2_planner__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Fbin_uF64__nav2_planner__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Fbin_uF64__nav2_planner__ubuntu_focal_amd64__binary/) | [![Build Status](http://build.ros2.org/job/Gsrc_uF__nav2_planner__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Gsrc_uF__nav2_planner__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Gbin_uF64__nav2_planner__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Gbin_uF64__nav2_planner__ubuntu_focal_amd64__binary/) | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_planner__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_planner__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_planner__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_planner__ubuntu_jammy_amd64__binary/) | -| nav2_regulated_pure_pursuit | [![Build Status](http://build.ros2.org/job/Fsrc_uF__nav2_regulated_pure_pursuit_controller__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Fsrc_uF__nav2_regulated_pure_pursuit_controller__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Fbin_uF64__nav2_regulated_pure_pursuit_controller__ubuntu_focal_amd64__binary/badge/icon)](https://build.ros2.org/job/Fbin_uF64__nav2_regulated_pure_pursuit_controller__ubuntu_focal_amd64__binary/) | [![Build Status](http://build.ros2.org/job/Gsrc_uF__nav2_regulated_pure_pursuit_controller__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Gsrc_uF__nav2_regulated_pure_pursuit_controller__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Gbin_uF64__nav2_regulated_pure_pursuit_controller__ubuntu_focal_amd64__binary/badge/icon)](https://build.ros2.org/job/Gbin_uF64__nav2_regulated_pure_pursuit_controller__ubuntu_focal_amd64__binary/) | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_regulated_pure_pursuit_controller__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_regulated_pure_pursuit_controller__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_regulated_pure_pursuit_controller__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_regulated_pure_pursuit_controller__ubuntu_jammy_amd64__binary/) | +| nav2_controller | [![Build Status](http://build.ros2.org/job/Fsrc_uF__nav2_controller__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Fsrc_uF__nav2_controller__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Fbin_uF64__nav2_controller__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Fbin_uF64__nav2_controller__ubuntu_focal_amd64__binary/) | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_controller__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_controller__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_controller__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_controller__ubuntu_jammy_amd64__binary/) | +| nav2_core | [![Build Status](http://build.ros2.org/job/Fsrc_uF__nav2_core__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Fsrc_uF__nav2_core__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Fbin_uF64__nav2_core__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Fbin_uF64__nav2_core__ubuntu_focal_amd64__binary/) | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_core__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_core__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_core__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_core__ubuntu_jammy_amd64__binary/) | +| nav2_costmap_2d | [![Build Status](http://build.ros2.org/job/Fsrc_uF__nav2_costmap_2d__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Fsrc_uF__nav2_costmap_2d__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Fbin_uF64__nav2_costmap_2d__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Fbin_uF64__nav2_costmap_2d__ubuntu_focal_amd64__binary/) | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_costmap_2d__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_costmap_2d__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_costmap_2d__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_costmap_2d__ubuntu_jammy_amd64__binary/) | +| nav2_dwb_controller | [![Build Status](http://build.ros2.org/job/Fsrc_uF__nav2_dwb_controller__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Fsrc_uF__nav2_dwb_controller__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Fbin_uF64__nav2_dwb_controller__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Fbin_uF64__nav2_dwb_controller__ubuntu_focal_amd64__binary/) | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_dwb_controller__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_dwb_controller__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_dwb_controller__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_dwb_controller__ubuntu_jammy_amd64__binary/) | +| nav2_lifecycle_manager | [![Build Status](http://build.ros2.org/job/Fsrc_uF__nav2_lifecycle_manager__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Fsrc_uF__nav2_lifecycle_manager__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Fbin_uF64__nav2_lifecycle_manager__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Fbin_uF64__nav2_lifecycle_manager__ubuntu_focal_amd64__binary/) | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_lifecycle_manager__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_lifecycle_manager__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_lifecycle_manager__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_lifecycle_manager__ubuntu_jammy_amd64__binary/) | +| nav2_map_server | [![Build Status](http://build.ros2.org/job/Fsrc_uF__nav2_map_server__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Fsrc_uF__nav2_map_server__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Fbin_uF64__nav2_map_server__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Fbin_uF64__nav2_map_server__ubuntu_focal_amd64__binary/) | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_map_server__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_map_server__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_map_server__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_map_server__ubuntu_jammy_amd64__binary/) | +| nav2_msgs | [![Build Status](http://build.ros2.org/job/Fsrc_uF__nav2_msgs__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Fsrc_uF__nav2_msgs__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Fbin_uF64__nav2_msgs__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Fbin_uF64__nav2_msgs__ubuntu_focal_amd64__binary/) | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_msgs__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_msgs__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_msgs__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_msgs__ubuntu_jammy_amd64__binary/) | +| nav2_navfn_planner | [![Build Status](http://build.ros2.org/job/Fsrc_uF__nav2_navfn_planner__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Fsrc_uF__nav2_navfn_planner__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Fbin_uF64__nav2_navfn_planner__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Fbin_uF64__nav2_navfn_planner__ubuntu_focal_amd64__binary/) | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_navfn_planner__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_navfn_planner__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_navfn_planner__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_navfn_planner__ubuntu_jammy_amd64__binary/) | +| nav2_planner | [![Build Status](http://build.ros2.org/job/Fsrc_uF__nav2_planner__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Fsrc_uF__nav2_planner__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Fbin_uF64__nav2_planner__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Fbin_uF64__nav2_planner__ubuntu_focal_amd64__binary/) | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_planner__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_planner__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_planner__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_planner__ubuntu_jammy_amd64__binary/) | +| nav2_regulated_pure_pursuit | [![Build Status](http://build.ros2.org/job/Fsrc_uF__nav2_regulated_pure_pursuit_controller__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Fsrc_uF__nav2_regulated_pure_pursuit_controller__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Fbin_uF64__nav2_regulated_pure_pursuit_controller__ubuntu_focal_amd64__binary/badge/icon)](https://build.ros2.org/job/Fbin_uF64__nav2_regulated_pure_pursuit_controller__ubuntu_focal_amd64__binary/) | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_regulated_pure_pursuit_controller__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_regulated_pure_pursuit_controller__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_regulated_pure_pursuit_controller__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_regulated_pure_pursuit_controller__ubuntu_jammy_amd64__binary/) | | nav2_rotation_shim_controller | N/A | N/A | N/A | N/A | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_rotation_shim_controller__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_rotation_shim_controller__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_rotation_shim_controller__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_rotation_shim_controller__ubuntu_jammy_amd64__binary/) | -| nav2_rviz_plugins | [![Build Status](http://build.ros2.org/job/Fsrc_uF__nav2_rviz_plugins__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Fsrc_uF__nav2_rviz_plugins__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Fbin_uF64__nav2_rviz_plugins__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Fbin_uF64__nav2_rviz_plugins__ubuntu_focal_amd64__binary/) | [![Build Status](http://build.ros2.org/job/Gsrc_uF__nav2_rviz_plugins__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Gsrc_uF__nav2_rviz_plugins__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Gbin_uF64__nav2_rviz_plugins__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Gbin_uF64__nav2_rviz_plugins__ubuntu_focal_amd64__binary/) | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_rviz_plugins__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_rviz_plugins__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_rviz_plugins__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_rviz_plugins__ubuntu_jammy_amd64__binary/) | -| nav2_simple_commander | N/A | N/A | [![Build Status](https://build.ros2.org/job/Gsrc_uF__nav2_simple_commander__ubuntu_focal__source/badge/icon)](https://build.ros2.org/job/Gsrc_uF__nav2_simple_commander__ubuntu_focal__source/) | [![Build Status](https://build.ros2.org/job/Gbin_uF64__nav2_simple_commander__ubuntu_focal_amd64__binary/badge/icon)](https://build.ros2.org/job/Gbin_uF64__nav2_simple_commander__ubuntu_focal_amd64__binary/) | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_simple_commander__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_simple_commander__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_simple_commander__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_simple_commander__ubuntu_jammy_amd64__binary/) | -| nav2_smac_planner | [![Build Status](http://build.ros2.org/job/Fsrc_uF__smac_planner__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Fsrc_uF__smac_planner__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Fbin_uF64__smac_planner__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Fbin_uF64__smac_planner__ubuntu_focal_amd64__binary/) | [![Build Status](http://build.ros2.org/job/Gsrc_uF__nav2_smac_planner__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Gsrc_uF__nav2_smac_planner__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Gbin_uF64__nav2_smac_planner__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Gbin_uF64__nav2_smac_planner__ubuntu_focal_amd64__binary/) | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_smac_planner__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_smac_planner__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_smac_planner__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_smac_planner__ubuntu_jammy_amd64__binary/) | +| nav2_rviz_plugins | [![Build Status](http://build.ros2.org/job/Fsrc_uF__nav2_rviz_plugins__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Fsrc_uF__nav2_rviz_plugins__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Fbin_uF64__nav2_rviz_plugins__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Fbin_uF64__nav2_rviz_plugins__ubuntu_focal_amd64__binary/) | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_rviz_plugins__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_rviz_plugins__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_rviz_plugins__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_rviz_plugins__ubuntu_jammy_amd64__binary/) | +| nav2_simple_commander | N/A | N/A | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_simple_commander__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_simple_commander__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_simple_commander__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_simple_commander__ubuntu_jammy_amd64__binary/) | +| nav2_smac_planner | [![Build Status](http://build.ros2.org/job/Fsrc_uF__smac_planner__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Fsrc_uF__smac_planner__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Fbin_uF64__smac_planner__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Fbin_uF64__smac_planner__ubuntu_focal_amd64__binary/) | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_smac_planner__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_smac_planner__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_smac_planner__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_smac_planner__ubuntu_jammy_amd64__binary/) | | nav2_smoother | N/A | N/A | N/A | N/A | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_smoother__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_smoother__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_smoother__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_smoother__ubuntu_jammy_amd64__binary/) | -| nav2_system_tests | [![Build Status](http://build.ros2.org/job/Fsrc_uF__nav2_system_tests__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Fsrc_uF__nav2_system_tests__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Fbin_uF64__nav2_system_tests__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Fbin_uF64__nav2_system_tests__ubuntu_focal_amd64__binary/) | [![Build Status](http://build.ros2.org/job/Gsrc_uF__nav2_system_tests__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Gsrc_uF__nav2_system_tests__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Gbin_uF64__nav2_system_tests__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Gbin_uF64__nav2_system_tests__ubuntu_focal_amd64__binary/) | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_system_tests__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_system_tests__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_system_tests__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_system_tests__ubuntu_jammy_amd64__binary/) | -| nav2_theta_star_planner | N/A | N/A | [![Build Status](https://build.ros2.org/job/Gsrc_uF__nav2_theta_star_planner__ubuntu_focal__source/badge/icon)](https://build.ros2.org/job/Gsrc_uF__nav2_theta_star_planner__ubuntu_focal__source/) | [![Build Status](https://build.ros2.org/job/Gbin_uF64__nav2_theta_star_planner__ubuntu_focal_amd64__binary/badge/icon)](https://build.ros2.org/job/Gbin_uF64__nav2_theta_star_planner__ubuntu_focal_amd64__binary/) | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_theta_star_planner__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_theta_star_planner__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_theta_star_planner__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_theta_star_planner__ubuntu_jammy_amd64__binary/) | -| nav2_util | [![Build Status](http://build.ros2.org/job/Fsrc_uF__nav2_util__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Fsrc_uF__nav2_util__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Fbin_uF64__nav2_util__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Fbin_uF64__nav2_util__ubuntu_focal_amd64__binary/) | [![Build Status](http://build.ros2.org/job/Gsrc_uF__nav2_util__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Gsrc_uF__nav2_util__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Gbin_uF64__nav2_util__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Gbin_uF64__nav2_util__ubuntu_focal_amd64__binary/) | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_util__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_util__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_util__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_util__ubuntu_jammy_amd64__binary/) | -| nav2_voxel_grid | [![Build Status](https://build.ros2.org/job/Fsrc_uF__nav2_voxel_grid__ubuntu_focal__source/badge/icon)](https://build.ros2.org/job/Fsrc_uF__nav2_voxel_grid__ubuntu_focal__source/) | [![Build Status](https://build.ros2.org/job/Fbin_uF64__nav2_voxel_grid__ubuntu_focal_amd64__binary/badge/icon)](https://build.ros2.org/job/Fbin_uF64__nav2_voxel_grid__ubuntu_focal_amd64__binary/) | [![Build Status](https://build.ros2.org/job/Gsrc_uF__nav2_voxel_grid__ubuntu_focal__source/badge/icon)](https://build.ros2.org/job/Gsrc_uF__nav2_voxel_grid__ubuntu_focal__source/) | [![Build Status](https://build.ros2.org/job/Gbin_uF64__nav2_voxel_grid__ubuntu_focal_amd64__binary/badge/icon)](https://build.ros2.org/job/Gbin_uF64__nav2_voxel_grid__ubuntu_focal_amd64__binary/) | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_voxel_grid__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_voxel_grid__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_voxel_grid__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_voxel_grid__ubuntu_jammy_amd64__binary/) | -| nav2_waypoint_follower | [![Build Status](http://build.ros2.org/job/Fsrc_uF__nav2_waypoint_follower__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Fsrc_uF__nav2_waypoint_follower__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Fbin_uF64__nav2_waypoint_follower__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Fbin_uF64__nav2_waypoint_follower__ubuntu_focal_amd64__binary/) | [![Build Status](http://build.ros2.org/job/Gsrc_uF__nav2_waypoint_follower__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Gsrc_uF__nav2_waypoint_follower__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Gbin_uF64__nav2_waypoint_follower__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Gbin_uF64__nav2_waypoint_follower__ubuntu_focal_amd64__binary/) | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_waypoint_follower__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_waypoint_follower__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_waypoint_follower__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_waypoint_follower__ubuntu_jammy_amd64__binary/) | +| nav2_system_tests | [![Build Status](http://build.ros2.org/job/Fsrc_uF__nav2_system_tests__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Fsrc_uF__nav2_system_tests__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Fbin_uF64__nav2_system_tests__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Fbin_uF64__nav2_system_tests__ubuntu_focal_amd64__binary/) | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_system_tests__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_system_tests__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_system_tests__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_system_tests__ubuntu_jammy_amd64__binary/) | +| nav2_theta_star_planner | N/A | N/A | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_theta_star_planner__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_theta_star_planner__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_theta_star_planner__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_theta_star_planner__ubuntu_jammy_amd64__binary/) | +| nav2_util | [![Build Status](http://build.ros2.org/job/Fsrc_uF__nav2_util__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Fsrc_uF__nav2_util__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Fbin_uF64__nav2_util__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Fbin_uF64__nav2_util__ubuntu_focal_amd64__binary/) | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_util__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_util__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_util__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_util__ubuntu_jammy_amd64__binary/) | +| nav2_voxel_grid | [![Build Status](https://build.ros2.org/job/Fsrc_uF__nav2_voxel_grid__ubuntu_focal__source/badge/icon)](https://build.ros2.org/job/Fsrc_uF__nav2_voxel_grid__ubuntu_focal__source/) | [![Build Status](https://build.ros2.org/job/Fbin_uF64__nav2_voxel_grid__ubuntu_focal_amd64__binary/badge/icon)](https://build.ros2.org/job/Fbin_uF64__nav2_voxel_grid__ubuntu_focal_amd64__binary/) | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_voxel_grid__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_voxel_grid__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_voxel_grid__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_voxel_grid__ubuntu_jammy_amd64__binary/) | +| nav2_waypoint_follower | [![Build Status](http://build.ros2.org/job/Fsrc_uF__nav2_waypoint_follower__ubuntu_focal__source/badge/icon)](http://build.ros2.org/job/Fsrc_uF__nav2_waypoint_follower__ubuntu_focal__source/) | [![Build Status](http://build.ros2.org/job/Fbin_uF64__nav2_waypoint_follower__ubuntu_focal_amd64__binary/badge/icon)](http://build.ros2.org/job/Fbin_uF64__nav2_waypoint_follower__ubuntu_focal_amd64__binary/) | [![Build Status](https://build.ros2.org/job/Hsrc_uJ__nav2_waypoint_follower__ubuntu_jammy__source/badge/icon)](https://build.ros2.org/job/Hsrc_uJ__nav2_waypoint_follower__ubuntu_jammy__source/) | [![Build Status](https://build.ros2.org/job/Hbin_uJ64__nav2_waypoint_follower__ubuntu_jammy_amd64__binary/badge/icon)](https://build.ros2.org/job/Hbin_uJ64__nav2_waypoint_follower__ubuntu_jammy_amd64__binary/) | diff --git a/doc/development/codespaces.md b/doc/development/codespaces.md new file mode 100644 index 0000000000..841db7532d --- /dev/null +++ b/doc/development/codespaces.md @@ -0,0 +1,23 @@ +# Codespaces + +TODO: welcome and introduction + +# Overview + +TODO: document devcontainer +TODO: reference extensions +TODO: use of dockercompose and services + +# Terminal + +TODO: link to vscode terminal + +# Graphics and Simulations + +TODO: vnc options +TODO: foxglove example +TODO: gazebo example with gzweb + +# References + +TODO: links to more info \ No newline at end of file diff --git a/doc/sponsors_may_2023.png b/doc/sponsors_may_2023.png new file mode 100644 index 0000000000..ce928b75e8 Binary files /dev/null and b/doc/sponsors_may_2023.png differ diff --git a/nav2_amcl/package.xml b/nav2_amcl/package.xml index 56a3a52453..2d2491da8b 100644 --- a/nav2_amcl/package.xml +++ b/nav2_amcl/package.xml @@ -2,7 +2,7 @@ nav2_amcl - 1.1.6 + 1.1.7

amcl is a probabilistic localization system for a robot moving in diff --git a/nav2_amcl/src/amcl_node.cpp b/nav2_amcl/src/amcl_node.cpp index 2e9730ae56..4afae2a122 100644 --- a/nav2_amcl/src/amcl_node.cpp +++ b/nav2_amcl/src/amcl_node.cpp @@ -146,11 +146,11 @@ AmclNode::AmclNode(const rclcpp::NodeOptions & options) add_parameter( "max_particles", rclcpp::ParameterValue(2000), - "Minimum allowed number of particles"); + "Maximum allowed number of particles"); add_parameter( "min_particles", rclcpp::ParameterValue(500), - "Maximum allowed number of particles"); + "Minimum allowed number of particles"); add_parameter( "odom_frame_id", rclcpp::ParameterValue(std::string("odom")), diff --git a/nav2_behavior_tree/CMakeLists.txt b/nav2_behavior_tree/CMakeLists.txt index 1767bdcf32..6522072368 100644 --- a/nav2_behavior_tree/CMakeLists.txt +++ b/nav2_behavior_tree/CMakeLists.txt @@ -129,6 +129,9 @@ list(APPEND plugin_libs nav2_distance_traveled_condition_bt_node) add_library(nav2_initial_pose_received_condition_bt_node SHARED plugins/condition/initial_pose_received_condition.cpp) list(APPEND plugin_libs nav2_initial_pose_received_condition_bt_node) +add_library(nav2_is_battery_charging_condition_bt_node SHARED plugins/condition/is_battery_charging_condition.cpp) +list(APPEND plugin_libs nav2_is_battery_charging_condition_bt_node) + add_library(nav2_is_battery_low_condition_bt_node SHARED plugins/condition/is_battery_low_condition.cpp) list(APPEND plugin_libs nav2_is_battery_low_condition_bt_node) diff --git a/nav2_behavior_tree/include/nav2_behavior_tree/bt_action_node.hpp b/nav2_behavior_tree/include/nav2_behavior_tree/bt_action_node.hpp index 90c7adda74..6141aa24ef 100644 --- a/nav2_behavior_tree/include/nav2_behavior_tree/bt_action_node.hpp +++ b/nav2_behavior_tree/include/nav2_behavior_tree/bt_action_node.hpp @@ -193,6 +193,9 @@ class BtActionNode : public BT::ActionNodeBase // user defined callback on_tick(); + if (!should_send_goal_) { + return BT::NodeStatus::FAILURE; + } send_new_goal(); } diff --git a/nav2_behavior_tree/include/nav2_behavior_tree/bt_service_node.hpp b/nav2_behavior_tree/include/nav2_behavior_tree/bt_service_node.hpp index a5fe6e0021..e050ab3038 100644 --- a/nav2_behavior_tree/include/nav2_behavior_tree/bt_service_node.hpp +++ b/nav2_behavior_tree/include/nav2_behavior_tree/bt_service_node.hpp @@ -39,13 +39,16 @@ class BtServiceNode : public BT::ActionNodeBase public: /** * @brief A nav2_behavior_tree::BtServiceNode constructor - * @param service_node_name Service name this node creates a client for + * @param service_node_name BT node name * @param conf BT node configuration + * @param service_name Optional service name this node creates a client for instead of from input port */ BtServiceNode( const std::string & service_node_name, - const BT::NodeConfiguration & conf) - : BT::ActionNodeBase(service_node_name, conf), service_node_name_(service_node_name) + const BT::NodeConfiguration & conf, + const std::string & service_name = "") + : BT::ActionNodeBase(service_node_name, conf), service_name_(service_name), service_node_name_( + service_node_name) { node_ = config().blackboard->template get("node"); callback_group_ = node_->create_callback_group( @@ -128,7 +131,17 @@ class BtServiceNode : public BT::ActionNodeBase BT::NodeStatus tick() override { if (!request_sent_) { + // reset the flag to send the request or not, + // allowing the user the option to set it in on_tick + should_send_request_ = true; + + // user defined callback, may modify "should_send_request_". on_tick(); + + if (!should_send_request_) { + return BT::NodeStatus::FAILURE; + } + future_result_ = service_client_->async_send_request(request_).share(); sent_time_ = node_->now(); request_sent_ = true; @@ -240,6 +253,9 @@ class BtServiceNode : public BT::ActionNodeBase std::shared_future future_result_; bool request_sent_{false}; rclcpp::Time sent_time_; + + // Can be set in on_tick or on_wait_for_result to indicate if a request should be sent. + bool should_send_request_; }; } // namespace nav2_behavior_tree diff --git a/nav2_behavior_tree/include/nav2_behavior_tree/plugins/condition/is_battery_charging_condition.hpp b/nav2_behavior_tree/include/nav2_behavior_tree/plugins/condition/is_battery_charging_condition.hpp new file mode 100644 index 0000000000..37a53b41e1 --- /dev/null +++ b/nav2_behavior_tree/include/nav2_behavior_tree/plugins/condition/is_battery_charging_condition.hpp @@ -0,0 +1,81 @@ +// Copyright (c) 2023 Alberto J. Tudela Roldán +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef NAV2_BEHAVIOR_TREE__PLUGINS__CONDITION__IS_BATTERY_CHARGING_CONDITION_HPP_ +#define NAV2_BEHAVIOR_TREE__PLUGINS__CONDITION__IS_BATTERY_CHARGING_CONDITION_HPP_ + +#include +#include +#include + +#include "rclcpp/rclcpp.hpp" +#include "sensor_msgs/msg/battery_state.hpp" +#include "behaviortree_cpp_v3/condition_node.h" + +namespace nav2_behavior_tree +{ + +/** + * @brief A BT::ConditionNode that listens to a battery topic and + * returns SUCCESS when battery is charging and FAILURE otherwise + */ +class IsBatteryChargingCondition : public BT::ConditionNode +{ +public: + /** + * @brief A constructor for nav2_behavior_tree::IsBatteryChargingCondition + * @param condition_name Name for the XML tag for this node + * @param conf BT node configuration + */ + IsBatteryChargingCondition( + const std::string & condition_name, + const BT::NodeConfiguration & conf); + + IsBatteryChargingCondition() = delete; + + /** + * @brief The main override required by a BT action + * @return BT::NodeStatus Status of tick execution + */ + BT::NodeStatus tick() override; + + /** + * @brief Creates list of BT ports + * @return BT::PortsList Containing node-specific ports + */ + static BT::PortsList providedPorts() + { + return { + BT::InputPort( + "battery_topic", std::string("/battery_status"), "Battery topic") + }; + } + +private: + /** + * @brief Callback function for battery topic + * @param msg Shared pointer to sensor_msgs::msg::BatteryState message + */ + void batteryCallback(sensor_msgs::msg::BatteryState::SharedPtr msg); + + rclcpp::CallbackGroup::SharedPtr callback_group_; + rclcpp::executors::SingleThreadedExecutor callback_group_executor_; + rclcpp::Subscription::SharedPtr battery_sub_; + std::string battery_topic_; + bool is_battery_charging_; +}; + +} // namespace nav2_behavior_tree + +#endif // NAV2_BEHAVIOR_TREE__PLUGINS__CONDITION__IS_BATTERY_CHARGING_CONDITION_HPP_ diff --git a/nav2_behavior_tree/nav2_tree_nodes.xml b/nav2_behavior_tree/nav2_tree_nodes.xml index 49cfeffb4a..3f199f3d99 100644 --- a/nav2_behavior_tree/nav2_tree_nodes.xml +++ b/nav2_behavior_tree/nav2_tree_nodes.xml @@ -44,7 +44,7 @@ - Service name to cancel the assisted teleop behavior + Server name to cancel the assisted teleop behavior Server timeout @@ -109,7 +109,7 @@ Path to follow - Goal checker + Goal checker Server name Server timeout @@ -218,6 +218,10 @@ Bool if check based on voltage or total % + + Topic for battery info + + Distance to check if passed reference frame to check in diff --git a/nav2_behavior_tree/package.xml b/nav2_behavior_tree/package.xml index 68e00ae4d8..30442368aa 100644 --- a/nav2_behavior_tree/package.xml +++ b/nav2_behavior_tree/package.xml @@ -2,7 +2,7 @@ nav2_behavior_tree - 1.1.6 + 1.1.7 TODO Michael Jeronimo Carlos Orduno diff --git a/nav2_behavior_tree/plugins/condition/is_battery_charging_condition.cpp b/nav2_behavior_tree/plugins/condition/is_battery_charging_condition.cpp new file mode 100644 index 0000000000..c9c05f6875 --- /dev/null +++ b/nav2_behavior_tree/plugins/condition/is_battery_charging_condition.cpp @@ -0,0 +1,66 @@ +// Copyright (c) 2023 Alberto J. Tudela Roldán +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "nav2_behavior_tree/plugins/condition/is_battery_charging_condition.hpp" + +namespace nav2_behavior_tree +{ + +IsBatteryChargingCondition::IsBatteryChargingCondition( + const std::string & condition_name, + const BT::NodeConfiguration & conf) +: BT::ConditionNode(condition_name, conf), + battery_topic_("/battery_status"), + is_battery_charging_(false) +{ + getInput("battery_topic", battery_topic_); + auto node = config().blackboard->get("node"); + callback_group_ = node->create_callback_group( + rclcpp::CallbackGroupType::MutuallyExclusive, + false); + callback_group_executor_.add_callback_group(callback_group_, node->get_node_base_interface()); + + rclcpp::SubscriptionOptions sub_option; + sub_option.callback_group = callback_group_; + battery_sub_ = node->create_subscription( + battery_topic_, + rclcpp::SystemDefaultsQoS(), + std::bind(&IsBatteryChargingCondition::batteryCallback, this, std::placeholders::_1), + sub_option); +} + +BT::NodeStatus IsBatteryChargingCondition::tick() +{ + callback_group_executor_.spin_some(); + if (is_battery_charging_) { + return BT::NodeStatus::SUCCESS; + } + return BT::NodeStatus::FAILURE; +} + +void IsBatteryChargingCondition::batteryCallback(sensor_msgs::msg::BatteryState::SharedPtr msg) +{ + is_battery_charging_ = + (msg->power_supply_status == sensor_msgs::msg::BatteryState::POWER_SUPPLY_STATUS_CHARGING); +} + +} // namespace nav2_behavior_tree + +#include "behaviortree_cpp_v3/bt_factory.h" +BT_REGISTER_NODES(factory) +{ + factory.registerNodeType("IsBatteryCharging"); +} diff --git a/nav2_behavior_tree/test/plugins/condition/CMakeLists.txt b/nav2_behavior_tree/test/plugins/condition/CMakeLists.txt index 21fd0b2940..3e5ba4392b 100644 --- a/nav2_behavior_tree/test/plugins/condition/CMakeLists.txt +++ b/nav2_behavior_tree/test/plugins/condition/CMakeLists.txt @@ -34,6 +34,10 @@ ament_add_gtest(test_condition_is_stuck test_is_stuck.cpp) target_link_libraries(test_condition_is_stuck nav2_is_stuck_condition_bt_node) ament_target_dependencies(test_condition_is_stuck ${dependencies}) +ament_add_gtest(test_condition_is_battery_charging test_is_battery_charging.cpp) +target_link_libraries(test_condition_is_battery_charging nav2_is_battery_charging_condition_bt_node) +ament_target_dependencies(test_condition_is_battery_charging ${dependencies}) + ament_add_gtest(test_condition_is_battery_low test_is_battery_low.cpp) target_link_libraries(test_condition_is_battery_low nav2_is_battery_low_condition_bt_node) ament_target_dependencies(test_condition_is_battery_low ${dependencies}) diff --git a/nav2_behavior_tree/test/plugins/condition/test_is_battery_charging.cpp b/nav2_behavior_tree/test/plugins/condition/test_is_battery_charging.cpp new file mode 100644 index 0000000000..46af6d5c6e --- /dev/null +++ b/nav2_behavior_tree/test/plugins/condition/test_is_battery_charging.cpp @@ -0,0 +1,130 @@ +// Copyright (c) 2023 Alberto J. Tudela Roldán +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include + +#include "sensor_msgs/msg/battery_state.hpp" + +#include "utils/test_behavior_tree_fixture.hpp" +#include "nav2_behavior_tree/plugins/condition/is_battery_charging_condition.hpp" + +class IsBatteryChargingConditionTestFixture : public ::testing::Test +{ +public: + static void SetUpTestCase() + { + node_ = std::make_shared("test_is_battery_charging"); + factory_ = std::make_shared(); + + config_ = new BT::NodeConfiguration(); + + // Create the blackboard that will be shared by all of the nodes in the tree + config_->blackboard = BT::Blackboard::create(); + // Put items on the blackboard + config_->blackboard->set( + "node", + node_); + + factory_->registerNodeType("IsBatteryCharging"); + + battery_pub_ = node_->create_publisher( + "/battery_status", + rclcpp::SystemDefaultsQoS()); + } + + static void TearDownTestCase() + { + delete config_; + config_ = nullptr; + battery_pub_.reset(); + node_.reset(); + factory_.reset(); + } + +protected: + static rclcpp::Node::SharedPtr node_; + static BT::NodeConfiguration * config_; + static std::shared_ptr factory_; + static rclcpp::Publisher::SharedPtr battery_pub_; +}; + +rclcpp::Node::SharedPtr IsBatteryChargingConditionTestFixture::node_ = nullptr; +BT::NodeConfiguration * IsBatteryChargingConditionTestFixture::config_ = nullptr; +std::shared_ptr IsBatteryChargingConditionTestFixture::factory_ = nullptr; +rclcpp::Publisher::SharedPtr +IsBatteryChargingConditionTestFixture::battery_pub_ = nullptr; + +TEST_F(IsBatteryChargingConditionTestFixture, test_behavior_power_supply_status) +{ + std::string xml_txt = + R"( + + + + + )"; + + auto tree = factory_->createTreeFromText(xml_txt, config_->blackboard); + + sensor_msgs::msg::BatteryState battery_msg; + battery_msg.power_supply_status = sensor_msgs::msg::BatteryState::POWER_SUPPLY_STATUS_UNKNOWN; + battery_pub_->publish(battery_msg); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + rclcpp::spin_some(node_); + EXPECT_EQ(tree.tickRoot(), BT::NodeStatus::FAILURE); + + battery_msg.power_supply_status = sensor_msgs::msg::BatteryState::POWER_SUPPLY_STATUS_CHARGING; + battery_pub_->publish(battery_msg); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + rclcpp::spin_some(node_); + EXPECT_EQ(tree.tickRoot(), BT::NodeStatus::SUCCESS); + + battery_msg.power_supply_status = sensor_msgs::msg::BatteryState::POWER_SUPPLY_STATUS_DISCHARGING; + battery_pub_->publish(battery_msg); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + rclcpp::spin_some(node_); + EXPECT_EQ(tree.tickRoot(), BT::NodeStatus::FAILURE); + + battery_msg.power_supply_status = + sensor_msgs::msg::BatteryState::POWER_SUPPLY_STATUS_NOT_CHARGING; + battery_pub_->publish(battery_msg); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + rclcpp::spin_some(node_); + EXPECT_EQ(tree.tickRoot(), BT::NodeStatus::FAILURE); + + battery_msg.power_supply_status = sensor_msgs::msg::BatteryState::POWER_SUPPLY_STATUS_FULL; + battery_pub_->publish(battery_msg); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + rclcpp::spin_some(node_); + EXPECT_EQ(tree.tickRoot(), BT::NodeStatus::FAILURE); +} + +int main(int argc, char ** argv) +{ + ::testing::InitGoogleTest(&argc, argv); + + // initialize ROS + rclcpp::init(argc, argv); + + bool all_successful = RUN_ALL_TESTS(); + + // shutdown ROS + rclcpp::shutdown(); + + return all_successful; +} diff --git a/nav2_behaviors/package.xml b/nav2_behaviors/package.xml index b4b941258e..e3855fc231 100644 --- a/nav2_behaviors/package.xml +++ b/nav2_behaviors/package.xml @@ -2,7 +2,7 @@ nav2_behaviors - 1.1.6 + 1.1.7 TODO Carlos Orduno Steve Macenski diff --git a/nav2_bringup/package.xml b/nav2_bringup/package.xml index 3d0ee04f32..89e9d77d87 100644 --- a/nav2_bringup/package.xml +++ b/nav2_bringup/package.xml @@ -2,7 +2,7 @@ nav2_bringup - 1.1.6 + 1.1.7 Bringup scripts and configurations for the Nav2 stack Michael Jeronimo Steve Macenski diff --git a/nav2_bringup/params/nav2_multirobot_params_1.yaml b/nav2_bringup/params/nav2_multirobot_params_1.yaml index a7659a337f..32d82f7fec 100644 --- a/nav2_bringup/params/nav2_multirobot_params_1.yaml +++ b/nav2_bringup/params/nav2_multirobot_params_1.yaml @@ -98,6 +98,7 @@ bt_navigator: - nav2_back_up_cancel_bt_node - nav2_assisted_teleop_cancel_bt_node - nav2_drive_on_heading_cancel_bt_node + - nav2_is_battery_charging_condition_bt_node error_code_names: - compute_path_error_code - follow_path_error_code diff --git a/nav2_bringup/params/nav2_multirobot_params_2.yaml b/nav2_bringup/params/nav2_multirobot_params_2.yaml index a75dcbc66b..75b7338513 100644 --- a/nav2_bringup/params/nav2_multirobot_params_2.yaml +++ b/nav2_bringup/params/nav2_multirobot_params_2.yaml @@ -98,6 +98,7 @@ bt_navigator: - nav2_back_up_cancel_bt_node - nav2_assisted_teleop_cancel_bt_node - nav2_drive_on_heading_cancel_bt_node + - nav2_is_battery_charging_condition_bt_node error_code_names: - compute_path_error_code - follow_path_error_code diff --git a/nav2_bringup/params/nav2_params.yaml b/nav2_bringup/params/nav2_params.yaml index d66df5d5e0..f8aad5616e 100644 --- a/nav2_bringup/params/nav2_params.yaml +++ b/nav2_bringup/params/nav2_params.yaml @@ -52,52 +52,57 @@ bt_navigator: # nav2_bt_navigator/navigate_through_poses_w_replanning_and_recovery.xml # They can be set here or via a RewrittenYaml remap from a parent launch file to Nav2. plugin_lib_names: - - nav2_compute_path_to_pose_action_bt_node - - nav2_compute_path_through_poses_action_bt_node - - nav2_smooth_path_action_bt_node - - nav2_follow_path_action_bt_node - - nav2_spin_action_bt_node - - nav2_wait_action_bt_node - - nav2_assisted_teleop_action_bt_node - - nav2_back_up_action_bt_node - - nav2_drive_on_heading_bt_node - - nav2_clear_costmap_service_bt_node - - nav2_is_stuck_condition_bt_node - - nav2_goal_reached_condition_bt_node - - nav2_goal_updated_condition_bt_node - - nav2_globally_updated_goal_condition_bt_node - - nav2_is_path_valid_condition_bt_node - - nav2_initial_pose_received_condition_bt_node - - nav2_reinitialize_global_localization_service_bt_node - - nav2_rate_controller_bt_node - - nav2_distance_controller_bt_node - - nav2_speed_controller_bt_node - - nav2_truncate_path_action_bt_node - - nav2_truncate_path_local_action_bt_node - - nav2_goal_updater_node_bt_node - - nav2_recovery_node_bt_node - - nav2_pipeline_sequence_bt_node - - nav2_round_robin_node_bt_node - - nav2_transform_available_condition_bt_node - - nav2_time_expired_condition_bt_node - - nav2_path_expiring_timer_condition - - nav2_distance_traveled_condition_bt_node - - nav2_single_trigger_bt_node - - nav2_goal_updated_controller_bt_node - - nav2_is_battery_low_condition_bt_node - - nav2_navigate_through_poses_action_bt_node - - nav2_navigate_to_pose_action_bt_node - - nav2_remove_passed_goals_action_bt_node - - nav2_planner_selector_bt_node - - nav2_controller_selector_bt_node - - nav2_goal_checker_selector_bt_node - - nav2_controller_cancel_bt_node - - nav2_path_longer_on_approach_bt_node - - nav2_wait_cancel_bt_node - - nav2_spin_cancel_bt_node - - nav2_back_up_cancel_bt_node - - nav2_assisted_teleop_cancel_bt_node - - nav2_drive_on_heading_cancel_bt_node + - nav2_compute_path_to_pose_action_bt_node + - nav2_compute_path_through_poses_action_bt_node + - nav2_smooth_path_action_bt_node + - nav2_follow_path_action_bt_node + - nav2_spin_action_bt_node + - nav2_wait_action_bt_node + - nav2_assisted_teleop_action_bt_node + - nav2_back_up_action_bt_node + - nav2_drive_on_heading_bt_node + - nav2_clear_costmap_service_bt_node + - nav2_is_stuck_condition_bt_node + - nav2_goal_reached_condition_bt_node + - nav2_goal_updated_condition_bt_node + - nav2_globally_updated_goal_condition_bt_node + - nav2_is_path_valid_condition_bt_node + - nav2_are_error_codes_active_condition_bt_node + - nav2_would_a_controller_recovery_help_condition_bt_node + - nav2_would_a_planner_recovery_help_condition_bt_node + - nav2_would_a_smoother_recovery_help_condition_bt_node + - nav2_initial_pose_received_condition_bt_node + - nav2_reinitialize_global_localization_service_bt_node + - nav2_rate_controller_bt_node + - nav2_distance_controller_bt_node + - nav2_speed_controller_bt_node + - nav2_truncate_path_action_bt_node + - nav2_truncate_path_local_action_bt_node + - nav2_goal_updater_node_bt_node + - nav2_recovery_node_bt_node + - nav2_pipeline_sequence_bt_node + - nav2_round_robin_node_bt_node + - nav2_transform_available_condition_bt_node + - nav2_time_expired_condition_bt_node + - nav2_path_expiring_timer_condition + - nav2_distance_traveled_condition_bt_node + - nav2_single_trigger_bt_node + - nav2_goal_updated_controller_bt_node + - nav2_is_battery_low_condition_bt_node + - nav2_navigate_through_poses_action_bt_node + - nav2_navigate_to_pose_action_bt_node + - nav2_remove_passed_goals_action_bt_node + - nav2_planner_selector_bt_node + - nav2_controller_selector_bt_node + - nav2_goal_checker_selector_bt_node + - nav2_controller_cancel_bt_node + - nav2_path_longer_on_approach_bt_node + - nav2_wait_cancel_bt_node + - nav2_spin_cancel_bt_node + - nav2_back_up_cancel_bt_node + - nav2_assisted_teleop_cancel_bt_node + - nav2_drive_on_heading_cancel_bt_node + - nav2_is_battery_charging_condition_bt_node bt_navigator_navigate_through_poses_rclcpp_node: ros__parameters: diff --git a/nav2_bt_navigator/package.xml b/nav2_bt_navigator/package.xml index f99cdcb03b..7f5530c212 100644 --- a/nav2_bt_navigator/package.xml +++ b/nav2_bt_navigator/package.xml @@ -2,7 +2,7 @@ nav2_bt_navigator - 1.1.6 + 1.1.7 TODO Michael Jeronimo Apache-2.0 diff --git a/nav2_bt_navigator/src/bt_navigator.cpp b/nav2_bt_navigator/src/bt_navigator.cpp index e2b9d8fc00..4f0f690d01 100644 --- a/nav2_bt_navigator/src/bt_navigator.cpp +++ b/nav2_bt_navigator/src/bt_navigator.cpp @@ -79,7 +79,8 @@ BtNavigator::BtNavigator(const rclcpp::NodeOptions & options) "nav2_spin_cancel_bt_node", "nav2_assisted_teleop_cancel_bt_node", "nav2_back_up_cancel_bt_node", - "nav2_drive_on_heading_cancel_bt_node" + "nav2_drive_on_heading_cancel_bt_node", + "nav2_is_battery_charging_condition_bt_node" }; declare_parameter("plugin_lib_names", plugin_libs); diff --git a/nav2_collision_monitor/include/nav2_collision_monitor/collision_monitor_node.hpp b/nav2_collision_monitor/include/nav2_collision_monitor/collision_monitor_node.hpp index 87f498e401..2dc2995856 100644 --- a/nav2_collision_monitor/include/nav2_collision_monitor/collision_monitor_node.hpp +++ b/nav2_collision_monitor/include/nav2_collision_monitor/collision_monitor_node.hpp @@ -127,13 +127,16 @@ class CollisionMonitor : public nav2_util::LifecycleNode * source->base time inerpolated transform. * @param transform_tolerance Transform tolerance * @param source_timeout Maximum time interval in which data is considered valid + * @param base_shift_correction Whether to correct source data towards to base frame movement, + * considering the difference between current time and latest source time * @return True if all sources were configured successfully or false in failure case */ bool configureSources( const std::string & base_frame_id, const std::string & odom_frame_id, const tf2::Duration & transform_tolerance, - const rclcpp::Duration & source_timeout); + const rclcpp::Duration & source_timeout, + const bool base_shift_correction); /** * @brief Main processing routine diff --git a/nav2_collision_monitor/include/nav2_collision_monitor/pointcloud.hpp b/nav2_collision_monitor/include/nav2_collision_monitor/pointcloud.hpp index c316c065f7..d5af32c277 100644 --- a/nav2_collision_monitor/include/nav2_collision_monitor/pointcloud.hpp +++ b/nav2_collision_monitor/include/nav2_collision_monitor/pointcloud.hpp @@ -41,6 +41,8 @@ class PointCloud : public Source * @param global_frame_id Global frame ID for correct transform calculation * @param transform_tolerance Transform tolerance * @param source_timeout Maximum time interval in which data is considered valid + * @param base_shift_correction Whether to correct source data towards to base frame movement, + * considering the difference between current time and latest source time */ PointCloud( const nav2_util::LifecycleNode::WeakPtr & node, @@ -49,7 +51,8 @@ class PointCloud : public Source const std::string & base_frame_id, const std::string & global_frame_id, const tf2::Duration & transform_tolerance, - const rclcpp::Duration & source_timeout); + const rclcpp::Duration & source_timeout, + const bool base_shift_correction); /** * @brief PointCloud destructor */ diff --git a/nav2_collision_monitor/include/nav2_collision_monitor/range.hpp b/nav2_collision_monitor/include/nav2_collision_monitor/range.hpp index 0fbe47501a..777b8e404c 100644 --- a/nav2_collision_monitor/include/nav2_collision_monitor/range.hpp +++ b/nav2_collision_monitor/include/nav2_collision_monitor/range.hpp @@ -41,6 +41,8 @@ class Range : public Source * @param global_frame_id Global frame ID for correct transform calculation * @param transform_tolerance Transform tolerance * @param source_timeout Maximum time interval in which data is considered valid + * @param base_shift_correction Whether to correct source data towards to base frame movement, + * considering the difference between current time and latest source time */ Range( const nav2_util::LifecycleNode::WeakPtr & node, @@ -49,7 +51,8 @@ class Range : public Source const std::string & base_frame_id, const std::string & global_frame_id, const tf2::Duration & transform_tolerance, - const rclcpp::Duration & source_timeout); + const rclcpp::Duration & source_timeout, + const bool base_shift_correction); /** * @brief Range destructor */ diff --git a/nav2_collision_monitor/include/nav2_collision_monitor/scan.hpp b/nav2_collision_monitor/include/nav2_collision_monitor/scan.hpp index 29747e8131..c3f7a11f3f 100644 --- a/nav2_collision_monitor/include/nav2_collision_monitor/scan.hpp +++ b/nav2_collision_monitor/include/nav2_collision_monitor/scan.hpp @@ -41,6 +41,8 @@ class Scan : public Source * @param global_frame_id Global frame ID for correct transform calculation * @param transform_tolerance Transform tolerance * @param source_timeout Maximum time interval in which data is considered valid + * @param base_shift_correction Whether to correct source data towards to base frame movement, + * considering the difference between current time and latest source time */ Scan( const nav2_util::LifecycleNode::WeakPtr & node, @@ -49,7 +51,8 @@ class Scan : public Source const std::string & base_frame_id, const std::string & global_frame_id, const tf2::Duration & transform_tolerance, - const rclcpp::Duration & source_timeout); + const rclcpp::Duration & source_timeout, + const bool base_shift_correction); /** * @brief Scan destructor */ diff --git a/nav2_collision_monitor/include/nav2_collision_monitor/source.hpp b/nav2_collision_monitor/include/nav2_collision_monitor/source.hpp index a24859bb4a..b7d58b6736 100644 --- a/nav2_collision_monitor/include/nav2_collision_monitor/source.hpp +++ b/nav2_collision_monitor/include/nav2_collision_monitor/source.hpp @@ -46,6 +46,8 @@ class Source * @param global_frame_id Global frame ID for correct transform calculation * @param transform_tolerance Transform tolerance * @param source_timeout Maximum time interval in which data is considered valid + * @param base_shift_correction Whether to correct source data towards to base frame movement, + * considering the difference between current time and latest source time */ Source( const nav2_util::LifecycleNode::WeakPtr & node, @@ -54,7 +56,8 @@ class Source const std::string & base_frame_id, const std::string & global_frame_id, const tf2::Duration & transform_tolerance, - const rclcpp::Duration & source_timeout); + const rclcpp::Duration & source_timeout, + const bool base_shift_correction); /** * @brief Source destructor */ @@ -125,6 +128,9 @@ class Source tf2::Duration transform_tolerance_; /// @brief Maximum time interval in which data is considered valid rclcpp::Duration source_timeout_; + /// @brief Whether to correct source data towards to base frame movement, + /// considering the difference between current time and latest source time + bool base_shift_correction_; }; // class Source } // namespace nav2_collision_monitor diff --git a/nav2_collision_monitor/package.xml b/nav2_collision_monitor/package.xml index 7efdefac4e..67edd5606c 100644 --- a/nav2_collision_monitor/package.xml +++ b/nav2_collision_monitor/package.xml @@ -2,7 +2,7 @@ nav2_collision_monitor - 1.1.6 + 1.1.7 Collision Monitor Alexey Merzlyakov Steve Macenski diff --git a/nav2_collision_monitor/params/collision_monitor_params.yaml b/nav2_collision_monitor/params/collision_monitor_params.yaml index f0fb4ef5dd..1b0c36529e 100644 --- a/nav2_collision_monitor/params/collision_monitor_params.yaml +++ b/nav2_collision_monitor/params/collision_monitor_params.yaml @@ -7,6 +7,7 @@ collision_monitor: cmd_vel_out_topic: "cmd_vel" transform_tolerance: 0.5 source_timeout: 5.0 + base_shift_correction: True stop_pub_timeout: 2.0 # Polygons represent zone around the robot for "stop" and "slowdown" action types, # and robot footprint for "approach" action type. diff --git a/nav2_collision_monitor/src/collision_monitor_node.cpp b/nav2_collision_monitor/src/collision_monitor_node.cpp index 313d71fb0a..939555bf3d 100644 --- a/nav2_collision_monitor/src/collision_monitor_node.cpp +++ b/nav2_collision_monitor/src/collision_monitor_node.cpp @@ -205,6 +205,10 @@ bool CollisionMonitor::getParameters( node, "source_timeout", rclcpp::ParameterValue(2.0)); source_timeout = rclcpp::Duration::from_seconds(get_parameter("source_timeout").as_double()); + nav2_util::declare_parameter_if_not_declared( + node, "base_shift_correction", rclcpp::ParameterValue(true)); + const bool base_shift_correction = + get_parameter("base_shift_correction").as_bool(); nav2_util::declare_parameter_if_not_declared( node, "stop_pub_timeout", rclcpp::ParameterValue(1.0)); @@ -215,7 +219,10 @@ bool CollisionMonitor::getParameters( return false; } - if (!configureSources(base_frame_id, odom_frame_id, transform_tolerance, source_timeout)) { + if ( + !configureSources( + base_frame_id, odom_frame_id, transform_tolerance, source_timeout, base_shift_correction)) + { return false; } @@ -271,7 +278,8 @@ bool CollisionMonitor::configureSources( const std::string & base_frame_id, const std::string & odom_frame_id, const tf2::Duration & transform_tolerance, - const rclcpp::Duration & source_timeout) + const rclcpp::Duration & source_timeout, + const bool base_shift_correction) { try { auto node = shared_from_this(); @@ -289,7 +297,7 @@ bool CollisionMonitor::configureSources( if (source_type == "scan") { std::shared_ptr s = std::make_shared( node, source_name, tf_buffer_, base_frame_id, odom_frame_id, - transform_tolerance, source_timeout); + transform_tolerance, source_timeout, base_shift_correction); s->configure(); @@ -297,7 +305,7 @@ bool CollisionMonitor::configureSources( } else if (source_type == "pointcloud") { std::shared_ptr p = std::make_shared( node, source_name, tf_buffer_, base_frame_id, odom_frame_id, - transform_tolerance, source_timeout); + transform_tolerance, source_timeout, base_shift_correction); p->configure(); @@ -305,7 +313,7 @@ bool CollisionMonitor::configureSources( } else if (source_type == "range") { std::shared_ptr r = std::make_shared( node, source_name, tf_buffer_, base_frame_id, odom_frame_id, - transform_tolerance, source_timeout); + transform_tolerance, source_timeout, base_shift_correction); r->configure(); diff --git a/nav2_collision_monitor/src/pointcloud.cpp b/nav2_collision_monitor/src/pointcloud.cpp index df4e86b63e..d4d2ea1adf 100644 --- a/nav2_collision_monitor/src/pointcloud.cpp +++ b/nav2_collision_monitor/src/pointcloud.cpp @@ -30,10 +30,11 @@ PointCloud::PointCloud( const std::string & base_frame_id, const std::string & global_frame_id, const tf2::Duration & transform_tolerance, - const rclcpp::Duration & source_timeout) + const rclcpp::Duration & source_timeout, + const bool base_shift_correction) : Source( node, source_name, tf_buffer, base_frame_id, global_frame_id, - transform_tolerance, source_timeout), + transform_tolerance, source_timeout, base_shift_correction), data_(nullptr) { RCLCPP_INFO(logger_, "[%s]: Creating PointCloud", source_name_.c_str()); @@ -75,11 +76,29 @@ void PointCloud::getData( return; } - // Obtaining the transform to get data from source frame and time where it was received - // to the base frame and current time tf2::Transform tf_transform; - if (!getTransform(data_->header.frame_id, data_->header.stamp, curr_time, tf_transform)) { - return; + if (base_shift_correction_) { + // Obtaining the transform to get data from source frame and time where it was received + // to the base frame and current time + if ( + !nav2_util::getTransform( + data_->header.frame_id, data_->header.stamp, + base_frame_id_, curr_time, global_frame_id_, + transform_tolerance_, tf_buffer_, tf_transform)) + { + return; + } + } else { + // Obtaining the transform to get data from source frame to base frame without time shift + // considered. Less accurate but much more faster option not dependent on state estimation + // frames. + if ( + !nav2_util::getTransform( + data_->header.frame_id, base_frame_id_, + transform_tolerance_, tf_buffer_, tf_transform)) + { + return; + } } sensor_msgs::PointCloud2ConstIterator iter_x(*data_, "x"); diff --git a/nav2_collision_monitor/src/range.cpp b/nav2_collision_monitor/src/range.cpp index 3ef51e2b69..7c12a4bd52 100644 --- a/nav2_collision_monitor/src/range.cpp +++ b/nav2_collision_monitor/src/range.cpp @@ -30,10 +30,11 @@ Range::Range( const std::string & base_frame_id, const std::string & global_frame_id, const tf2::Duration & transform_tolerance, - const rclcpp::Duration & source_timeout) + const rclcpp::Duration & source_timeout, + const bool base_shift_correction) : Source( node, source_name, tf_buffer, base_frame_id, global_frame_id, - transform_tolerance, source_timeout), + transform_tolerance, source_timeout, base_shift_correction), data_(nullptr) { RCLCPP_INFO(logger_, "[%s]: Creating Range", source_name_.c_str()); @@ -84,11 +85,29 @@ void Range::getData( return; } - // Obtaining the transform to get data from source frame and time where it was received - // to the base frame and current time tf2::Transform tf_transform; - if (!getTransform(data_->header.frame_id, data_->header.stamp, curr_time, tf_transform)) { - return; + if (base_shift_correction_) { + // Obtaining the transform to get data from source frame and time where it was received + // to the base frame and current time + if ( + !nav2_util::getTransform( + data_->header.frame_id, data_->header.stamp, + base_frame_id_, curr_time, global_frame_id_, + transform_tolerance_, tf_buffer_, tf_transform)) + { + return; + } + } else { + // Obtaining the transform to get data from source frame to base frame without time shift + // considered. Less accurate but much more faster option not dependent on state estimation + // frames. + if ( + !nav2_util::getTransform( + data_->header.frame_id, base_frame_id_, + transform_tolerance_, tf_buffer_, tf_transform)) + { + return; + } } // Calculate poses and refill data array diff --git a/nav2_collision_monitor/src/scan.cpp b/nav2_collision_monitor/src/scan.cpp index d1f52b31f1..50f670cb13 100644 --- a/nav2_collision_monitor/src/scan.cpp +++ b/nav2_collision_monitor/src/scan.cpp @@ -27,10 +27,11 @@ Scan::Scan( const std::string & base_frame_id, const std::string & global_frame_id, const tf2::Duration & transform_tolerance, - const rclcpp::Duration & source_timeout) + const rclcpp::Duration & source_timeout, + const bool base_shift_correction) : Source( node, source_name, tf_buffer, base_frame_id, global_frame_id, - transform_tolerance, source_timeout), + transform_tolerance, source_timeout, base_shift_correction), data_(nullptr) { RCLCPP_INFO(logger_, "[%s]: Creating Scan", source_name_.c_str()); @@ -73,11 +74,29 @@ void Scan::getData( return; } - // Obtaining the transform to get data from source frame and time where it was received - // to the base frame and current time tf2::Transform tf_transform; - if (!getTransform(data_->header.frame_id, data_->header.stamp, curr_time, tf_transform)) { - return; + if (base_shift_correction_) { + // Obtaining the transform to get data from source frame and time where it was received + // to the base frame and current time + if ( + !nav2_util::getTransform( + data_->header.frame_id, data_->header.stamp, + base_frame_id_, curr_time, global_frame_id_, + transform_tolerance_, tf_buffer_, tf_transform)) + { + return; + } + } else { + // Obtaining the transform to get data from source frame to base frame without time shift + // considered. Less accurate but much more faster option not dependent on state estimation + // frames. + if ( + !nav2_util::getTransform( + data_->header.frame_id, base_frame_id_, + transform_tolerance_, tf_buffer_, tf_transform)) + { + return; + } } // Calculate poses and refill data array diff --git a/nav2_collision_monitor/src/source.cpp b/nav2_collision_monitor/src/source.cpp index bd1028518c..2b73672368 100644 --- a/nav2_collision_monitor/src/source.cpp +++ b/nav2_collision_monitor/src/source.cpp @@ -33,10 +33,12 @@ Source::Source( const std::string & base_frame_id, const std::string & global_frame_id, const tf2::Duration & transform_tolerance, - const rclcpp::Duration & source_timeout) + const rclcpp::Duration & source_timeout, + const bool base_shift_correction) : node_(node), source_name_(source_name), tf_buffer_(tf_buffer), base_frame_id_(base_frame_id), global_frame_id_(global_frame_id), - transform_tolerance_(transform_tolerance), source_timeout_(source_timeout) + transform_tolerance_(transform_tolerance), source_timeout_(source_timeout), + base_shift_correction_(base_shift_correction) { } diff --git a/nav2_collision_monitor/test/sources_test.cpp b/nav2_collision_monitor/test/sources_test.cpp index 7101b61175..0bfcd1a062 100644 --- a/nav2_collision_monitor/test/sources_test.cpp +++ b/nav2_collision_monitor/test/sources_test.cpp @@ -172,10 +172,11 @@ class ScanWrapper : public nav2_collision_monitor::Scan const std::string & base_frame_id, const std::string & global_frame_id, const tf2::Duration & transform_tolerance, - const rclcpp::Duration & data_timeout) + const rclcpp::Duration & data_timeout, + const bool base_shift_correction) : nav2_collision_monitor::Scan( node, source_name, tf_buffer, base_frame_id, global_frame_id, - transform_tolerance, data_timeout) + transform_tolerance, data_timeout, base_shift_correction) {} bool dataReceived() const @@ -194,10 +195,11 @@ class PointCloudWrapper : public nav2_collision_monitor::PointCloud const std::string & base_frame_id, const std::string & global_frame_id, const tf2::Duration & transform_tolerance, - const rclcpp::Duration & data_timeout) + const rclcpp::Duration & data_timeout, + const bool base_shift_correction) : nav2_collision_monitor::PointCloud( node, source_name, tf_buffer, base_frame_id, global_frame_id, - transform_tolerance, data_timeout) + transform_tolerance, data_timeout, base_shift_correction) {} bool dataReceived() const @@ -216,10 +218,11 @@ class RangeWrapper : public nav2_collision_monitor::Range const std::string & base_frame_id, const std::string & global_frame_id, const tf2::Duration & transform_tolerance, - const rclcpp::Duration & data_timeout) + const rclcpp::Duration & data_timeout, + const bool base_shift_correction) : nav2_collision_monitor::Range( node, source_name, tf_buffer, base_frame_id, global_frame_id, - transform_tolerance, data_timeout) + transform_tolerance, data_timeout, base_shift_correction) {} bool dataReceived() const @@ -235,6 +238,9 @@ class Tester : public ::testing::Test ~Tester(); protected: + // Data sources creation routine + void createSources(const bool base_shift_correction = true); + // Setting TF chains void sendTransforms(const rclcpp::Time & stamp); @@ -263,7 +269,22 @@ Tester::Tester() tf_buffer_ = std::make_shared(test_node_->get_clock()); tf_buffer_->setUsingDedicatedThread(true); // One-thread broadcasting-listening model tf_listener_ = std::make_shared(*tf_buffer_); +} + +Tester::~Tester() +{ + scan_.reset(); + pointcloud_.reset(); + range_.reset(); + test_node_.reset(); + + tf_listener_.reset(); + tf_buffer_.reset(); +} + +void Tester::createSources(const bool base_shift_correction) +{ // Create Scan object test_node_->declare_parameter( std::string(SCAN_NAME) + ".topic", rclcpp::ParameterValue(SCAN_TOPIC)); @@ -273,7 +294,7 @@ Tester::Tester() scan_ = std::make_shared( test_node_, SCAN_NAME, tf_buffer_, BASE_FRAME_ID, GLOBAL_FRAME_ID, - TRANSFORM_TOLERANCE, DATA_TIMEOUT); + TRANSFORM_TOLERANCE, DATA_TIMEOUT, base_shift_correction); scan_->configure(); // Create PointCloud object @@ -293,7 +314,7 @@ Tester::Tester() pointcloud_ = std::make_shared( test_node_, POINTCLOUD_NAME, tf_buffer_, BASE_FRAME_ID, GLOBAL_FRAME_ID, - TRANSFORM_TOLERANCE, DATA_TIMEOUT); + TRANSFORM_TOLERANCE, DATA_TIMEOUT, base_shift_correction); pointcloud_->configure(); // Create Range object @@ -308,22 +329,10 @@ Tester::Tester() range_ = std::make_shared( test_node_, RANGE_NAME, tf_buffer_, BASE_FRAME_ID, GLOBAL_FRAME_ID, - TRANSFORM_TOLERANCE, DATA_TIMEOUT); + TRANSFORM_TOLERANCE, DATA_TIMEOUT, base_shift_correction); range_->configure(); } -Tester::~Tester() -{ - scan_.reset(); - pointcloud_.reset(); - range_.reset(); - - test_node_.reset(); - - tf_listener_.reset(); - tf_buffer_.reset(); -} - void Tester::sendTransforms(const rclcpp::Time & stamp) { std::shared_ptr tf_broadcaster = @@ -453,6 +462,8 @@ TEST_F(Tester, testGetData) { rclcpp::Time curr_time = test_node_->now(); + createSources(); + sendTransforms(curr_time); // Publish data for sources @@ -485,6 +496,8 @@ TEST_F(Tester, testGetOutdatedData) { rclcpp::Time curr_time = test_node_->now(); + createSources(); + sendTransforms(curr_time); // Publish outdated data for sources @@ -515,6 +528,8 @@ TEST_F(Tester, testIncorrectFrameData) { rclcpp::Time curr_time = test_node_->now(); + createSources(); + // Send incorrect transform sendTransforms(curr_time - 1s); @@ -546,6 +561,8 @@ TEST_F(Tester, testIncorrectData) { rclcpp::Time curr_time = test_node_->now(); + createSources(); + sendTransforms(curr_time); // Publish data for sources @@ -567,6 +584,41 @@ TEST_F(Tester, testIncorrectData) ASSERT_EQ(data.size(), 0u); } +TEST_F(Tester, testIgnoreTimeShift) +{ + rclcpp::Time curr_time = test_node_->now(); + + createSources(false); + + // Send incorrect transform + sendTransforms(curr_time - 1s); + + // Publish data for sources + test_node_->publishScan(curr_time, 1.0); + test_node_->publishPointCloud(curr_time); + test_node_->publishRange(curr_time, 1.0); + + // Wait until all sources will receive the data + ASSERT_TRUE(waitScan(500ms)); + ASSERT_TRUE(waitPointCloud(500ms)); + ASSERT_TRUE(waitRange(500ms)); + + // Scan data should be consistent + std::vector data; + scan_->getData(curr_time, data); + checkScan(data); + + // Pointcloud data should be consistent + data.clear(); + pointcloud_->getData(curr_time, data); + checkPointCloud(data); + + // Range data should be consistent + data.clear(); + range_->getData(curr_time, data); + checkRange(data); +} + int main(int argc, char ** argv) { // Initialize the system diff --git a/nav2_common/nav2_common/launch/rewritten_yaml.py b/nav2_common/nav2_common/launch/rewritten_yaml.py index 63d3905d5e..3b29fdda59 100644 --- a/nav2_common/nav2_common/launch/rewritten_yaml.py +++ b/nav2_common/nav2_common/launch/rewritten_yaml.py @@ -161,7 +161,7 @@ def pathify(self, d, p=None, paths=None, joinchar='.'): if isinstance(d, dict): for k in d: v = d[k] - self.pathify(v, pn + k, paths, joinchar=joinchar) + self.pathify(v, str(pn) + str(k), paths, joinchar=joinchar) elif isinstance(d, list): for idx, e in enumerate(d): self.pathify(e, pn + str(idx), paths, joinchar=joinchar) diff --git a/nav2_common/package.xml b/nav2_common/package.xml index 9056c712cf..8ef6b4cf6f 100644 --- a/nav2_common/package.xml +++ b/nav2_common/package.xml @@ -2,7 +2,7 @@ nav2_common - 1.1.6 + 1.1.7 Common support functionality used throughout the navigation 2 stack Carl Delsey Apache-2.0 diff --git a/nav2_constrained_smoother/CMakeLists.txt b/nav2_constrained_smoother/CMakeLists.txt index 0d0cb497ae..6bc4e7dc6e 100644 --- a/nav2_constrained_smoother/CMakeLists.txt +++ b/nav2_constrained_smoother/CMakeLists.txt @@ -17,6 +17,10 @@ find_package(Ceres REQUIRED COMPONENTS SuiteSparse) set(CMAKE_CXX_STANDARD 17) +if(${CERES_VERSION} VERSION_LESS_EQUAL 2.0.0) + add_definitions(-DUSE_OLD_CERES_API) +endif() + nav2_package() set(library_name nav2_constrained_smoother) diff --git a/nav2_constrained_smoother/include/nav2_constrained_smoother/smoother_cost_function.hpp b/nav2_constrained_smoother/include/nav2_constrained_smoother/smoother_cost_function.hpp index c0c3919ccc..7253119721 100644 --- a/nav2_constrained_smoother/include/nav2_constrained_smoother/smoother_cost_function.hpp +++ b/nav2_constrained_smoother/include/nav2_constrained_smoother/smoother_cost_function.hpp @@ -159,7 +159,7 @@ class SmootherCostFunction Eigen::Matrix center = arcCenter( pt_prev, pt, pt_next, next_to_last_length_ratio_ < 0); - if (ceres::isinf(center[0])) { + if (CERES_ISINF(center[0])) { return; } T turning_rad = (pt - center).norm(); diff --git a/nav2_constrained_smoother/include/nav2_constrained_smoother/utils.hpp b/nav2_constrained_smoother/include/nav2_constrained_smoother/utils.hpp index 682ed1c16b..fb4e2aa130 100644 --- a/nav2_constrained_smoother/include/nav2_constrained_smoother/utils.hpp +++ b/nav2_constrained_smoother/include/nav2_constrained_smoother/utils.hpp @@ -20,6 +20,16 @@ #define EPSILON 0.0001 +/** + * Compatibility with different ceres::isinf() and ceres::IsInfinite() API + * used in Ceres Solver 2.1.0+ and 2.0.0- versions respectively + */ +#if defined(USE_OLD_CERES_API) + #define CERES_ISINF(x) ceres::IsInfinite(x) +#else + #define CERES_ISINF(x) ceres::isinf(x) +#endif + namespace nav2_constrained_smoother { @@ -86,7 +96,7 @@ inline Eigen::Matrix tangentDir( bool is_cusp) { Eigen::Matrix center = arcCenter(pt_prev, pt, pt_next, is_cusp); - if (ceres::isinf(center[0])) { // straight line + if (CERES_ISINF(center[0])) { // straight line Eigen::Matrix d1 = pt - pt_prev; Eigen::Matrix d2 = pt_next - pt; diff --git a/nav2_constrained_smoother/package.xml b/nav2_constrained_smoother/package.xml index 837c67ec16..b20c946bf7 100644 --- a/nav2_constrained_smoother/package.xml +++ b/nav2_constrained_smoother/package.xml @@ -2,7 +2,7 @@ nav2_constrained_smoother - 1.1.6 + 1.1.7 Ceres constrained smoother Matej Vargovcik Steve Macenski diff --git a/nav2_controller/CMakeLists.txt b/nav2_controller/CMakeLists.txt index e5ea991c34..ded67fbf41 100644 --- a/nav2_controller/CMakeLists.txt +++ b/nav2_controller/CMakeLists.txt @@ -50,6 +50,10 @@ set(dependencies add_library(simple_progress_checker SHARED plugins/simple_progress_checker.cpp) ament_target_dependencies(simple_progress_checker ${dependencies}) +add_library(pose_progress_checker SHARED plugins/pose_progress_checker.cpp) +target_link_libraries(pose_progress_checker simple_progress_checker) +ament_target_dependencies(pose_progress_checker ${dependencies}) + add_library(simple_goal_checker SHARED plugins/simple_goal_checker.cpp) ament_target_dependencies(simple_goal_checker ${dependencies}) @@ -79,7 +83,7 @@ target_link_libraries(${executable_name} ${library_name}) rclcpp_components_register_nodes(${library_name} "nav2_controller::ControllerServer") -install(TARGETS simple_progress_checker simple_goal_checker stopped_goal_checker ${library_name} +install(TARGETS simple_progress_checker pose_progress_checker simple_goal_checker stopped_goal_checker ${library_name} ARCHIVE DESTINATION lib LIBRARY DESTINATION lib RUNTIME DESTINATION bin @@ -102,6 +106,7 @@ endif() ament_export_include_directories(include) ament_export_libraries(simple_progress_checker + pose_progress_checker simple_goal_checker stopped_goal_checker ${library_name}) diff --git a/nav2_controller/include/nav2_controller/plugins/pose_progress_checker.hpp b/nav2_controller/include/nav2_controller/plugins/pose_progress_checker.hpp new file mode 100644 index 0000000000..3db219eaae --- /dev/null +++ b/nav2_controller/include/nav2_controller/plugins/pose_progress_checker.hpp @@ -0,0 +1,67 @@ +// Copyright (c) 2023 Dexory +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef NAV2_CONTROLLER__PLUGINS__POSE_PROGRESS_CHECKER_HPP_ +#define NAV2_CONTROLLER__PLUGINS__POSE_PROGRESS_CHECKER_HPP_ + +#include +#include +#include "rclcpp/rclcpp.hpp" +#include "nav2_controller/plugins/simple_progress_checker.hpp" +#include "rclcpp_lifecycle/lifecycle_node.hpp" + +namespace nav2_controller +{ +/** +* @class PoseProgressChecker +* @brief This plugin is used to check the position and the angle of the robot to make sure +* that it is actually progressing or rotating towards a goal. +*/ + +class PoseProgressChecker : public SimpleProgressChecker +{ +public: + void initialize( + const rclcpp_lifecycle::LifecycleNode::WeakPtr & parent, + const std::string & plugin_name) override; + bool check(geometry_msgs::msg::PoseStamped & current_pose) override; + +protected: + /** + * @brief Calculates robots movement from baseline pose + * @param pose Current pose of the robot + * @return true, if movement is greater than radius_, or false + */ + bool isRobotMovedEnough(const geometry_msgs::msg::Pose2D & pose); + + static double poseAngleDistance( + const geometry_msgs::msg::Pose2D &, + const geometry_msgs::msg::Pose2D &); + + double required_movement_angle_; + + // Dynamic parameters handler + rclcpp::node_interfaces::OnSetParametersCallbackHandle::SharedPtr dyn_params_handler_; + std::string plugin_name_; + + /** + * @brief Callback executed when a paramter change is detected + * @param parameters list of changed parameters + */ + rcl_interfaces::msg::SetParametersResult + dynamicParametersCallback(std::vector parameters); +}; +} // namespace nav2_controller + +#endif // NAV2_CONTROLLER__PLUGINS__POSE_PROGRESS_CHECKER_HPP_ diff --git a/nav2_controller/include/nav2_controller/plugins/simple_progress_checker.hpp b/nav2_controller/include/nav2_controller/plugins/simple_progress_checker.hpp index 656d67fb30..b86a7018e5 100644 --- a/nav2_controller/include/nav2_controller/plugins/simple_progress_checker.hpp +++ b/nav2_controller/include/nav2_controller/plugins/simple_progress_checker.hpp @@ -46,12 +46,16 @@ class SimpleProgressChecker : public nav2_core::ProgressChecker * @param pose Current pose of the robot * @return true, if movement is greater than radius_, or false */ - bool is_robot_moved_enough(const geometry_msgs::msg::Pose2D & pose); + bool isRobotMovedEnough(const geometry_msgs::msg::Pose2D & pose); /** * @brief Resets baseline pose with the current pose of the robot * @param pose Current pose of the robot */ - void reset_baseline_pose(const geometry_msgs::msg::Pose2D & pose); + void resetBaselinePose(const geometry_msgs::msg::Pose2D & pose); + + static double pose_distance( + const geometry_msgs::msg::Pose2D &, + const geometry_msgs::msg::Pose2D &); rclcpp::Clock::SharedPtr clock_; diff --git a/nav2_controller/package.xml b/nav2_controller/package.xml index 3c6d8f8626..14bfa0e04e 100644 --- a/nav2_controller/package.xml +++ b/nav2_controller/package.xml @@ -2,7 +2,7 @@ nav2_controller - 1.1.6 + 1.1.7 Controller action interface Carl Delsey Apache-2.0 diff --git a/nav2_controller/plugins.xml b/nav2_controller/plugins.xml index ad27127c03..ffbd0e5f1c 100644 --- a/nav2_controller/plugins.xml +++ b/nav2_controller/plugins.xml @@ -4,6 +4,11 @@ Checks if distance between current and previous pose is above a threshold + + + Checks if distance and angle between current and previous pose is above a threshold + + Checks if current pose is within goal window for x,y and yaw diff --git a/nav2_controller/plugins/pose_progress_checker.cpp b/nav2_controller/plugins/pose_progress_checker.cpp new file mode 100644 index 0000000000..271e5635c0 --- /dev/null +++ b/nav2_controller/plugins/pose_progress_checker.cpp @@ -0,0 +1,97 @@ +// Copyright (c) 2023 Dexory +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "nav2_controller/plugins/pose_progress_checker.hpp" +#include +#include +#include +#include +#include "angles/angles.h" +#include "nav_2d_utils/conversions.hpp" +#include "geometry_msgs/msg/pose_stamped.hpp" +#include "geometry_msgs/msg/pose2_d.hpp" +#include "nav2_util/node_utils.hpp" +#include "pluginlib/class_list_macros.hpp" + +using rcl_interfaces::msg::ParameterType; +using std::placeholders::_1; + +namespace nav2_controller +{ + +void PoseProgressChecker::initialize( + const rclcpp_lifecycle::LifecycleNode::WeakPtr & parent, + const std::string & plugin_name) +{ + plugin_name_ = plugin_name; + SimpleProgressChecker::initialize(parent, plugin_name); + auto node = parent.lock(); + + nav2_util::declare_parameter_if_not_declared( + node, plugin_name + ".required_movement_angle", rclcpp::ParameterValue(0.5)); + node->get_parameter_or(plugin_name + ".required_movement_angle", required_movement_angle_, 0.5); + + // Add callback for dynamic parameters + dyn_params_handler_ = node->add_on_set_parameters_callback( + std::bind(&PoseProgressChecker::dynamicParametersCallback, this, _1)); +} + +bool PoseProgressChecker::check(geometry_msgs::msg::PoseStamped & current_pose) +{ + // relies on short circuit evaluation to not call is_robot_moved_enough if + // baseline_pose is not set. + geometry_msgs::msg::Pose2D current_pose2d; + current_pose2d = nav_2d_utils::poseToPose2D(current_pose.pose); + + if (!baseline_pose_set_ || PoseProgressChecker::isRobotMovedEnough(current_pose2d)) { + resetBaselinePose(current_pose2d); + return true; + } + return clock_->now() - baseline_time_ <= time_allowance_; +} + +bool PoseProgressChecker::isRobotMovedEnough(const geometry_msgs::msg::Pose2D & pose) +{ + return pose_distance(pose, baseline_pose_) > radius_ || + poseAngleDistance(pose, baseline_pose_) > required_movement_angle_; +} + +double PoseProgressChecker::poseAngleDistance( + const geometry_msgs::msg::Pose2D & pose1, + const geometry_msgs::msg::Pose2D & pose2) +{ + return abs(angles::shortest_angular_distance(pose1.theta, pose2.theta)); +} + +rcl_interfaces::msg::SetParametersResult +PoseProgressChecker::dynamicParametersCallback(std::vector parameters) +{ + rcl_interfaces::msg::SetParametersResult result; + for (auto parameter : parameters) { + const auto & type = parameter.get_type(); + const auto & name = parameter.get_name(); + + if (type == ParameterType::PARAMETER_DOUBLE) { + if (name == plugin_name_ + ".required_movement_angle") { + required_movement_angle_ = parameter.as_double(); + } + } + } + result.successful = true; + return result; +} + +} // namespace nav2_controller + +PLUGINLIB_EXPORT_CLASS(nav2_controller::PoseProgressChecker, nav2_core::ProgressChecker) diff --git a/nav2_controller/plugins/simple_progress_checker.cpp b/nav2_controller/plugins/simple_progress_checker.cpp index 1ced16dc5b..5cfe2da841 100644 --- a/nav2_controller/plugins/simple_progress_checker.cpp +++ b/nav2_controller/plugins/simple_progress_checker.cpp @@ -29,8 +29,6 @@ using std::placeholders::_1; namespace nav2_controller { -static double pose_distance(const geometry_msgs::msg::Pose2D &, const geometry_msgs::msg::Pose2D &); - void SimpleProgressChecker::initialize( const rclcpp_lifecycle::LifecycleNode::WeakPtr & parent, const std::string & plugin_name) @@ -62,8 +60,8 @@ bool SimpleProgressChecker::check(geometry_msgs::msg::PoseStamped & current_pose geometry_msgs::msg::Pose2D current_pose2d; current_pose2d = nav_2d_utils::poseToPose2D(current_pose.pose); - if ((!baseline_pose_set_) || (is_robot_moved_enough(current_pose2d))) { - reset_baseline_pose(current_pose2d); + if ((!baseline_pose_set_) || (isRobotMovedEnough(current_pose2d))) { + resetBaselinePose(current_pose2d); return true; } return !((clock_->now() - baseline_time_) > time_allowance_); @@ -74,19 +72,19 @@ void SimpleProgressChecker::reset() baseline_pose_set_ = false; } -void SimpleProgressChecker::reset_baseline_pose(const geometry_msgs::msg::Pose2D & pose) +void SimpleProgressChecker::resetBaselinePose(const geometry_msgs::msg::Pose2D & pose) { baseline_pose_ = pose; baseline_time_ = clock_->now(); baseline_pose_set_ = true; } -bool SimpleProgressChecker::is_robot_moved_enough(const geometry_msgs::msg::Pose2D & pose) +bool SimpleProgressChecker::isRobotMovedEnough(const geometry_msgs::msg::Pose2D & pose) { return pose_distance(pose, baseline_pose_) > radius_; } -static double pose_distance( +double SimpleProgressChecker::pose_distance( const geometry_msgs::msg::Pose2D & pose1, const geometry_msgs::msg::Pose2D & pose2) { diff --git a/nav2_controller/plugins/test/CMakeLists.txt b/nav2_controller/plugins/test/CMakeLists.txt index d4f34d3918..0226676ce3 100644 --- a/nav2_controller/plugins/test/CMakeLists.txt +++ b/nav2_controller/plugins/test/CMakeLists.txt @@ -1,4 +1,4 @@ ament_add_gtest(pctest progress_checker.cpp) -target_link_libraries(pctest simple_progress_checker) +target_link_libraries(pctest simple_progress_checker pose_progress_checker) ament_add_gtest(gctest goal_checker.cpp) target_link_libraries(gctest simple_goal_checker stopped_goal_checker) diff --git a/nav2_controller/plugins/test/goal_checker.cpp b/nav2_controller/plugins/test/goal_checker.cpp index 387bfcc735..acfffcb9cd 100644 --- a/nav2_controller/plugins/test/goal_checker.cpp +++ b/nav2_controller/plugins/test/goal_checker.cpp @@ -145,6 +145,16 @@ TEST(VelocityIterator, goal_checker_reset) EXPECT_TRUE(true); } +TEST(VelocityIterator, stopped_goal_checker_reset) +{ + auto x = std::make_shared("stopped_goal_checker"); + + nav2_core::GoalChecker * sgc = new StoppedGoalChecker; + sgc->reset(); + delete sgc; + EXPECT_TRUE(true); +} + TEST(VelocityIterator, two_checks) { auto x = std::make_shared("goal_checker"); diff --git a/nav2_controller/plugins/test/progress_checker.cpp b/nav2_controller/plugins/test/progress_checker.cpp index 418ff1032a..0f42561447 100644 --- a/nav2_controller/plugins/test/progress_checker.cpp +++ b/nav2_controller/plugins/test/progress_checker.cpp @@ -37,10 +37,13 @@ #include "gtest/gtest.h" #include "nav2_controller/plugins/simple_progress_checker.hpp" +#include "nav2_controller/plugins/pose_progress_checker.hpp" #include "nav_2d_utils/conversions.hpp" #include "nav2_util/lifecycle_node.hpp" +#include "nav2_util/geometry_utils.hpp" using nav2_controller::SimpleProgressChecker; +using nav2_controller::PoseProgressChecker; class TestLifecycleNode : public nav2_util::LifecycleNode { @@ -83,8 +86,8 @@ class TestLifecycleNode : public nav2_util::LifecycleNode void checkMacro( nav2_core::ProgressChecker & pc, - double x0, double y0, - double x1, double y1, + double x0, double y0, double theta0, + double x1, double y1, double theta1, int delay, bool expected_result) { @@ -92,10 +95,12 @@ void checkMacro( geometry_msgs::msg::PoseStamped pose0, pose1; pose0.pose.position.x = x0; pose0.pose.position.y = y0; + pose0.pose.orientation = nav2_util::geometry_utils::orientationAroundZAxis(theta0); pose1.pose.position.x = x1; pose1.pose.position.y = y1; + pose1.pose.orientation = nav2_util::geometry_utils::orientationAroundZAxis(theta1); EXPECT_TRUE(pc.check(pose0)); - rclcpp::sleep_for(std::chrono::seconds(delay)); + rclcpp::sleep_for(std::chrono::milliseconds(delay)); if (expected_result) { EXPECT_TRUE(pc.check(pose1)); } else { @@ -119,12 +124,116 @@ TEST(SimpleProgressChecker, unit_tests) SimpleProgressChecker pc; pc.initialize(x, "nav2_controller"); - checkMacro(pc, 0, 0, 0, 0, 1, true); - checkMacro(pc, 0, 0, 1, 0, 1, true); - checkMacro(pc, 0, 0, 0, 1, 1, true); - checkMacro(pc, 0, 0, 1, 0, 11, true); - checkMacro(pc, 0, 0, 0, 1, 11, true); - checkMacro(pc, 0, 0, 0, 0, 11, false); + + double time_allowance = 0.5; + int half_time_allowance_ms = static_cast(time_allowance * 0.5 * 1000); + int twice_time_allowance_ms = static_cast(time_allowance * 2.0 * 1000); + + auto rec_param = std::make_shared( + x->get_node_base_interface(), x->get_node_topics_interface(), + x->get_node_graph_interface(), + x->get_node_services_interface()); + + auto results = rec_param->set_parameters_atomically( + {rclcpp::Parameter("nav2_controller.movement_time_allowance", time_allowance)}); + + rclcpp::spin_until_future_complete( + x->get_node_base_interface(), + results); + + EXPECT_EQ( + x->get_parameter("nav2_controller.movement_time_allowance").as_double(), + time_allowance); + + // BELOW time allowance (set to time_allowance) + // no movement + checkMacro(pc, 0, 0, 0, 0, 0, 0, half_time_allowance_ms, true); + // translation below required_movement_radius (default 0.5) + checkMacro(pc, 0, 0, 0, 0.25, 0, 0, half_time_allowance_ms, true); + checkMacro(pc, 0, 0, 0, 0, 0.25, 0, half_time_allowance_ms, true); + // translation above required_movement_radius (default 0.5) + checkMacro(pc, 0, 0, 0, 1, 0, 0, half_time_allowance_ms, true); + checkMacro(pc, 0, 0, 0, 0, 1, 0, half_time_allowance_ms, true); + + // ABOVE time allowance (set to time_allowance) + // no movement + checkMacro(pc, 0, 0, 0, 0, 0, 0, twice_time_allowance_ms, false); + // translation below required_movement_radius (default 0.5) + checkMacro(pc, 0, 0, 0, 0.25, 0, 0, twice_time_allowance_ms, false); + checkMacro(pc, 0, 0, 0, 0, 0.25, 0, twice_time_allowance_ms, false); + // translation above required_movement_radius (default 0.5) + checkMacro(pc, 0, 0, 0, 1, 0, 0, twice_time_allowance_ms, true); + checkMacro(pc, 0, 0, 0, 0, 1, 0, twice_time_allowance_ms, true); +} + +TEST(PoseProgressChecker, pose_progress_checker_reset) +{ + auto x = std::make_shared("pose_progress_checker"); + + PoseProgressChecker * rpc = new PoseProgressChecker; + rpc->reset(); + delete rpc; + EXPECT_TRUE(true); +} + +TEST(PoseProgressChecker, unit_tests) +{ + auto x = std::make_shared("pose_progress_checker"); + + PoseProgressChecker rpc; + rpc.initialize(x, "nav2_controller"); + + double time_allowance = 0.5; + int half_time_allowance_ms = static_cast(time_allowance * 0.5 * 1000); + int twice_time_allowance_ms = static_cast(time_allowance * 2.0 * 1000); + + auto rec_param = std::make_shared( + x->get_node_base_interface(), x->get_node_topics_interface(), + x->get_node_graph_interface(), + x->get_node_services_interface()); + + auto results = rec_param->set_parameters_atomically( + {rclcpp::Parameter("nav2_controller.movement_time_allowance", time_allowance)}); + + rclcpp::spin_until_future_complete( + x->get_node_base_interface(), + results); + + EXPECT_EQ( + x->get_parameter("nav2_controller.movement_time_allowance").as_double(), + time_allowance); + + // BELOW time allowance (set to time_allowance) + // no movement + checkMacro(rpc, 0, 0, 0, 0, 0, 0, half_time_allowance_ms, true); + // translation below required_movement_radius (default 0.5) + checkMacro(rpc, 0, 0, 0, 0.25, 0, 0, half_time_allowance_ms, true); + checkMacro(rpc, 0, 0, 0, 0, 0.25, 0, half_time_allowance_ms, true); + // rotation below required_movement_angle (default 0.5) + checkMacro(rpc, 0, 0, 0, 0, 0, 0.25, half_time_allowance_ms, true); + checkMacro(rpc, 0, 0, 0, 0, 0, -0.25, half_time_allowance_ms, true); + // translation above required_movement_radius (default 0.5) + checkMacro(rpc, 0, 0, 0, 1, 0, 0, half_time_allowance_ms, true); + checkMacro(rpc, 0, 0, 0, 0, 1, 0, half_time_allowance_ms, true); + // rotation above required_movement_angle (default 0.5) + checkMacro(rpc, 0, 0, 0, 0, 0, 1, half_time_allowance_ms, true); + checkMacro(rpc, 0, 0, 0, 0, 0, -1, half_time_allowance_ms, true); + + // ABOVE time allowance (set to time_allowance) + // no movement + checkMacro(rpc, 0, 0, 0, 0, 0, 0, twice_time_allowance_ms, false); + // translation below required_movement_radius (default 0.5) + checkMacro(rpc, 0, 0, 0, 0.25, 0, 0, twice_time_allowance_ms, false); + checkMacro(rpc, 0, 0, 0, 0, 0.25, 0, twice_time_allowance_ms, false); + // rotation below required_movement_angle (default 0.5) + checkMacro(rpc, 0, 0, 0, 0, 0, 0.25, twice_time_allowance_ms, false); + checkMacro(rpc, 0, 0, 0, 0, 0, -0.25, twice_time_allowance_ms, false); + // translation above required_movement_radius (default 0.5) + checkMacro(rpc, 0, 0, 0, 1, 0, 0, twice_time_allowance_ms, true); + checkMacro(rpc, 0, 0, 0, 0, 1, 0, twice_time_allowance_ms, true); + // rotation above required_movement_angle (default 0.5) + checkMacro(rpc, 0, 0, 0, 0, 0, 1, twice_time_allowance_ms, true); + checkMacro(rpc, 0, 0, 0, 0, 0, -1, twice_time_allowance_ms, true); } int main(int argc, char ** argv) diff --git a/nav2_controller/src/controller_server.cpp b/nav2_controller/src/controller_server.cpp index 2a52e296da..ff86f64255 100644 --- a/nav2_controller/src/controller_server.cpp +++ b/nav2_controller/src/controller_server.cpp @@ -123,6 +123,8 @@ ControllerServer::on_configure(const rclcpp_lifecycle::State & /*state*/) get_parameter("failure_tolerance", failure_tolerance_); costmap_ros_->configure(); + // Launch a thread to run the costmap node + costmap_thread_ = std::make_unique(costmap_ros_); try { progress_checker_type_ = nav2_util::get_plugin_type_param(node, progress_checker_id_); diff --git a/nav2_core/package.xml b/nav2_core/package.xml index 0016b0d840..fa990f04dc 100644 --- a/nav2_core/package.xml +++ b/nav2_core/package.xml @@ -2,7 +2,7 @@ nav2_core - 1.1.6 + 1.1.7 A set of headers for plugins core to the Nav2 stack Steve Macenski Carl Delsey diff --git a/nav2_costmap_2d/CMakeLists.txt b/nav2_costmap_2d/CMakeLists.txt index 683cb04fdd..f039cf5d42 100644 --- a/nav2_costmap_2d/CMakeLists.txt +++ b/nav2_costmap_2d/CMakeLists.txt @@ -87,6 +87,7 @@ add_library(layers SHARED src/observation_buffer.cpp plugins/voxel_layer.cpp plugins/range_sensor_layer.cpp + plugins/denoise_layer.cpp ) ament_target_dependencies(layers ${dependencies} diff --git a/nav2_costmap_2d/costmap_plugins.xml b/nav2_costmap_2d/costmap_plugins.xml index e5867d5108..6748cd5fca 100644 --- a/nav2_costmap_2d/costmap_plugins.xml +++ b/nav2_costmap_2d/costmap_plugins.xml @@ -15,6 +15,9 @@ A range-sensor (sonar, IR) based obstacle layer for costmap_2d + + Filters noise-induced freestanding obstacles or small obstacles groups + diff --git a/nav2_costmap_2d/include/nav2_costmap_2d/costmap_filters/costmap_filter.hpp b/nav2_costmap_2d/include/nav2_costmap_2d/costmap_filters/costmap_filter.hpp index 35f53c6b96..49aeab21e3 100644 --- a/nav2_costmap_2d/include/nav2_costmap_2d/costmap_filters/costmap_filter.hpp +++ b/nav2_costmap_2d/include/nav2_costmap_2d/costmap_filters/costmap_filter.hpp @@ -2,6 +2,7 @@ * * Software License Agreement (BSD License) * + * Copyright (c) 2008, 2013, Willow Garage, Inc. * Copyright (c) 2020 Samsung Research Russia * All rights reserved. * @@ -32,7 +33,9 @@ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * - * Author: Alexey Merzlyakov + * Author: Eitan Marder-Eppstein + * David V. Lu!! + * Alexey Merzlyakov *********************************************************************/ #ifndef NAV2_COSTMAP_2D__COSTMAP_FILTERS__COSTMAP_FILTER_HPP_ diff --git a/nav2_costmap_2d/include/nav2_costmap_2d/denoise/image.hpp b/nav2_costmap_2d/include/nav2_costmap_2d/denoise/image.hpp new file mode 100644 index 0000000000..db7ae8fce8 --- /dev/null +++ b/nav2_costmap_2d/include/nav2_costmap_2d/denoise/image.hpp @@ -0,0 +1,207 @@ +// Copyright (c) 2023 Andrey Ryzhikov +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef NAV2_COSTMAP_2D__DENOISE__IMAGE_HPP_ +#define NAV2_COSTMAP_2D__DENOISE__IMAGE_HPP_ + +#include +#include + +namespace nav2_costmap_2d +{ + +/** + * @brief Image with pixels of type T + * Сan own data, be a wrapper over some memory buffer, or refer to a fragment of another image + * Pixels of one row are stored continuity. But rows continuity is not guaranteed. + * The distance (number of elements of type T) from row(i) to row(i + 1) is equal to step() + * @tparam T type of pixel + */ +template +class Image +{ +public: + /// @brief Create empty image + Image() = default; + + /** + * @brief Create image referencing to a third-party buffer + * @param rows number of image rows + * @param columns number of image columns + * @param data existing memory buffer with size at least rows * columns + * @param step offset from row(i) to row(i + 1) in memory buffer (number of elements of type T). + * offset = columns if rows are stored continuity + */ + Image(size_t rows, size_t columns, T * data, size_t step); + + /** + * @brief Create image referencing to the other + * Share image data between new and old object. + * Changing data in a new object will affect the given one and vice versa + */ + Image(Image & other); + + /** + * @brief Create image from the other (move constructor) + */ + Image(Image && other) noexcept; + + /// @return number of image rows + size_t rows() const {return rows_;} + + /// @return number of image columns + size_t columns() const {return columns_;} + + /// @return true if image empty + bool empty() const {return rows_ == 0 || columns_ == 0;} + + /// @return offset (number of elements of type T) from row(i) to row(i + 1) + size_t step() const {return step_;} + + /** + * @return pointer to first pixel of row + * @warning If row >= rows(), the behavior is undefined + */ + T * row(size_t row); + + /// @overload + const T * row(size_t row) const; + + /** + * @brief Read (and modify, if need) each pixel sequentially + * @tparam Functor function object. + * Signature should be equivalent to the following: + * void fn(T& pixel) or void fn(const T& pixel) + * @param fn a function that will be applied to each pixel in the image. Can modify image data + */ + template + void forEach(Functor && fn); + + /** + * @brief Read each pixel sequentially + * @tparam Functor function object. + * Signature should be equivalent to the following: + * void fn(const T& pixel) + * @param fn a function that will be applied to each pixel in the image + */ + template + void forEach(Functor && fn) const; + /** + * @brief Convert each pixel to corresponding pixel of target using a custom function + * + * The source and target must be the same size. + * For calculation of new target value the operation can use source value and + * an optionally current target value. + * This function call operation(this[i, j], target[i, j]) for each pixel + * where target[i, j] is mutable + * @tparam TargetElement type of target pixel + * @tparam Converter function object. + * Signature should be equivalent to the following: + * void fn(const T& src, TargetElement& trg) + * @param target output image with TargetElement-type pixels + * @param operation the binary operation op is applied to pairs of pixels: + * first (const) from source and second (mutable) from target + * @throw std::logic_error if the source and target of different sizes + */ + template + void convert(Image & target, Converter && converter) const; + +private: + T * data_start_{}; + size_t rows_{}; + size_t columns_{}; + size_t step_{}; +}; + +template +Image::Image(size_t rows, size_t columns, T * data, size_t step) +: rows_{rows}, columns_{columns}, step_{step} +{ + data_start_ = data; +} + +template +Image::Image(Image & other) +: data_start_{other.data_start_}, + rows_{other.rows_}, columns_{other.columns_}, step_{other.step_} {} + +template +Image::Image(Image && other) noexcept +: data_start_{other.data_start_}, + rows_{other.rows_}, columns_{other.columns_}, step_{other.step_} {} + +template +T * Image::row(size_t row) +{ + return const_cast( static_cast &>(*this).row(row) ); +} + +template +const T * Image::row(size_t row) const +{ + return data_start_ + row * step_; +} + +template +template +void Image::forEach(Functor && fn) +{ + static_cast &>(*this).forEach( + [&](const T & pixel) { + fn(const_cast(pixel)); + }); +} + +template +template +void Image::forEach(Functor && fn) const +{ + const T * rowPtr = row(0); + + for (size_t row = 0; row < rows(); ++row) { + const T * rowEnd = rowPtr + columns(); + + for (const T * pixel = rowPtr; pixel != rowEnd; ++pixel) { + fn(*pixel); + } + rowPtr += step(); + } +} + +template +template +void Image::convert(Image & target, Converter && converter) const +{ + if (rows() != target.rows() || columns() != target.columns()) { + throw std::logic_error("Image::convert. The source and target images size are different"); + } + const T * source_row = row(0); + TargetElement * target_row = target.row(0); + + for (size_t row = 0; row < rows(); ++row) { + const T * rowInEnd = source_row + columns(); + const T * src = source_row; + TargetElement * trg = target_row; + + for (; src != rowInEnd; ++src, ++trg) { + converter(*src, *trg); + } + source_row += step(); + target_row += target.step(); + } +} + +} // namespace nav2_costmap_2d + +#endif // NAV2_COSTMAP_2D__DENOISE__IMAGE_HPP_ diff --git a/nav2_costmap_2d/include/nav2_costmap_2d/denoise/image_processing.hpp b/nav2_costmap_2d/include/nav2_costmap_2d/denoise/image_processing.hpp new file mode 100644 index 0000000000..ee79894847 --- /dev/null +++ b/nav2_costmap_2d/include/nav2_costmap_2d/denoise/image_processing.hpp @@ -0,0 +1,1051 @@ +// Copyright (c) 2023 Andrey Ryzhikov +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef NAV2_COSTMAP_2D__DENOISE__IMAGE_PROCESSING_HPP_ +#define NAV2_COSTMAP_2D__DENOISE__IMAGE_PROCESSING_HPP_ + +#include "image.hpp" +#include +#include +#include +#include +#include + +namespace nav2_costmap_2d +{ + +/** + * @enum nav2_costmap_2d::ConnectivityType + * @brief Describes the type of pixel connectivity (is the way in which + * pixels in image relate to their neighbors) + */ +enum class ConnectivityType : int +{ + /// neighbors pixels are connected horizontally and vertically + Way4 = 4, + /// neighbors pixels are connected horizontally, vertically and diagonally + Way8 = 8 +}; + +/** + * @brief A memory buffer that can grow to an upper-bounded capacity + */ +class MemoryBuffer +{ +public: + /// @brief Free memory allocated for the buffer + inline ~MemoryBuffer() {reset();} + /** + * @brief Return a pointer to an uninitialized array of count elements + * Delete the old block of memory and allocates a new one if the size of the old is too small. + * The returned pointer is valid until the next call to get() or destructor. + * @tparam T type of element + * @param count number of elements + * @throw std::bad_alloc or any other exception thrown by allocator + */ + template + T * get(std::size_t count); + +private: + inline void reset(); + inline void allocate(size_t bytes); + +private: + void * data_{}; + size_t size_{}; +}; + +// forward declarations +namespace imgproc_impl +{ +template +class EquivalenceLabelTrees; + +template +void morphologyOperation( + const Image & input, Image & output, + const Image & shape, AggregateFn aggregate); + +using ShapeBuffer3x3 = std::array; +inline Image createShape(ShapeBuffer3x3 & buffer, ConnectivityType connectivity); +} // namespace imgproc_impl + +/** + * @brief Perform morphological dilation + * @tparam Max function object + * @param input input image + * @param output output image + * @param connectivity selector for selecting structuring element (Way4-> cross, Way8-> rect) + * @param max_function takes as input std::initializer_list with three elements. + * Returns the greatest value in list + */ +template +inline void dilate( + const Image & input, Image & output, + ConnectivityType connectivity, Max && max_function) +{ + using namespace imgproc_impl; + ShapeBuffer3x3 shape_buffer; + Image shape = createShape(shape_buffer, connectivity); + morphologyOperation(input, output, shape, max_function); +} + +/** +* @brief Compute the connected components labeled image of binary image +* Implements the SAUF algorithm +* (Two Strategies to Speed up Connected Component Labeling Algorithms +* Kesheng Wu, Ekow Otoo, Kenji Suzuki). +* @tparam connectivity pixels connectivity type +* @tparam Label integer type of label +* @tparam IsBg functor with signature bool (uint8_t) +* @param image input image +* @param buffer memory block that will be used to store the result (labeled image) +* and the internal buffer for labels trees +* @param label_trees union-find data structure +* @param is_background returns true if the passed pixel value is background +* @throw LabelOverflow if all possible values of the Label type are used and +* it is impossible to create a new unique +* @return pair(labeled image, total number of labels) +* Labeled image has the same size as image. Label 0 represents the background label, +* labels [1, - 1] - separate components. +* Total number of labels == 0 for empty image. +* In other cases, label 0 is always counted, +* even if there is no background in the image. +* For example, for an image of one background pixel, the total number of labels == 2. +* Two labels (0, 1) have been counted, although label 0 is not used) +*/ +template +std::pair, Label> connectedComponents( + const Image & image, MemoryBuffer & buffer, + imgproc_impl::EquivalenceLabelTrees