diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 84688714..206c4398 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -16,7 +16,8 @@ // "ROS_DOMAIN_ID": "${localEnv:ROS_DOMAIN_ID}" }, "initializeCommand": "git config --global --add safe.directory '*' && git reset --soft && git submodule update --init --recursive && git config --global submodule.recurse true && git pull || true", - "postCreateCommand": "echo 'source /opt/ros/humble/setup.bash' >> ~/.bashrc && echo 'source /workspaces/Lunabotics/install/setup.bash' >> ~/.bashrc && sudo apt-get update && sudo apt install git-lfs", + "postCreateCommand": "sudo rm -f /etc/apt/sources.list.d/ros2.list && sudo rm -f /etc/apt/sources.list.d/ros2-latest.list && sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg && echo \"deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu jammy main\" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null && sudo apt-get update && sudo apt install -y git-lfs && echo 'source /opt/ros/humble/setup.bash' >> ~/.bashrc && echo 'source /workspaces/Lunabotics/install/setup.bash' >> ~/.bashrc && sudo chown -R admin:admin /workspaces/Lunabotics", + "postStartCommand": "rosdep update && rosdep install -i --from-path src --rosdistro $ROS_DISTRO -y --skip-keys 'nvblox negotiated draco_point_cloud_transport point_cloud_transport_plugins ffmpeg_encoder_decoder ffmpeg_image_transport isaac_ros_peoplesemseg_models_install isaac_ros_h264_decoder isaac_ros_unet isaac_ros_triton isaac_ros_dnn_image_encoder nova_carter_navigation'", // "mounts": [ // "type=bind,source=/tmp/.X11-unix,target=/tmp/.X11-unix", @@ -48,35 +49,20 @@ "files.autoSave": "afterDelay", "editor.formatOnSave": true, "[python]": { - "editor.formatOnPaste": false, - "editor.formatOnSaveMode": "file" - }, - "[workbench.view.extension]": { - "ms-python.python": false + "editor.defaultFormatter": "ms-python.black-formatter", + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.fixAll": "explicit" + } }, "black-formatter.args": [ "--line-length", "120" ], - "[C_Cpp]": { - "default.cppStandard": "c++17", - "default.cStandard": "c99" + "[c++]": { + "editor.defaultFormatter": "ms-vscode.cpptools" }, - "ruff.lineLength": 99, - "ruff.lint.enable": true, - "ruff.lint.select": [ - "A", - "C", - "CNL", - "D", - "E", - "F", - "I", - "Q", - "W" - ], - "ruff.nativeServer": true, - "C_Cpp.clang_format_fallbackStyle": "Google", + "C_Cpp.clang_format_fallbackStyle": "{ ColumnLimit: 120 }", "diffEditor.ignoreTrimWhitespace": false, // Make bash the default shell in the container terminal "terminal.integrated.defaultProfile.linux": "bash" diff --git a/.gitmodules b/.gitmodules index ccf4d9d4..1b025f7b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -30,3 +30,9 @@ path = src/isaac_ros/isaac_ros_nvblox url = https://github.com/GOFIRST-Robotics/isaac_ros_nvblox.git branch = main +[submodule "src/behaviortree_ros2"] + path = src/behaviortree_ros2 + url = https://github.com/BehaviorTree/BehaviorTree.ROS2.git +[submodule "src/behaviortree_cpp"] + path = src/behaviortree_cpp + url = https://github.com/BehaviorTree/BehaviorTree.CPP.git diff --git a/.vscode/tasks.json b/.vscode/tasks.json index d70e11dc..a0dc610c 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -3,7 +3,7 @@ "tasks": [ { "label": "Build All Task", - "type": "colcon", + "command": "colcon", "args": [ "build", "--symlink-install" @@ -13,7 +13,7 @@ }, { "label": "Build No GPU Task", - "type": "colcon", + "command": "colcon", "args": [ "build", "--symlink-install", @@ -24,15 +24,6 @@ ], "problemMatcher": [], "group": "build" - }, - { - "label": "Test All", - "type": "colcon", - "args": [ - "test" - ], - "problemMatcher": [], - "group": "test" - }, + } ] } \ No newline at end of file diff --git a/Back + Sides.perspective b/Back + Sides.perspective new file mode 100644 index 00000000..4b1ff887 --- /dev/null +++ b/Back + Sides.perspective @@ -0,0 +1,253 @@ +{ + "keys": {}, + "groups": { + "mainwindow": { + "keys": { + "geometry": { + "repr(QByteArray.hex)": "QtCore.QByteArray(b'01d9d0cb00030000000000460000001b0000077f000004af00000046000000400000077f000004af0000000000000000078000000046000000400000077f000004af')", + "type": "repr(QByteArray.hex)", + "pretty-print": " F F @ F @ " + }, + "state": { + "repr(QByteArray.hex)": "QtCore.QByteArray(b'000000ff00000000fd00000001000000030000073a00000446fc0100000003fb0000005a007200710074005f0069006d006100670065005f0076006900650077005f005f0049006d0061006700650056006900650077005f005f0033005f005f0049006d0061006700650056006900650077005700690064006700650074010000000000000268000000d300fffffffb0000005a007200710074005f0069006d006100670065005f0076006900650077005f005f0049006d0061006700650056006900650077005f005f0031005f005f0049006d0061006700650056006900650077005700690064006700650074010000026e00000262000000be00fffffffb0000005a007200710074005f0069006d006100670065005f0076006900650077005f005f0049006d0061006700650056006900650077005f005f0032005f005f0049006d006100670065005600690065007700570069006400670065007401000004d600000264000000d300ffffff0000073a0000000000000004000000040000000800000008fc00000001000000030000000100000036004d0069006e0069006d0069007a006500640044006f0063006b00570069006400670065007400730054006f006f006c0062006100720000000000ffffffff0000000000000000')", + "type": "repr(QByteArray.hex)", + "pretty-print": " Zrqt_image_view__ImageView__3__ImageViewWidget Zrqt_image_view__ImageView__1__ImageViewWidget Zrqt_image_view__ImageView__2__ImageViewWidget 6MinimizedDockWidgetsToolbar " + } + }, + "groups": { + "toolbar_areas": { + "keys": { + "MinimizedDockWidgetsToolbar": { + "repr": "8", + "type": "repr" + } + }, + "groups": {} + } + } + }, + "pluginmanager": { + "keys": { + "running-plugins": { + "repr": "{'rqt_image_view/ImageView': [1, 2, 3]}", + "type": "repr" + } + }, + "groups": { + "plugin__rqt_image_view__ImageView__1": { + "keys": {}, + "groups": { + "dock_widget__ImageViewWidget": { + "keys": { + "dock_widget_title": { + "repr": "'Image View'", + "type": "repr" + }, + "dockable": { + "repr": "True", + "type": "repr" + }, + "parent": { + "repr": "None", + "type": "repr" + } + }, + "groups": {} + }, + "plugin": { + "keys": { + "color_scheme": { + "repr": "-1", + "type": "repr" + }, + "dynamic_range": { + "repr": "False", + "type": "repr" + }, + "max_range": { + "repr": "10.0", + "type": "repr" + }, + "mouse_pub_topic": { + "repr": "'/back/image_raw/compressed_mouse_left'", + "type": "repr" + }, + "num_gridlines": { + "repr": "0", + "type": "repr" + }, + "publish_click_location": { + "repr": "False", + "type": "repr" + }, + "rotate": { + "repr": "3", + "type": "repr" + }, + "smooth_image": { + "repr": "False", + "type": "repr" + }, + "toolbar_hidden": { + "repr": "False", + "type": "repr" + }, + "topic": { + "repr": "'/back/image_raw/compressed'", + "type": "repr" + }, + "zoom1": { + "repr": "False", + "type": "repr" + } + }, + "groups": {} + } + } + }, + "plugin__rqt_image_view__ImageView__2": { + "keys": {}, + "groups": { + "dock_widget__ImageViewWidget": { + "keys": { + "dock_widget_title": { + "repr": "'Image View (2)'", + "type": "repr" + }, + "dockable": { + "repr": "True", + "type": "repr" + }, + "parent": { + "repr": "None", + "type": "repr" + } + }, + "groups": {} + }, + "plugin": { + "keys": { + "color_scheme": { + "repr": "-1", + "type": "repr" + }, + "dynamic_range": { + "repr": "False", + "type": "repr" + }, + "max_range": { + "repr": "10.0", + "type": "repr" + }, + "mouse_pub_topic": { + "repr": "'/left/image_raw/compressed_mouse_left'", + "type": "repr" + }, + "num_gridlines": { + "repr": "0", + "type": "repr" + }, + "publish_click_location": { + "repr": "False", + "type": "repr" + }, + "rotate": { + "repr": "0", + "type": "repr" + }, + "smooth_image": { + "repr": "False", + "type": "repr" + }, + "toolbar_hidden": { + "repr": "False", + "type": "repr" + }, + "topic": { + "repr": "'/left/image_raw/compressed'", + "type": "repr" + }, + "zoom1": { + "repr": "False", + "type": "repr" + } + }, + "groups": {} + } + } + }, + "plugin__rqt_image_view__ImageView__3": { + "keys": {}, + "groups": { + "dock_widget__ImageViewWidget": { + "keys": { + "dock_widget_title": { + "repr": "'Image View (3)'", + "type": "repr" + }, + "dockable": { + "repr": "True", + "type": "repr" + }, + "parent": { + "repr": "None", + "type": "repr" + } + }, + "groups": {} + }, + "plugin": { + "keys": { + "color_scheme": { + "repr": "-1", + "type": "repr" + }, + "dynamic_range": { + "repr": "False", + "type": "repr" + }, + "max_range": { + "repr": "10.0", + "type": "repr" + }, + "mouse_pub_topic": { + "repr": "'/right/image_raw/compressed_mouse_left'", + "type": "repr" + }, + "num_gridlines": { + "repr": "0", + "type": "repr" + }, + "publish_click_location": { + "repr": "False", + "type": "repr" + }, + "rotate": { + "repr": "0", + "type": "repr" + }, + "smooth_image": { + "repr": "False", + "type": "repr" + }, + "toolbar_hidden": { + "repr": "False", + "type": "repr" + }, + "topic": { + "repr": "'/right/image_raw/compressed'", + "type": "repr" + }, + "zoom1": { + "repr": "False", + "type": "repr" + } + }, + "groups": {} + } + } + } + } + } + } +} \ No newline at end of file diff --git a/Back.perspective b/Back.perspective new file mode 100644 index 00000000..7ea9a964 --- /dev/null +++ b/Back.perspective @@ -0,0 +1,182 @@ +{ + "keys": {}, + "groups": { + "mainwindow": { + "keys": { + "geometry": { + "repr(QByteArray.hex)": "QtCore.QByteArray(b'01d9d0cb00030000000000460000001b0000077f000004af00000046000000400000077f000004af0000000000000000078000000046000000400000077f000004af')", + "type": "repr(QByteArray.hex)", + "pretty-print": " F F @ F @ " + }, + "state": { + "repr(QByteArray.hex)": "QtCore.QByteArray(b'000000ff00000000fd00000001000000030000073a00000446fc0100000002fb0000005a007200710074005f0069006d006100670065005f0076006900650077005f005f0049006d0061006700650056006900650077005f005f0031005f005f0049006d006100670065005600690065007700570069006400670065007401000000000000073a000000be00fffffffb0000005a007200710074005f0069006d006100670065005f0076006900650077005f005f0049006d0061006700650056006900650077005f005f0032005f005f0049006d0061006700650056006900650077005700690064006700650074010000039f0000039b00000000000000000000073a0000000000000004000000040000000800000008fc00000001000000030000000100000036004d0069006e0069006d0069007a006500640044006f0063006b00570069006400670065007400730054006f006f006c0062006100720000000000ffffffff0000000000000000')", + "type": "repr(QByteArray.hex)", + "pretty-print": " Zrqt_image_view__ImageView__1__ImageViewWidget Zrqt_image_view__ImageView__2__ImageViewWidget 6MinimizedDockWidgetsToolbar " + } + }, + "groups": { + "toolbar_areas": { + "keys": { + "MinimizedDockWidgetsToolbar": { + "repr": "8", + "type": "repr" + } + }, + "groups": {} + } + } + }, + "pluginmanager": { + "keys": { + "running-plugins": { + "repr": "{'rqt_image_view/ImageView': [1]}", + "type": "repr" + } + }, + "groups": { + "plugin__rqt_image_view__ImageView__1": { + "keys": {}, + "groups": { + "dock_widget__ImageViewWidget": { + "keys": { + "dock_widget_title": { + "repr": "'Image View'", + "type": "repr" + }, + "dockable": { + "repr": "True", + "type": "repr" + }, + "parent": { + "repr": "None", + "type": "repr" + } + }, + "groups": {} + }, + "plugin": { + "keys": { + "color_scheme": { + "repr": "-1", + "type": "repr" + }, + "dynamic_range": { + "repr": "False", + "type": "repr" + }, + "max_range": { + "repr": "10.0", + "type": "repr" + }, + "mouse_pub_topic": { + "repr": "'/back/image_raw/compressed_mouse_left'", + "type": "repr" + }, + "num_gridlines": { + "repr": "0", + "type": "repr" + }, + "publish_click_location": { + "repr": "False", + "type": "repr" + }, + "rotate": { + "repr": "3", + "type": "repr" + }, + "smooth_image": { + "repr": "False", + "type": "repr" + }, + "toolbar_hidden": { + "repr": "False", + "type": "repr" + }, + "topic": { + "repr": "'/back/image_raw/compressed'", + "type": "repr" + }, + "zoom1": { + "repr": "False", + "type": "repr" + } + }, + "groups": {} + } + } + }, + "plugin__rqt_image_view__ImageView__2": { + "keys": {}, + "groups": { + "dock_widget__ImageViewWidget": { + "keys": { + "dock_widget_title": { + "repr": "'Image View (2)'", + "type": "repr" + }, + "dockable": { + "repr": "True", + "type": "repr" + }, + "parent": { + "repr": "None", + "type": "repr" + } + }, + "groups": {} + }, + "plugin": { + "keys": { + "color_scheme": { + "repr": "-1", + "type": "repr" + }, + "dynamic_range": { + "repr": "False", + "type": "repr" + }, + "max_range": { + "repr": "10.0", + "type": "repr" + }, + "mouse_pub_topic": { + "repr": "'/dumper/image_raw_mouse_left'", + "type": "repr" + }, + "num_gridlines": { + "repr": "0", + "type": "repr" + }, + "publish_click_location": { + "repr": "False", + "type": "repr" + }, + "rotate": { + "repr": "0", + "type": "repr" + }, + "smooth_image": { + "repr": "False", + "type": "repr" + }, + "toolbar_hidden": { + "repr": "False", + "type": "repr" + }, + "topic": { + "repr": "'/dumper/image_raw'", + "type": "repr" + }, + "zoom1": { + "repr": "False", + "type": "repr" + } + }, + "groups": {} + } + } + } + } + } + } +} \ No newline at end of file diff --git a/Front + Sides.perspective b/Front + Sides.perspective new file mode 100644 index 00000000..cae90381 --- /dev/null +++ b/Front + Sides.perspective @@ -0,0 +1,253 @@ +{ + "keys": {}, + "groups": { + "mainwindow": { + "keys": { + "geometry": { + "repr(QByteArray.hex)": "QtCore.QByteArray(b'01d9d0cb00030000000000460000001b0000077f000004af00000046000000400000077f000004af0000000000000000078000000046000000400000077f000004af')", + "type": "repr(QByteArray.hex)", + "pretty-print": " F F @ F @ " + }, + "state": { + "repr(QByteArray.hex)": "QtCore.QByteArray(b'000000ff00000000fd00000001000000030000073a00000446fc0100000003fb0000005a007200710074005f0069006d006100670065005f0076006900650077005f005f0049006d0061006700650056006900650077005f005f0032005f005f0049006d00610067006500560069006500770057006900640067006500740100000000000001e1000000d300fffffffb0000005a007200710074005f0069006d006100670065005f0076006900650077005f005f0049006d0061006700650056006900650077005f005f0031005f005f0049006d006100670065005600690065007700570069006400670065007401000001e70000035d000000be00fffffffb0000005a007200710074005f0069006d006100670065005f0076006900650077005f005f0049006d0061006700650056006900650077005f005f0033005f005f0049006d0061006700650056006900650077005700690064006700650074010000054a000001f0000000d300ffffff0000073a0000000000000004000000040000000800000008fc00000001000000030000000100000036004d0069006e0069006d0069007a006500640044006f0063006b00570069006400670065007400730054006f006f006c0062006100720000000000ffffffff0000000000000000')", + "type": "repr(QByteArray.hex)", + "pretty-print": " Zrqt_image_view__ImageView__2__ImageViewWidget Zrqt_image_view__ImageView__1__ImageViewWidget Zrqt_image_view__ImageView__3__ImageViewWidget 6MinimizedDockWidgetsToolbar " + } + }, + "groups": { + "toolbar_areas": { + "keys": { + "MinimizedDockWidgetsToolbar": { + "repr": "8", + "type": "repr" + } + }, + "groups": {} + } + } + }, + "pluginmanager": { + "keys": { + "running-plugins": { + "repr": "{'rqt_image_view/ImageView': [1, 2, 3]}", + "type": "repr" + } + }, + "groups": { + "plugin__rqt_image_view__ImageView__1": { + "keys": {}, + "groups": { + "dock_widget__ImageViewWidget": { + "keys": { + "dock_widget_title": { + "repr": "'Image View'", + "type": "repr" + }, + "dockable": { + "repr": "True", + "type": "repr" + }, + "parent": { + "repr": "None", + "type": "repr" + } + }, + "groups": {} + }, + "plugin": { + "keys": { + "color_scheme": { + "repr": "-1", + "type": "repr" + }, + "dynamic_range": { + "repr": "False", + "type": "repr" + }, + "max_range": { + "repr": "10.0", + "type": "repr" + }, + "mouse_pub_topic": { + "repr": "'/zed2i/zed_node_zed2i/rgb_raw/image_raw_color/compressed_mouse_left'", + "type": "repr" + }, + "num_gridlines": { + "repr": "0", + "type": "repr" + }, + "publish_click_location": { + "repr": "False", + "type": "repr" + }, + "rotate": { + "repr": "0", + "type": "repr" + }, + "smooth_image": { + "repr": "False", + "type": "repr" + }, + "toolbar_hidden": { + "repr": "False", + "type": "repr" + }, + "topic": { + "repr": "'/zed2i/zed_node_zed2i/rgb_raw/image_raw_color/compressed'", + "type": "repr" + }, + "zoom1": { + "repr": "False", + "type": "repr" + } + }, + "groups": {} + } + } + }, + "plugin__rqt_image_view__ImageView__2": { + "keys": {}, + "groups": { + "dock_widget__ImageViewWidget": { + "keys": { + "dock_widget_title": { + "repr": "'Image View (2)'", + "type": "repr" + }, + "dockable": { + "repr": "True", + "type": "repr" + }, + "parent": { + "repr": "None", + "type": "repr" + } + }, + "groups": {} + }, + "plugin": { + "keys": { + "color_scheme": { + "repr": "-1", + "type": "repr" + }, + "dynamic_range": { + "repr": "False", + "type": "repr" + }, + "max_range": { + "repr": "10.0", + "type": "repr" + }, + "mouse_pub_topic": { + "repr": "'/left/image_raw/compressed_mouse_left'", + "type": "repr" + }, + "num_gridlines": { + "repr": "0", + "type": "repr" + }, + "publish_click_location": { + "repr": "False", + "type": "repr" + }, + "rotate": { + "repr": "0", + "type": "repr" + }, + "smooth_image": { + "repr": "False", + "type": "repr" + }, + "toolbar_hidden": { + "repr": "False", + "type": "repr" + }, + "topic": { + "repr": "'/left/image_raw/compressed'", + "type": "repr" + }, + "zoom1": { + "repr": "False", + "type": "repr" + } + }, + "groups": {} + } + } + }, + "plugin__rqt_image_view__ImageView__3": { + "keys": {}, + "groups": { + "dock_widget__ImageViewWidget": { + "keys": { + "dock_widget_title": { + "repr": "'Image View (3)'", + "type": "repr" + }, + "dockable": { + "repr": "True", + "type": "repr" + }, + "parent": { + "repr": "None", + "type": "repr" + } + }, + "groups": {} + }, + "plugin": { + "keys": { + "color_scheme": { + "repr": "-1", + "type": "repr" + }, + "dynamic_range": { + "repr": "False", + "type": "repr" + }, + "max_range": { + "repr": "10.0", + "type": "repr" + }, + "mouse_pub_topic": { + "repr": "'/right/image_raw/compressed_mouse_left'", + "type": "repr" + }, + "num_gridlines": { + "repr": "0", + "type": "repr" + }, + "publish_click_location": { + "repr": "False", + "type": "repr" + }, + "rotate": { + "repr": "0", + "type": "repr" + }, + "smooth_image": { + "repr": "False", + "type": "repr" + }, + "toolbar_hidden": { + "repr": "False", + "type": "repr" + }, + "topic": { + "repr": "'/right/image_raw/compressed'", + "type": "repr" + }, + "zoom1": { + "repr": "False", + "type": "repr" + } + }, + "groups": {} + } + } + } + } + } + } +} \ No newline at end of file diff --git a/Front.perspective b/Front.perspective new file mode 100644 index 00000000..9aeb250e --- /dev/null +++ b/Front.perspective @@ -0,0 +1,182 @@ +{ + "keys": {}, + "groups": { + "mainwindow": { + "keys": { + "geometry": { + "repr(QByteArray.hex)": "QtCore.QByteArray(b'01d9d0cb00030000000000460000001b0000077f000004af00000046000000400000077f000004af0000000000000000078000000046000000400000077f000004af')", + "type": "repr(QByteArray.hex)", + "pretty-print": " F F @ F @ " + }, + "state": { + "repr(QByteArray.hex)": "QtCore.QByteArray(b'000000ff00000000fd00000001000000030000073a00000446fc0100000002fb0000005a007200710074005f0069006d006100670065005f0076006900650077005f005f0049006d0061006700650056006900650077005f005f0031005f005f0049006d006100670065005600690065007700570069006400670065007401000000000000073a000000be00fffffffb0000005a007200710074005f0069006d006100670065005f0076006900650077005f005f0049006d0061006700650056006900650077005f005f0032005f005f0049006d0061006700650056006900650077005700690064006700650074010000039f0000039b00000000000000000000073a0000000000000004000000040000000800000008fc00000001000000030000000100000036004d0069006e0069006d0069007a006500640044006f0063006b00570069006400670065007400730054006f006f006c0062006100720000000000ffffffff0000000000000000')", + "type": "repr(QByteArray.hex)", + "pretty-print": " Zrqt_image_view__ImageView__1__ImageViewWidget Zrqt_image_view__ImageView__2__ImageViewWidget 6MinimizedDockWidgetsToolbar " + } + }, + "groups": { + "toolbar_areas": { + "keys": { + "MinimizedDockWidgetsToolbar": { + "repr": "8", + "type": "repr" + } + }, + "groups": {} + } + } + }, + "pluginmanager": { + "keys": { + "running-plugins": { + "repr": "{'rqt_image_view/ImageView': [1]}", + "type": "repr" + } + }, + "groups": { + "plugin__rqt_image_view__ImageView__1": { + "keys": {}, + "groups": { + "dock_widget__ImageViewWidget": { + "keys": { + "dock_widget_title": { + "repr": "'Image View'", + "type": "repr" + }, + "dockable": { + "repr": "True", + "type": "repr" + }, + "parent": { + "repr": "None", + "type": "repr" + } + }, + "groups": {} + }, + "plugin": { + "keys": { + "color_scheme": { + "repr": "-1", + "type": "repr" + }, + "dynamic_range": { + "repr": "False", + "type": "repr" + }, + "max_range": { + "repr": "10.0", + "type": "repr" + }, + "mouse_pub_topic": { + "repr": "'/zed2i/zed_node_zed2i/rgb_raw/image_raw_color/compressed_mouse_left'", + "type": "repr" + }, + "num_gridlines": { + "repr": "0", + "type": "repr" + }, + "publish_click_location": { + "repr": "False", + "type": "repr" + }, + "rotate": { + "repr": "0", + "type": "repr" + }, + "smooth_image": { + "repr": "False", + "type": "repr" + }, + "toolbar_hidden": { + "repr": "False", + "type": "repr" + }, + "topic": { + "repr": "'/zed2i/zed_node_zed2i/rgb_raw/image_raw_color/compressed'", + "type": "repr" + }, + "zoom1": { + "repr": "False", + "type": "repr" + } + }, + "groups": {} + } + } + }, + "plugin__rqt_image_view__ImageView__2": { + "keys": {}, + "groups": { + "dock_widget__ImageViewWidget": { + "keys": { + "dock_widget_title": { + "repr": "'Image View (2)'", + "type": "repr" + }, + "dockable": { + "repr": "'true'", + "type": "repr" + }, + "parent": { + "repr": "None", + "type": "repr" + } + }, + "groups": {} + }, + "plugin": { + "keys": { + "color_scheme": { + "repr": "'-1'", + "type": "repr" + }, + "dynamic_range": { + "repr": "'false'", + "type": "repr" + }, + "max_range": { + "repr": "'10'", + "type": "repr" + }, + "mouse_pub_topic": { + "repr": "'/dumper/image_raw_mouse_left'", + "type": "repr" + }, + "num_gridlines": { + "repr": "'0'", + "type": "repr" + }, + "publish_click_location": { + "repr": "'false'", + "type": "repr" + }, + "rotate": { + "repr": "'0'", + "type": "repr" + }, + "smooth_image": { + "repr": "'false'", + "type": "repr" + }, + "toolbar_hidden": { + "repr": "'false'", + "type": "repr" + }, + "topic": { + "repr": "'/dumper/image_raw'", + "type": "repr" + }, + "zoom1": { + "repr": "'false'", + "type": "repr" + } + }, + "groups": {} + } + } + } + } + } + } +} \ No newline at end of file diff --git a/Groot2 b/Groot2 new file mode 160000 index 00000000..b65aff68 --- /dev/null +++ b/Groot2 @@ -0,0 +1 @@ +Subproject commit b65aff68445e34e63516e01378d489261e76c4f1 diff --git a/Inside Cams.perspective b/Inside Cams.perspective new file mode 100644 index 00000000..f5f40894 --- /dev/null +++ b/Inside Cams.perspective @@ -0,0 +1,182 @@ +{ + "keys": {}, + "groups": { + "mainwindow": { + "keys": { + "geometry": { + "repr(QByteArray.hex)": "QtCore.QByteArray(b'01d9d0cb00030000000000460000001b0000077f000004af00000046000000400000077f000004af0000000000000000078000000046000000400000077f000004af')", + "type": "repr(QByteArray.hex)", + "pretty-print": " F F @ F @ " + }, + "state": { + "repr(QByteArray.hex)": "QtCore.QByteArray(b'000000ff00000000fd00000001000000030000073a00000446fc0100000002fb0000005a007200710074005f0069006d006100670065005f0076006900650077005f005f0049006d0061006700650056006900650077005f005f0031005f005f0049006d0061006700650056006900650077005700690064006700650074010000000000000399000000be00fffffffb0000005a007200710074005f0069006d006100670065005f0076006900650077005f005f0049006d0061006700650056006900650077005f005f0032005f005f0049006d0061006700650056006900650077005700690064006700650074010000039f0000039b000000d300ffffff0000073a0000000000000004000000040000000800000008fc00000001000000030000000100000036004d0069006e0069006d0069007a006500640044006f0063006b00570069006400670065007400730054006f006f006c0062006100720000000000ffffffff0000000000000000')", + "type": "repr(QByteArray.hex)", + "pretty-print": " Zrqt_image_view__ImageView__1__ImageViewWidget Zrqt_image_view__ImageView__2__ImageViewWidget 6MinimizedDockWidgetsToolbar " + } + }, + "groups": { + "toolbar_areas": { + "keys": { + "MinimizedDockWidgetsToolbar": { + "repr": "8", + "type": "repr" + } + }, + "groups": {} + } + } + }, + "pluginmanager": { + "keys": { + "running-plugins": { + "repr": "{'rqt_image_view/ImageView': [1, 2]}", + "type": "repr" + } + }, + "groups": { + "plugin__rqt_image_view__ImageView__1": { + "keys": {}, + "groups": { + "dock_widget__ImageViewWidget": { + "keys": { + "dock_widget_title": { + "repr": "'Image View'", + "type": "repr" + }, + "dockable": { + "repr": "True", + "type": "repr" + }, + "parent": { + "repr": "None", + "type": "repr" + } + }, + "groups": {} + }, + "plugin": { + "keys": { + "color_scheme": { + "repr": "-1", + "type": "repr" + }, + "dynamic_range": { + "repr": "False", + "type": "repr" + }, + "max_range": { + "repr": "10.0", + "type": "repr" + }, + "mouse_pub_topic": { + "repr": "'/digger/image_raw_mouse_left'", + "type": "repr" + }, + "num_gridlines": { + "repr": "0", + "type": "repr" + }, + "publish_click_location": { + "repr": "False", + "type": "repr" + }, + "rotate": { + "repr": "0", + "type": "repr" + }, + "smooth_image": { + "repr": "False", + "type": "repr" + }, + "toolbar_hidden": { + "repr": "False", + "type": "repr" + }, + "topic": { + "repr": "'/digger/image_raw'", + "type": "repr" + }, + "zoom1": { + "repr": "False", + "type": "repr" + } + }, + "groups": {} + } + } + }, + "plugin__rqt_image_view__ImageView__2": { + "keys": {}, + "groups": { + "dock_widget__ImageViewWidget": { + "keys": { + "dock_widget_title": { + "repr": "'Image View (2)'", + "type": "repr" + }, + "dockable": { + "repr": "True", + "type": "repr" + }, + "parent": { + "repr": "None", + "type": "repr" + } + }, + "groups": {} + }, + "plugin": { + "keys": { + "color_scheme": { + "repr": "-1", + "type": "repr" + }, + "dynamic_range": { + "repr": "False", + "type": "repr" + }, + "max_range": { + "repr": "10.0", + "type": "repr" + }, + "mouse_pub_topic": { + "repr": "'/dumper/image_raw_mouse_left'", + "type": "repr" + }, + "num_gridlines": { + "repr": "0", + "type": "repr" + }, + "publish_click_location": { + "repr": "False", + "type": "repr" + }, + "rotate": { + "repr": "0", + "type": "repr" + }, + "smooth_image": { + "repr": "False", + "type": "repr" + }, + "toolbar_hidden": { + "repr": "False", + "type": "repr" + }, + "topic": { + "repr": "'/dumper/image_raw'", + "type": "repr" + }, + "zoom1": { + "repr": "False", + "type": "repr" + } + }, + "groups": {} + } + } + } + } + } + } +} \ No newline at end of file diff --git a/arduino/sensors/sensors.ino b/arduino/sensors/sensors.ino index fc3ef28b..dc9ac952 100644 --- a/arduino/sensors/sensors.ino +++ b/arduino/sensors/sensors.ino @@ -1,30 +1,21 @@ // Define a struct to hold the sensor data struct SensorData { - bool diggerTopLimitSwitch; - bool diggerBottomLimitSwitch; - bool dumperTopLimitSwitch; - bool dumperBottomLimitSwitch; int leftMotorPotentiometer; int rightMotorPotentiometer; + bool bottomLimitSwitch; }; // Define the sensor pins here -#define DIGGER_TOP_LIMIT_SWITCH 2 -#define DIGGER_BOTTOM_LIMIT_SWITCH 3 -#define DUMPER_TOP_LIMIT_SWITCH 4 -#define DUMPER_BOTTOM_LIMIT_SWITCH 5 #define LEFT_MOTOR_POT_PIN A0 #define RIGHT_MOTOR_POT_PIN A1 +#define RELAY_PIN 7 void setup() { // Initialize serial communication Serial.begin(9600); - // Initialize the digital inputs (limit switches) - pinMode(DIGGER_TOP_LIMIT_SWITCH, INPUT_PULLUP); - pinMode(DIGGER_BOTTOM_LIMIT_SWITCH, INPUT_PULLUP); - pinMode(DUMPER_TOP_LIMIT_SWITCH, INPUT_PULLUP); - pinMode(DUMPER_BOTTOM_LIMIT_SWITCH, INPUT_PULLUP); + pinMode(RELAY_PIN, OUTPUT); // Set relay pin as an output + digitalWrite(RELAY_PIN, LOW); // Ensure the motor is off at the start // No need to configure analog pins explicitly for potentiometers // as they are used as inputs by default. @@ -34,19 +25,35 @@ void loop() { // Create a SensorData struct SensorData data; - // Read from the digital inputs (limit switches) - data.diggerTopLimitSwitch = (bool)digitalRead(DIGGER_TOP_LIMIT_SWITCH); - data.diggerBottomLimitSwitch = (bool)digitalRead(DIGGER_BOTTOM_LIMIT_SWITCH); - data.dumperTopLimitSwitch = (bool)digitalRead(DUMPER_TOP_LIMIT_SWITCH); - data.dumperBottomLimitSwitch = (bool)digitalRead(DUMPER_BOTTOM_LIMIT_SWITCH); - // Read from the analog inputs (potentiometers) data.leftMotorPotentiometer = analogRead(LEFT_MOTOR_POT_PIN); // Read left motor potentiometer value data.rightMotorPotentiometer = analogRead(RIGHT_MOTOR_POT_PIN); // Read right motor potentiometer value + data.bottomLimitSwitch = analogRead(bottom_limit_switch); //bottom limit switch value + // Send the struct over the serial bus to the Nvidia Jetson Serial.write((byte *)&data, sizeof(SensorData)); + // Check for incoming relay commands from the Jetson + if (Serial.available()) { + byte cmd = Serial.read(); + static bool relayState = LOW; + switch (cmd) { + case '0': // Off + relayState = LOW; + break; + case '1': // On + relayState = HIGH; + break; + case '2': // Toggle + relayState = !relayState; + break; + default: // Unknown command + break; + } + digitalWrite(RELAY_PIN, relayState); + } + // Wait 100ms before the next loop iteration delay(100); } diff --git a/config/auger_config.yaml b/config/auger_config.yaml new file mode 100644 index 00000000..5d2a7153 --- /dev/null +++ b/config/auger_config.yaml @@ -0,0 +1,21 @@ +/**: + ros__parameters: + #TODO: Figure out all of these values + SCREW_SPEED: 4000 # TODO: update + POWER_LIMIT: 1 #TODO: update + MAX_SCREW_SPEED: 4000 # in RPM + MIN_SCREW_SPEED: 2000 # in RPM TODO: This is a guess + MAX_RETRACT_PUSH_MOTOR_VELOCITY: -600 # in RPM, potentially could be faster + MAX_EXTEND_PUSH_MOTOR_VELOCITY: 600 # in RPM + push_motor_position: 0 + tilt_actuator_position: 0 + TILT_ACTUATOR_CURRENT_THRESHOLD: 0 + MAX_PUSH_MOTOR_POSITION: 0 + MIN_PUSH_MOTOR_POSITION: 0 + PUSH_MOTOR_POS_TOLERANCE: 0 + MAX_PUSH_MOTOR_VELOCITY: 0 + TILT_ACTUATOR_SPEED: 0 + TILT_ACTUATOR_MIN_EXTENSION: 0 + TILT_ACTUATOR_ID: 0 + PUSH_MOTOR_ID: 0 + SPIN_MOTOR_ID: 0 \ No newline at end of file diff --git a/config/costmap_config.yaml b/config/costmap_config.yaml new file mode 100644 index 00000000..61dfb40b --- /dev/null +++ b/config/costmap_config.yaml @@ -0,0 +1,11 @@ +/**: + ros__parameters: + unknown_cost: -1 + lethal_cost: 100 + input_topic: /zed2i/zed_node_zed2i/mapping/fused_cloud + output_topic: /map + costmap_resolution: 0.1 + lethal_slope: 0.3 # the slope at which a point is considered lethal + # Units for slope are non existant. higher = steeper + + diff --git a/config/motor_control.yaml b/config/motor_control.yaml index 173914f8..a756c74c 100644 --- a/config/motor_control.yaml +++ b/config/motor_control.yaml @@ -15,9 +15,9 @@ DIGGER_ACTUATORS_kP: 0.05 # kP for digger actuator position control DIGGER_ACTUATORS_kP_coupling: 0.004 # kP for digger actuator position syncing DIGGER_PITCH_kP: 2.5 # kP for tipping while digging - TIPPING_SPEED_ADJUSTMENT: true # determines if pitch speed adjustment is used + TIPPING_SPEED_ADJUSTMENT: false # determines if pitch speed adjustment is used DIGGER_SAFETY_ZONE: 120 # Measured in potentiometer units (0 to 1023) CURRENT_SPIKE_THRESHOLD: 1.8 # Threshold for current spike detection (measured in Amps) CURRENT_SPIKE_TIME: 0.2 # Time in seconds to consider it a current spike (measured in seconds) - BUCKETS_CURRENT_SPIKE_THRESHOLD: 8.0 # Threshold for current spike detection (measured in Amps) + BUCKETS_CURRENT_SPIKE_THRESHOLD: 12.0 # Threshold for current spike detection (measured in Amps) BUCKETS_CURRENT_SPIKE_TIME: 0.2 # Time in seconds to consider it a current spike (measured in seconds) diff --git a/config/nav2_isaac_sim.yaml b/config/nav2_isaac_sim.yaml index 2d0bdc10..15a0ec1c 100644 --- a/config/nav2_isaac_sim.yaml +++ b/config/nav2_isaac_sim.yaml @@ -5,22 +5,16 @@ bt_navigator: ros__parameters: use_sim_time: False - global_frame: odom + global_frame: map robot_base_frame: base_link odom_topic: /odom bt_loop_duration: 10 default_server_timeout: 20 navigators: ["navigate_to_pose", "navigate_through_poses"] - #default_nav_to_pose_bt_xml: $(find-pkg-share nova_carter_navigation)/behavior_trees/navigate_to_pose_w_replanning_and_recovery.xml - #default_nav_through_poses_bt_xml: $(find-pkg-share nova_carter_navigation)/behavior_trees/navigate_through_poses_w_replanning_and_recovery.xml navigate_to_pose: plugin: "nav2_bt_navigator/NavigateToPoseNavigator" navigate_through_poses: plugin: "nav2_bt_navigator/NavigateThroughPosesNavigator" - # 'default_nav_through_poses_bt_xml' and 'default_nav_to_pose_bt_xml' are use defaults: - # nav2_bt_navigator/navigate_to_pose_w_replanning_and_recovery.xml - # 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 @@ -28,7 +22,6 @@ bt_navigator: - 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 @@ -54,7 +47,6 @@ bt_navigator: - 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 @@ -66,9 +58,7 @@ bt_navigator: - 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: use_sim_time: False @@ -83,7 +73,6 @@ controller_server: progress_checker_plugin: "progress_checker" goal_checker_plugins: ["stopped_goal_checker"] controller_plugins: ["FollowPath"] - # Progress checker parameters progress_checker: plugin: "nav2_controller::PoseProgressChecker" required_movement_radius: 0.1 @@ -96,7 +85,6 @@ controller_server: xy_goal_tolerance: 0.1 yaw_goal_tolerance: 0.1 FollowPath: - # RotationShimController parameters plugin: "nav2_rotation_shim_controller::RotationShimController" primary_controller: "dwb_core::DWBLocalPlanner" angular_dist_threshold: 0.785 @@ -104,7 +92,6 @@ controller_server: rotate_to_heading_angular_vel: 0.75 max_angular_accel: 0.75 simulate_ahead_time: 1.0 - # Primary controller params (DWBLocalPlanner) debug_trajectory_details: True min_vel_x: -0.75 min_vel_y: 0.0 @@ -152,8 +139,6 @@ controller_server: RotateToGoal.slowing_factor: 1.0 RotateToGoal.lookahead_time: -1.0 -# Update velocity smoother default params to match our controller server config -# https://navigation.ros.org/configuration/packages/configuring-velocity-smoother.html velocity_smoother: ros__parameters: use_sim_time: False @@ -171,74 +156,43 @@ velocity_smoother: local_costmap: local_costmap: ros__parameters: - update_frequency: 10.0 - publish_frequency: 10.0 - global_frame: odom + global_frame: map robot_base_frame: base_link - use_sim_time: False - rolling_window: True - width: 10 - height: 10 + update_frequency: 5.0 + publish_frequency: 2.0 + rolling_window: true + width: 3 + height: 3 resolution: 0.05 - footprint: "[ [0.595, 0.38], [-0.595, 0.38,], [-0.595, -0.38], [0.595, -0.38] ]" - plugins: ["nvblox_base_layer", "inflation_layer"] - nvblox_base_layer: - plugin: "nvblox::nav2::NvbloxCostmapLayer" - enabled: True - max_obstacle_distance: 1.0 - gradient_multiplier: 50.0 - # Need to play with this. this is just linear multiplier to the values found by the gradient. - # As of right now, the formula for the cost value is - # cost = min(max_cost, (gradient * gradient_multiplier * max_cost)) - # max cost = 255 - # gradient = abs(height(x - 1) - height(x + 1)) + abs(height(y - 1) - height(y + 1)) - nvblox_map_slice_topic: "/nvblox_node/static_map_slice" + plugins: ["static_layer", "inflation_layer"] inflation_layer: plugin: "nav2_costmap_2d::InflationLayer" - enabled: True - cost_scaling_factor: 8.0 - inflation_radius: 5.0 - always_send_full_costmap: True - local_costmap_client: - ros__parameters: - use_sim_time: False - local_costmap_rclcpp_node: - ros__parameters: - use_sim_time: False + cost_scaling_factor: 3.0 + inflation_radius: 0.55 + static_layer: + plugin: "nav2_costmap_2d::StaticLayer" + map_subscribe_transient_local: True + map_topic: /map_server/map global_costmap: global_costmap: ros__parameters: - update_frequency: 10.0 - publish_frequency: 10.0 - global_frame: odom + global_frame: map robot_base_frame: base_link - use_sim_time: False - rolling_window: True - width: 200 - height: 200 - footprint: "[ [0.595, 0.38], [-0.595, 0.38,], [-0.595, -0.38], [0.595, -0.38] ]" - # resolution: 0.1 - origin_x: -100.0 - origin_y: -100.0 - plugins: ["nvblox_base_layer", "inflation_layer"] - nvblox_base_layer: - plugin: "nvblox::nav2::NvbloxCostmapLayer" - enabled: True - max_obstacle_distance: 2.0 - nvblox_map_slice_topic: "/nvblox_node/static_map_slice" + update_frequency: 5.0 + publish_frequency: 2.0 + rolling_window: false + resolution: 0.1 + plugins: ["static_layer", "inflation_layer"] inflation_layer: plugin: "nav2_costmap_2d::InflationLayer" - enabled: True - cost_scaling_factor: 4.0 - inflation_radius: 5.0 - always_send_full_costmap: True - global_costmap_client: - ros__parameters: - use_sim_time: False - global_costmap_rclcpp_node: - ros__parameters: - use_sim_time: False + cost_scaling_factor: 3.0 + inflation_radius: 0.55 + static_layer: + plugin: "nav2_costmap_2d::StaticLayer" + map_subscribe_transient_local: False + subscribe_to_updates: True + map_topic: /map planner_server: ros__parameters: @@ -292,11 +246,9 @@ smoother_server: behavior_server: ros__parameters: - costmap_topic: local_costmap/costmap_raw footprint_topic: local_costmap/published_footprint cycle_frequency: 10.0 - behavior_plugins: - ["spin", "backup", "drive_on_heading", "assisted_teleop", "wait"] + behavior_plugins: ["spin", "backup", "drive_on_heading", "wait"] spin: plugin: "nav2_behaviors/Spin" backup: @@ -305,28 +257,15 @@ behavior_server: plugin: "nav2_behaviors/DriveOnHeading" wait: plugin: "nav2_behaviors/Wait" - assisted_teleop: - plugin: "nav2_behaviors/AssistedTeleop" - global_frame: odom + global_frame: map robot_base_frame: base_link transform_tolerance: 0.1 use_sim_time: False - simulate_ahead_time: 2.0 - max_rotational_vel: 1.0 - min_rotational_vel: 0.4 - rotational_acc_lim: 3.2 + simulate_ahead_time: 1.0 + max_rotational_vel: 0.75 + min_rotational_vel: -0.75 + rotational_acc_lim: 0.75 robot_state_publisher: ros__parameters: use_sim_time: False - -waypoint_follower: - ros__parameters: - use_sim_time: False - loop_rate: 20 - stop_on_failure: false - waypoint_task_executor_plugin: "wait_at_waypoint" - wait_at_waypoint: - plugin: "nav2_waypoint_follower::WaitAtWaypoint" - enabled: True - waypoint_pause_duration: 200 diff --git a/config/rovr_control.yaml b/config/rovr_control.yaml index 021729f7..b78d7bef 100644 --- a/config/rovr_control.yaml +++ b/config/rovr_control.yaml @@ -2,12 +2,13 @@ ros__parameters: max_drive_power: 1.0 # Measured in Duty Cycle (0.0-1.0) max_turn_power: 1.0 # Measured in Duty Cycle (0.0-1.0) - digger_chain_power: 0.2 # Measured in Duty Cycle (0.0-1.0) + digger_chain_power: 0.165 # Measured in Duty Cycle (0.0-1.0) digger_lift_manual_power_down: 0.12 # Measured in Duty Cycle (0.0-1.0) digger_lift_manual_power_up: 0.5 # Measured in Duty Cycle (0.0-1.0) autonomous_field_type: "cosmic" # The type of field ("cosmic", "top", "bottom", "nasa") - lift_digging_start_position: 125.0 # Measured in potentiometer units (0 to 1023) - + tilt_digging_start_position: 125.0 # Measured in potentiometer units (0 to 1023) + fast_screw_speed: 4000 + slow_screw_speed: 2000 # Auto Dig cost starts at max_dig_cost, and increases to absolute_max_dig_cost absolute_max_dig_cost: 200 # Measured as a Costmap grid value (0-255) # TODO: tune this max_dig_cost: 100 # Measured as a Costmap grid value (0-255) # TODO: Tune this @@ -15,4 +16,5 @@ # Locations of all dig sites to check, as [x, y] pairs # Ros param lists must be flattened, so this is a list of all x values followed by all y values # https://discord.com/channels/893292981094858754/896192143570403340/1345192968629387294 - all_dig_locations: [.6, .37, .6, 1.1, .6, 1.83, 1.8, .37, 1.8, 1.1, 1.8, 1.83, 3.0, .37, 3.0, 1.1, 3.0, 1.83, .6, 2.5, 1.8, 2.5, 3.0, 2.5] + all_dig_locations: [2.0, 2.0, .7, .7, .7, 1.4, .7, 2.1, 1.9, .7, 1.9, 1.4, 1.9, 2.1, 3.0, .7, 3.0, 1.4, 3.0, 2.1, .7, 2.8, 1.9, 2.8, 3.0, 2.8] + # all_dig_locations: [.6, .37, .6, 1.1, .6, 1.83, 1.8, .37, 1.8, 1.1, 1.8, 1.83, 3.0, .37, 3.0, 1.1, 3.0, 1.83, .6, 2.5, 1.8, 2.5, 3.0, 2.5] diff --git a/config/rviz/zed_example.rviz b/config/rviz/zed_example.rviz index e90abcf7..262ffeed 100644 --- a/config/rviz/zed_example.rviz +++ b/config/rviz/zed_example.rviz @@ -4,19 +4,14 @@ Panels: Name: Displays Property Tree Widget: Expanded: - - /Global Options1 - - /Status1 - - /TF1 - /TF1/Frames1 - /TF1/Tree1 - /Nvblox1 - /Nvblox1/Map Slice Bounds1 - /Nvblox1/Esdf Slice1 - - /Sensors1 - - /Plan1 - - /RobotModel1 + - /Map2 Splitter Ratio: 0.5896806120872498 - Tree Height: 723 + Tree Height: 1001 - Class: rviz_common/Selection Name: Selection - Class: rviz_common/Tool Properties @@ -86,21 +81,29 @@ Visualization Manager: odom: zed2i_camera_link: base_link: - back_left_wheel_Link: {} - back_right_wheel_Link: {} - front_left_wheel_Link: {} - front_right_wheel_Link: {} + back_left_wheel_Link: + {} + back_right_wheel_Link: + {} + front_left_wheel_Link: + {} + front_right_wheel_Link: + {} zed2i_rear_camera_link: zed2i_rear_camera_center: zed2i_rear_left_camera_frame: - zed2i_rear_left_camera_optical_frame: {} + zed2i_rear_left_camera_optical_frame: + {} zed2i_rear_right_camera_frame: - zed2i_rear_right_camera_optical_frame: {} + zed2i_rear_right_camera_optical_frame: + {} zed2i_camera_center: zed2i_left_camera_frame: - zed2i_left_camera_optical_frame: {} + zed2i_left_camera_optical_frame: + {} zed2i_right_camera_frame: - zed2i_right_camera_optical_frame: {} + zed2i_right_camera_optical_frame: + {} Update Interval: 0 Value: true - Alpha: 0.5 @@ -140,7 +143,8 @@ Visualization Manager: - Class: rviz_default_plugins/MarkerArray Enabled: false Name: Mesh Marker - Namespaces: {} + Namespaces: + {} Topic: Depth: 5 Durability Policy: Volatile @@ -151,7 +155,8 @@ Visualization Manager: - Class: rviz_default_plugins/Marker Enabled: false Name: Map Slice Bounds - Namespaces: {} + Namespaces: + {} Topic: Depth: 5 Durability Policy: Volatile @@ -228,7 +233,7 @@ Visualization Manager: Use Fixed Frame: true Use rainbow: true Value: true - Enabled: true + Enabled: false Name: Nvblox - Class: rviz_common/Group Displays: @@ -485,7 +490,7 @@ Visualization Manager: Visual Enabled: true - Alpha: 0.699999988079071 Class: rviz_default_plugins/Map - Color Scheme: map + Color Scheme: costmap Draw Behind: false Enabled: false Name: Map @@ -517,10 +522,65 @@ Visualization Manager: Reliability Policy: Reliable Value: /local_costmap/published_footprint Value: true + - Alpha: 1 + Autocompute Intensity Bounds: true + Autocompute Value Bounds: + Max Value: 10 + Min Value: -10 + Value: true + Axis: Z + Channel Name: intensity + Class: rviz_default_plugins/PointCloud2 + Color: 255; 255; 255 + Color Transformer: RGB8 + Decay Time: 0 + Enabled: true + Invert Rainbow: false + Max Color: 255; 255; 255 + Max Intensity: 4096 + Min Color: 0; 0; 0 + Min Intensity: 0 + Name: PointCloud2 + Position Transformer: XYZ + Selectable: true + Size (Pixels): 3 + Size (m): 0.009999999776482582 + Style: Flat Squares + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /zed2i/zed_node_zed2i/mapping/fused_cloud + Use Fixed Frame: true + Use rainbow: true + Value: true + - Alpha: 0.699999988079071 + Class: rviz_default_plugins/Map + Color Scheme: map + Draw Behind: false + Enabled: true + Name: Map + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /map + Update Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /map_updates + Use Timestamp: false + Value: true Enabled: true Global Options: Background Color: 212; 212; 212 - Fixed Frame: map + Fixed Frame: odom Frame Rate: 30 Name: root Tools: @@ -563,25 +623,25 @@ Visualization Manager: Views: Current: Class: rviz_default_plugins/Orbit - Distance: 18.716569900512695 + Distance: 15.352240562438965 Enable Stereo Rendering: Stereo Eye Separation: 0.05999999865889549 Stereo Focal Distance: 1 Swap Stereo Eyes: false Value: false Focal Point: - X: 5.1264262199401855 - Y: -3.5869696140289307 - Z: -2.170865774154663 + X: 1.3153513669967651 + Y: 0.44086113572120667 + Z: 0.31986361742019653 Focal Shape Fixed Size: false Focal Shape Size: 0.05000000074505806 Invert Z Axis: false Name: Current View Near Clip Distance: 0.009999999776482582 - Pitch: 1.1947964429855347 + Pitch: 1.1347968578338623 Target Frame: camera_link Value: Orbit (rviz) - Yaw: 1.8003982305526733 + Yaw: 4.697237491607666 Saved: ~ Window Geometry: Color Image: @@ -590,16 +650,16 @@ Window Geometry: collapsed: false Displays: collapsed: false - Height: 1006 + Height: 1385 Hide Left Dock: false Hide Right Dock: true - QMainWindow State: 000000ff00000000fd0000000400000000000002350000035cfc0200000013fb0000001200530065006c0065006300740069006f006e00000001e10000009b000000ab00fffffffb0000001e0054006f006f006c002000500072006f00700065007200740069006500730200000780000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c00610079007301000000690000035c0000017800fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261fb00000010007200670062005f006c00650066007402000008f4000000fe000003740000026efb0000001400640065007000740068005f006c0065006600740200000c8c000001e70000014600000125fb0000000a0049006d0061006700650200000c900000023f0000025e000001d3fb0000000c00430061006d0065007200610200000c2f00000104000001e600000190fb000000160044006500700074006800200049006d0061006700650200000592000002f00000018800000148fb000000160043006f006c006f007200200049006d006100670065020000078000000205000001850000013bfb0000001a0043006f006c006f00720020004f007600650072006c0061007902000005910000019f000001880000013cfb0000001a004400650070007400680020004f007600650072006c006100790200000591000002eb0000018c00000145fb0000000a0049006d0061006700650100000385000000d00000000000000000fb0000001c00530065006d0061006e00740069006300200049006d0061006700650200000410000002ee0000017800000144fb0000002000440079006e0061006d0069006300730020004f007600650072006c006100790200000592000000c6000001850000013d000000010000010f00000435fc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073000000003b000004350000012300fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000004420000003efc0100000002fb0000000800540069006d00650100000000000004420000000000000000fb0000000800540069006d006501000000000000045000000000000000000000053f0000035c00000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 + QMainWindow State: 000000ff00000000fd000000040000000000000235000004d7fc0200000013fb0000001200530065006c0065006300740069006f006e00000001e10000009b000000ab00fffffffb0000001e0054006f006f006c002000500072006f00700065007200740069006500730200000780000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c0061007900730100000069000004d70000017800fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261fb00000010007200670062005f006c00650066007402000008f4000000fe000003740000026efb0000001400640065007000740068005f006c0065006600740200000c8c000001e70000014600000125fb0000000a0049006d0061006700650200000c900000023f0000025e000001d3fb0000000c00430061006d0065007200610200000c2f00000104000001e600000190fb000000160044006500700074006800200049006d0061006700650200000592000002f00000018800000148fb000000160043006f006c006f007200200049006d006100670065020000078000000205000001850000013bfb0000001a0043006f006c006f00720020004f007600650072006c0061007902000005910000019f000001880000013cfb0000001a004400650070007400680020004f007600650072006c006100790200000591000002eb0000018c00000145fb0000000a0049006d0061006700650100000385000000d00000000000000000fb0000001c00530065006d0061006e00740069006300200049006d0061006700650200000410000002ee0000017800000144fb0000002000440079006e0061006d0069006300730020004f007600650072006c006100790200000592000000c6000001850000013d000000010000010f00000435fc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073000000003b000004350000012300fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000004420000003efc0100000002fb0000000800540069006d00650100000000000004420000000000000000fb0000000800540069006d00650100000000000004500000000000000000000006bc000004d700000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 Selection: collapsed: false Tool Properties: collapsed: false Views: collapsed: true - Width: 1920 - X: 0 - Y: 726 + Width: 2301 + X: 798 + Y: 101 diff --git a/config/sensors/zed_common.yaml b/config/sensors/zed_common.yaml index 5a4ed976..15456a8a 100644 --- a/config/sensors/zed_common.yaml +++ b/config/sensors/zed_common.yaml @@ -64,7 +64,7 @@ pos_tracking: pos_tracking_enabled: true # True to enable positional tracking from start - pos_tracking_mode: "GEN_2" # Matches the ZED SDK setting: 'GEN_1', 'GEN_2' + pos_tracking_mode: "GEN_1" # Matches the ZED SDK setting: 'GEN_1', 'GEN_2' imu_fusion: true # enable/disable IMU fusion. When set to false, only the optical odometry will be used. publish_tf: true # [usually overwritten by launch file] publish `odom -> camera_link` TF publish_map_tf: false # [usually overwritten by launch file] publish `map -> odom` TF @@ -100,10 +100,10 @@ target_yaw_uncertainty: 1e-2 # defines the target yaw uncertainty at which the calibration process between GNSS and VIO concludes. The unit of this parameter is in radian. By default, the threshold is set at 0.1 radians. mapping: - mapping_enabled: false # True to enable mapping and fused point cloud publication + mapping_enabled: true # True to enable mapping and fused point cloud publication resolution: 0.05 # maps resolution in meters [min: 0.01f - max: 0.2f] max_mapping_range: 5.0 # maximum depth range while mapping in meters (-1 for automatic calculation) [2.0, 20.0] - fused_pointcloud_freq: 1.0 # frequency of the publishing of the fused colored point cloud + fused_pointcloud_freq: 3.0 # frequency of the publishing of the fused colored point cloud clicked_point_topic: "/clicked_point" # Topic published by Rviz when a point of the cloud is clicked. Used for plane detection pd_max_distance_threshold: 0.15 # Plane detection: controls the spread of plane by checking the position difference. pd_normal_similarity_threshold: 15.0 # Plane detection: controls the spread of plane by checking the angle difference. diff --git a/docker/Dockerfile.deepstream b/docker/Dockerfile.deepstream index 392406d9..44ac7782 100644 --- a/docker/Dockerfile.deepstream +++ b/docker/Dockerfile.deepstream @@ -1,6 +1,8 @@ ARG BASE_IMAGE FROM ${BASE_IMAGE} +RUN curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg + COPY deepstream/deepstream.deb deepstream.deb RUN --mount=type=cache,target=/var/cache/apt apt-get update && apt-get install -y \ diff --git a/docker/Dockerfile.gazebo b/docker/Dockerfile.gazebo index 22455481..b125a51d 100644 --- a/docker/Dockerfile.gazebo +++ b/docker/Dockerfile.gazebo @@ -3,6 +3,8 @@ FROM ${BASE_IMAGE} USER root +RUN curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg + RUN --mount=type=cache,target=/var/cache/apt apt-get update && apt-get install -y \ ros-humble-ros-gz \ ros-humble-sdformat-urdf \ diff --git a/docker/Dockerfile.umn b/docker/Dockerfile.umn index a098cbfe..ff532cef 100644 --- a/docker/Dockerfile.umn +++ b/docker/Dockerfile.umn @@ -3,6 +3,8 @@ FROM ${BASE_IMAGE} USER root +RUN curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg + # To get list of packages currently missing run: rosdep install --simulate --from-path src --ignore-src --skip-keys "ros-humble-isaac-ros-h264-decoder ros-humble-nova-carter-navigation ros-humble-isaac-ros-dnn-image-encoder ros-humble-isaac-ros-triton ros-humble-isaac-ros-unet ros-humble-isaac-ros-peoplesemseg-models-install" | awk '{print $5 }' RUN --mount=type=cache,target=/var/cache/apt apt-get update && apt-get install -y \ ros-humble-rclc \ @@ -38,7 +40,16 @@ RUN --mount=type=cache,target=/var/cache/apt apt-get update && apt-get install ros-humble-ffmpeg-image-transport \ ros-humble-ffmpeg-encoder-decoder \ ros-humble-zed-msgs \ + libudev-dev \ + libusb-1.0-0-dev \ + libhidapi-libusb0 \ && rm -rf /var/lib/apt/lists/* +RUN python3 -m pip install -U \ + pyvista \ + streamdeck + +RUN echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="0fd9", MODE="0666"' | sudo tee /etc/udev/rules.d/99-streamdeck.rules + ARG USERNAME=admin RUN adduser ${USERNAME} dialout && adduser ${USERNAME} tty && adduser ${USERNAME} input diff --git a/point_cloud_data.npz b/point_cloud_data.npz new file mode 100644 index 00000000..40f2ac91 Binary files /dev/null and b/point_cloud_data.npz differ diff --git a/scripts/laptop.sh b/scripts/laptop.sh index 5e26e117..f6432e78 100755 --- a/scripts/laptop.sh +++ b/scripts/laptop.sh @@ -1,4 +1,4 @@ #!/bin/bash source install/setup.bash && \ -ros2 launch gstreamer laptop_launch.py +ros2 run rqt_image_view rqt_image_view diff --git a/src/digger/digger/__init__.py b/src/alexblox/alexblox/__init__.py similarity index 100% rename from src/digger/digger/__init__.py rename to src/alexblox/alexblox/__init__.py diff --git a/src/alexblox/alexblox/costmapGenerator.py b/src/alexblox/alexblox/costmapGenerator.py new file mode 100644 index 00000000..121f5fc5 --- /dev/null +++ b/src/alexblox/alexblox/costmapGenerator.py @@ -0,0 +1,109 @@ +import rclpy +from rclpy.node import Node +import numpy as np + +from sensor_msgs.msg import PointCloud2 +from nav_msgs.msg import OccupancyGrid + + +class CostmapGenerator(Node): + def __init__(self): + super().__init__("costmap_generator") + + self.declare_parameter("costmap_resolution", 0.1) + self.resolution = float(self.get_parameter("costmap_resolution").value) + + self.declare_parameter("lethal_slope", 0.3) + self.lethal_slope = float(self.get_parameter("lethal_slope").value) + # who knows what units this is in + + self.declare_parameter("lethal_cost", 254) + self.lethal_cost = int(self.get_parameter("lethal_cost").value) + + self.declare_parameter("unknown_cost", 255) + self.unknown_cost = int(self.get_parameter("unknown_cost").value) + + self.declare_parameter("input_topic", "/zed2i/zed_node_zed2i/mapping/fused_cloud") + input_topic = str(self.get_parameter("input_topic").value) + + self.declare_parameter("output_topic", "/costmap/costmap") + output_topic = str(self.get_parameter("output_topic").value) + + self.pointCloudSubscription = self.create_subscription(PointCloud2, input_topic, self.pointCloudCallback, 10) + + self.costmapPublisher = self.create_publisher(OccupancyGrid, output_topic, 10) + # self.distanceMapSubscription = self.create_subscription( + # DistanceMapSlice, + # '/nvblox_node/static_map_slice', + # self.distanceMapCallback, + # 10 + # ) + self.get_logger().info(f"Costmap generating on {input_topic} -> {output_topic}") + print(input_topic, output_topic) + + def pointCloudCallback(self, msg): + if len(msg.data) == 0: + return + print("Received PointCloud2 message") + resolution = self.resolution + + # UNPACK THE POINT CLOUD DATA + data = np.frombuffer(msg.data, dtype=np.uint8).view(dtype=np.float32) + points = data.reshape(-1, 4)[:, :3] + + world_min_y = np.min(points[:, 1]) + world_max_y = np.max(points[:, 1]) + world_min_x = np.min(points[:, 0]) + world_max_x = np.max(points[:, 0]) + grid_x_size = int((world_max_x - world_min_x) / resolution) + 1 + grid_y_size = int((world_max_y - world_min_y) / resolution) + 1 + + # ESDF PROJECTION + projected = np.full((grid_y_size, grid_x_size), fill_value=np.nan, dtype=np.float32) + projected_x_indices = ((points[:, 0] - world_min_x) / resolution).astype(np.int32) + projected_y_indices = ((points[:, 1] - world_min_y) / resolution).astype(np.int32) + sums = np.zeros_like(projected) + counts = np.zeros_like(projected, dtype=int) + + np.add.at(sums, (projected_y_indices, projected_x_indices), points[:, 2]) + np.add.at(counts, (projected_y_indices, projected_x_indices), 1) + + average_heightmap = np.divide(sums, counts, out=np.full_like(sums, np.nan), where=counts != 0) + projected = average_heightmap + + origin = (world_min_x, world_min_y) + + # EDGE DETECTION + gradient = np.gradient(projected, resolution, edge_order=1) + magnitude = np.sqrt(gradient[0] ** 2 + gradient[1] ** 2) + costmap = np.full_like(magnitude, fill_value=self.unknown_cost, dtype=np.uint8) + costmap[magnitude <= self.lethal_slope] = 0 + costmap[magnitude > self.lethal_slope] = self.lethal_cost + + # MESSAGE PACKAGING/PUBLISHING + occ_grid = OccupancyGrid() + occ_grid.header = msg.header + occ_grid.info.resolution = float(resolution) + occ_grid.info.width = costmap.shape[1] + occ_grid.info.height = costmap.shape[0] + occ_grid.info.origin.position.x = float(origin[0]) + occ_grid.info.origin.position.y = float(origin[1]) + occ_grid.info.origin.position.z = 0.0 + # For a 2D map, the orientation is typically an identity quaternion (no rotation) + occ_grid.info.origin.orientation.w = 1.0 + occ_grid.info.origin.orientation.x = 0.0 + occ_grid.info.origin.orientation.y = 0.0 + occ_grid.info.origin.orientation.z = 0.0 + occ_grid.data = costmap.astype(np.int8).flatten().tolist() + + self.costmapPublisher.publish(occ_grid) + + return + + +def main(args=None): + rclpy.init(args=args) + node = CostmapGenerator() + rclpy.spin(node) + node.destroy_node() + rclpy.shutdown() diff --git a/src/alexblox/alexblox/visualizer.py b/src/alexblox/alexblox/visualizer.py new file mode 100644 index 00000000..b2e44a7d --- /dev/null +++ b/src/alexblox/alexblox/visualizer.py @@ -0,0 +1,108 @@ +import rclpy +from rclpy.node import Node +import numpy as np +import pyvista as pv # this isnt in the container right now, +# either pip install each time, or rebuild the container. i added it as a dependency. + +from sensor_msgs.msg import PointCloud2 +from nav_msgs.msg import OccupancyGrid +from threading import Lock + + +class CostmapVisualizer(Node): + def __init__(self): + super().__init__("costmap_visualizer") + self.lock = Lock() + + self.declare_parameter("costmap_resolution", 0.1) + self.resolution = float(self.get_parameter("costmap_resolution").value) + + self.declare_parameter("lethal_slope", 0.3) + self.lethal_slope = float(self.get_parameter("lethal_slope").value) + # who knows what units this is in + + self.declare_parameter("lethal_cost", 254) + self.lethal_cost = int(self.get_parameter("lethal_cost").value) + + self.declare_parameter("unknown_cost", 255) + self.unknown_cost = int(self.get_parameter("unknown_cost").value) + + self.declare_parameter("input_topic", "/zed2i/zed_node_zed2i/mapping/fused_cloud") + input_topic = str(self.get_parameter("input_topic").value) + + self.declare_parameter("output_topic", "/costmap/costmap") + output_topic = str(self.get_parameter("output_topic").value) + + self.pointCloudSubscription = self.create_subscription(PointCloud2, input_topic, self.pointCloudCallback, 10) + + self.costmapPublisher = self.create_publisher(OccupancyGrid, output_topic, 10) + # self.distanceMapSubscription = self.create_subscription( + # DistanceMapSlice, + # '/nvblox_node/static_map_slice', + # self.distanceMapCallback, + # 10 + # ) + self.get_logger().info(f"Costmap generating on {input_topic} -> {output_topic}") + print(input_topic, output_topic) + + def pointCloudCallback(self, msg): + if len(msg.data) == 0: + return + + with self.lock: + print("Received PointCloud2 message") + resolution = self.resolution + + # UNPACK THE POINT CLOUD DATA + data = np.frombuffer(msg.data, dtype=np.uint8).view(dtype=np.float32) + points = data.reshape(-1, 4)[:, :3] + + world_min_y = np.min(points[:, 1]) + world_max_y = np.max(points[:, 1]) + world_min_x = np.min(points[:, 0]) + world_max_x = np.max(points[:, 0]) + grid_x_size = int((world_max_x - world_min_x) / resolution) + 1 + grid_y_size = int((world_max_y - world_min_y) / resolution) + 1 + + # ESDF PROJECTION + projected = np.full((grid_x_size, grid_y_size), fill_value=np.nan, dtype=np.float32) + projected_x_indices = ((points[:, 0] - world_min_x) / resolution).astype(np.int32) + projected_y_indices = ((points[:, 1] - world_min_y) / resolution).astype(np.int32) + sums = np.zeros_like(projected) + counts = np.zeros_like(projected, dtype=int) + + np.add.at(sums, (projected_x_indices, projected_y_indices), points[:, 2]) + np.add.at(counts, (projected_x_indices, projected_y_indices), 1) + + average_heightmap = np.divide(sums, counts, out=np.full_like(sums, np.nan), where=counts != 0) + projected = average_heightmap + + # origin = (world_min_x, world_min_y) + + # EDGE DETECTION + gradient = np.gradient(projected, resolution, edge_order=1) + magnitude = np.sqrt(gradient[0] ** 2 + gradient[1] ** 2) + costmap = np.full_like(magnitude, fill_value=self.unknown_cost, dtype=np.uint8) + costmap[magnitude <= self.lethal_slope] = 0 + costmap[magnitude > self.lethal_slope] = self.lethal_cost + + point_cloud = pv.PolyData(points) + plotter = pv.Plotter() + scalars = costmap[ + ((points[:, 0] - world_min_x) // resolution).astype(int), + ((points[:, 1] - world_min_y) // resolution).astype(int), + ] + plotter.add_mesh(point_cloud, scalars=scalars, point_size=15, render_points_as_spheres=True) + plotter.set_background("white") + plotter.show() + + +def main(args=None): + rclpy.init(args=args) + + costmap_visualizer = CostmapVisualizer() + + rclpy.spin(costmap_visualizer) + + costmap_visualizer.destroy_node() + rclpy.shutdown() diff --git a/src/alexblox/launch/alexblox_launch.py b/src/alexblox/launch/alexblox_launch.py new file mode 100644 index 00000000..10d6ebc8 --- /dev/null +++ b/src/alexblox/launch/alexblox_launch.py @@ -0,0 +1,13 @@ +import launch +from launch_ros.actions import Node + + +def generate_launch_description(): + alexblox_node = Node( + package='alexblox', + executable='costmapGenerator', + name='costmap', + parameters=['config/costmap_config.yaml'] + ) + + return launch.LaunchDescription([alexblox_node]) diff --git a/src/alexblox/package.xml b/src/alexblox/package.xml new file mode 100644 index 00000000..9d538c86 --- /dev/null +++ b/src/alexblox/package.xml @@ -0,0 +1,20 @@ + + + + alexblox + 0.0.1 + Package for processing apriltag detections received from ISAAC ROS Apriltag. + Alex Berg + MIT License + + rovr_interfaces + + ament_copyright + ament_flake8 + ament_pep257 + python3-pytest + + + ament_python + + diff --git a/src/digger/resource/digger b/src/alexblox/resource/alexblox similarity index 100% rename from src/digger/resource/digger rename to src/alexblox/resource/alexblox diff --git a/src/alexblox/setup.cfg b/src/alexblox/setup.cfg new file mode 100644 index 00000000..09df28a3 --- /dev/null +++ b/src/alexblox/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script_dir=$base/lib/alexblox +[install] +install_scripts=$base/lib/alexblox diff --git a/src/alexblox/setup.py b/src/alexblox/setup.py new file mode 100644 index 00000000..12556c4c --- /dev/null +++ b/src/alexblox/setup.py @@ -0,0 +1,33 @@ +from setuptools import find_packages, setup +import os +from glob import glob + +package_name = 'alexblox' + +setup( + name=package_name, + version='0.0.0', + packages=find_packages(exclude=['test']), + data_files=[ + ('share/ament_index/resource_index/packages', + ['resource/' + package_name]), + ('share/' + package_name, ['package.xml']), + ( + os.path.join("share", package_name), + glob("launch/*launch.[pxy][yma]*", recursive=True), + ), + ], + install_requires=['setuptools'], + zip_safe=True, + maintainer='Alex Berg', + maintainer_email='aaberg333@gmail.com', + description='TODO: Package description', + license='MIT License', + tests_require=['pytest'], + entry_points={ + 'console_scripts': [ + 'costmapGenerator = alexblox.costmapGenerator:main', + 'visualizer = alexblox.visualizer:main', + ], + }, +) diff --git a/src/alexblox/test/test_copyright.py b/src/alexblox/test/test_copyright.py new file mode 100644 index 00000000..97a39196 --- /dev/null +++ b/src/alexblox/test/test_copyright.py @@ -0,0 +1,25 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# 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. + +from ament_copyright.main import main +import pytest + + +# Remove the `skip` decorator once the source file(s) have a copyright header +@pytest.mark.skip(reason='No copyright header has been placed in the generated source file.') +@pytest.mark.copyright +@pytest.mark.linter +def test_copyright(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found errors' diff --git a/src/alexblox/test/test_flake8.py b/src/alexblox/test/test_flake8.py new file mode 100644 index 00000000..27ee1078 --- /dev/null +++ b/src/alexblox/test/test_flake8.py @@ -0,0 +1,25 @@ +# Copyright 2017 Open Source Robotics Foundation, Inc. +# +# 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. + +from ament_flake8.main import main_with_errors +import pytest + + +@pytest.mark.flake8 +@pytest.mark.linter +def test_flake8(): + rc, errors = main_with_errors(argv=[]) + assert rc == 0, \ + 'Found %d code style errors / warnings:\n' % len(errors) + \ + '\n'.join(errors) diff --git a/src/alexblox/test/test_pep257.py b/src/alexblox/test/test_pep257.py new file mode 100644 index 00000000..b234a384 --- /dev/null +++ b/src/alexblox/test/test_pep257.py @@ -0,0 +1,23 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# 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. + +from ament_pep257.main import main +import pytest + + +@pytest.mark.linter +@pytest.mark.pep257 +def test_pep257(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found code style errors / warnings' diff --git a/src/apriltag/apriltag/apriltag_node.py b/src/apriltag/apriltag/apriltag_node.py index 553e7ae5..a857fd36 100644 --- a/src/apriltag/apriltag/apriltag_node.py +++ b/src/apriltag/apriltag/apriltag_node.py @@ -100,7 +100,7 @@ def reset_callback(self, request, response): # Publish transform if the tag is detected def postTransform(self, tag): if tag and (self.get_clock().now().to_msg().sec == tag.header.stamp.sec): - self.get_logger().info(str("Resetting the map -> odom TF")) + self.get_logger().info("Resetting the map -> odom TF", throttle_duration_sec=5) self.map_transform = tag return True return False diff --git a/src/apriltag/setup.py b/src/apriltag/setup.py index 231a4cb6..41537c3e 100644 --- a/src/apriltag/setup.py +++ b/src/apriltag/setup.py @@ -24,6 +24,6 @@ license="MIT License", tests_require=["pytest"], entry_points={ - "console_scripts": ["apriltag_node = apriltag.apriltag_node:main", "testingESDF = apriltag.testingESDF:main"], + "console_scripts": ["apriltag_node = apriltag.apriltag_node:main"], }, ) diff --git a/src/auger/auger/__init__.py b/src/auger/auger/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/auger/auger/auger_node.py b/src/auger/auger/auger_node.py new file mode 100644 index 00000000..d91f98c2 --- /dev/null +++ b/src/auger/auger/auger_node.py @@ -0,0 +1,583 @@ +# This ROS 2 node contains the code for the auger subsystem of the robot +import time +import math + +# Import the ROS 2 Python module +import rclpy +from rclpy.node import Node +from rclpy.executors import MultiThreadedExecutor +from rclpy.callback_groups import MutuallyExclusiveCallbackGroup + +# Import ROS 2 formatted message types + +# Import custom ROS 2 interfaces +from rovr_interfaces.srv import ( + MotorCommandSet, + MotorCommandGet, + AugerSetPushMotor, + SetScrewMotorSpeed, +) +from rovr_interfaces.srv import SetExtension +from rovr_interfaces.msg import Potentiometers +from std_srvs.srv import Trigger + + +class Auger(Node): + def __init__(self): + "Initialize the ROS 2 Auger node" + super().__init__("auger") + + # Callback group for anything that changes motor speeds + # There should not be a need for a separate one because all services + # should eventually terminate timely + self.service_cb_group = MutuallyExclusiveCallbackGroup() + + # TODO Define service clients here + self.cli_motor_set = self.create_client(MotorCommandSet, "motor/set") + self.cli_motor_get = self.create_client(MotorCommandGet, "motor/get") + + # Define parameters here + self.declare_parameter("POWER_LIMIT", 1) + self.declare_parameter("SCREW_SPEED", 4000) + self.declare_parameter( + "MAX_SCREW_SPEED", 4_000 + ) # in RPM for both negative and positive direction + self.declare_parameter("MIN_SCREW_DIG_SPEED", 2_000) + self.declare_parameter("MAX_SPIN_MOTOR_CURRENT", 0) + self.declare_parameter("push_motor_position", 0) + self.declare_parameter("tilt_actuator_position", 0) + # The error range to consider the current 0 + self.declare_parameter("TILT_ACTUATOR_CURRENT_THRESHOLD", 0) + # TODO: Find real value for this + self.declare_parameter("MAX_PUSH_MOTOR_POSITION", 0) + self.declare_parameter("MIN_PUSH_MOTOR_POSITION", 0) + self.declare_parameter("DEFAULT_PUSH_MOTOR_SPEED", 0) + self.declare_parameter("MAX_PUSH_MOTOR_CURRENT", 0) + self.declare_parameter("PUSH_MOTOR_POS_TOLERANCE", 0) + # Could potentially be faster + self.declare_parameter("MAX_RETRACT_PUSH_MOTOR_VELOCITY", -600) + self.declare_parameter("MAX_EXTEND_PUSH_MOTOR_VELOCITY", 600) + # Since we only care if the tilt actuator is fully extened or fully + # retracted then we should only need to care about the speed it moves + # make sure to verify direction of this velocity + self.declare_parameter("TILT_ACTUATOR_SPEED", 0) + # Minimum amount the tilt actuator needs to be extened to safely extend + # push motor + self.declare_parameter("TILT_ACTUATOR_MIN_EXTENSION", 0) + # Minimum amount the push motor needs to be retracted to safely retract + # tilt actuator + self.declare_parameter("PUSH_MOTOR_MIN_RETRACTION", 0) + # TODO Get real can ids + self.declare_parameter("TILT_ACTUATOR_ID", 0) + self.declare_parameter("PUSH_MOTOR_ID", 0) + self.declare_parameter("SPIN_MOTOR_ID", 0) + + # Local variables here + self.POWER_LIMIT = self.get_parameter("POWER_LIMIT").value + self.SCREW_SPEED = self.get_parameter("SCREW_SPEED").value + self.MAX_SCREW_SPEED = self.get_parameter("MAX_SCREW_SPEED").value + self.MIN_SCREW_DIG_SPEED = self.get_parameter("MIN_SCREW_DIG_SPEED").value + self.MAX_SPIN_MOTOR_CURRENT = self.get_parameter("MAX_SPIN_MOTOR_CURRENT").value + self.push_motor_position = self.get_parameter("push_motor_position").value + self.tilt_actuator_position = self.get_parameter("tilt_actuator_position").value + self.TILT_ACTUATOR_CURRENT_THRESHOLD = self.get_parameter( + "TILT_ACTUATOR_CURRENT_THRESHOLD" + ) + self.MAX_PUSH_MOTOR_POSITION = self.get_parameter( + "MAX_PUSH_MOTOR_POSITION" + ).value + self.MIN_PUSH_MOTOR_POSITION = self.get_parameter( + "MIN_PUSH_MOTOR_POSITION" + ).value + self.DEFAULT_PUSH_MOTOR_SPEED = self.get_parameter( + "DEFAULT_PUSH_MOTOR_SPEED" + ).value + self.MAX_PUSH_MOTOR_CURRENT = self.get_parameter("MAX_PUSH_MOTOR_CURRENT").value + self.PUSH_MOTOR_POS_TOLERANCE = self.get_parameter( + "PUSH_MOTOR_POS_TOLERANCE" + ).value + self.MAX_RETRACT_PUSH_MOTOR_VELOCITY = self.get_parameter( + "MAX_RETRACT_PUSH_MOTOR_VELOCITY" + ).value + self.MAX_EXTEND_PUSH_MOTOR_VELOCITY = self.get_parameter( + "MAX_EXTEND_PUSH_MOTOR_VELOCITY" + ).value + self.TILT_ACTUATOR_SPEED = self.get_parameter("TILT_ACTUATOR_SPEED").value + self.TILT_ACTUATOR_MIN_EXTENSION = self.get_parameter( + "TILT_ACTUATOR_MIN_EXTENSION" + ).value + self.PUSH_MOTOR_MIN_RETRACTION = self.get_parameter( + "PUSH_MOTOR_MIN_RETRACTION" + ).value + self.TILT_ACTUATOR_ID = self.get_parameter("TILT_ACTUATOR_ID").value + self.PUSH_MOTOR_ID = self.get_parameter("PUSH_MOTOR_ID").value + self.SPIN_MOTOR_ID = self.get_parameter("SPIN_MOTOR_ID").value + + # TODO Define services (methods callable from the outside) here + self.srv_set_tilt_extension = self.create_service( + SetExtension, + "auger/tilt_actuator/setExtension", + self.set_tilt_extension_callback, + callback_group=self.service_cb_group, + ) + + self.srv_stop_tilt = self.create_service( + Trigger, + "auger/tilt_actuator/stop", + self.stop_tilt_callback, + callback_group=self.service_cb_group, + ) + + self.srv_set_push_position = self.create_service( + AugerSetPushMotor, + "auger/push_motor/setPosition", + self.set_push_position_callback, + callback_group=self.service_cb_group, + ) + + self.srv_stop_push = self.create_service( + Trigger, + "auger/push_motor/stop", + self.stop_push_callback, + callback_group=self.service_cb_group, + ) + + self.srv_extend_push = self.create_service( + Trigger, + "auger/push_motor/extend", + self.extend_push_callback, + callback_group=self.service_cb_group, + ) + + self.srv_retract_push = self.create_service( + Trigger, + "auger/push_motor/retract", + self.retract_push_callback, + callback_group=self.service_cb_group, + ) + + self.srv_run_auger_spin = self.create_service( + SetScrewMotorSpeed, + "auger/screw/run", + self.run_auger_spin_velocity_callback, + callback_group=self.service_cb_group, + ) + + self.srv_stop_spin = self.create_service( + Trigger, + "auger/screw/stop", + self.stop_spin_callback, + callback_group=self.service_cb_group, + ) + + self.srv_extend_digger = self.create_service( + Trigger, + "auger/control/extend_digger", + self.extend_digger_callback, + callback_group=self.service_cb_group, + ) + + self.srv_retract_digger = self.create_service( + Trigger, + "auger/control/retract_digger", + self.retract_digger_callback, + callback_group=self.service_cb_group, + ) + + self.srv_retract_digger = self.create_service( + Trigger, + "auger/control/stop_all", + self.stop_all, + callback_group=self.service_cb_group, + ) + + # TODO Define subscribers here - need to subscribe to potentiometer + # readings? + self.potentiometer_sub = self.create_subscription( + Potentiometers, "potentiometer", self.push_motor_position_callback, 10 + ) + + # TODO Define publishers here + + # Define subsystem methods here + + def set_actuator_tilt_extension(self, tilt: bool) -> bool: + """ + Sets the auger tilt position of the actuator. True for extend, False for retract. + This method will spin until the actuator has hit a limit switch. + This will return false and do nothing if the push motor is currently extended. + Caller is responsible for timeouts. + """ + push_motor_pos_future = self.cli_motor_get.call_async( + MotorCommandGet.Request(type="position", can_id=self.PUSH_MOTOR_ID) + ) + rclpy.spin_until_future_complete(self, push_motor_pos_future) + push_motor_pos = push_motor_pos_future.result() + if not push_motor_pos.success: + self.get_logger().info( + "WARNING: Failed to move the tilt actuator because the push motor position could not be determined" + ) + return False + if push_motor_pos.data > self.PUSH_MOTOR_MIN_RETRACTION: + self.get_logger().info( + "WARNING: Failed to move the tilt actuator because the push motor is extended too far" + ) + return False + + if tilt: + self.get_logger().info("Extending tilt actuator") + else: + self.get_logger().info("Retracting tilt actuator") + + speed = self.TILT_ACTUATOR_SPEED * (1 if tilt else -1) + + motor_set_future = self.cli_motor_set.call_async( + MotorCommandSet.Request( + type="velocity", can_id=self.TILT_ACTUATOR_ID, value=speed + ) + ) + rclpy.spin_until_future_complete(self, motor_set_future) + if not motor_set_future.result().success: + self.get_logger().info("WARNING: Failed to set tilt motor velocity") + return False + + # gets motor current until it is 0 which means it has hit an limit + # switch + while True: + motor_get_future = self.cli_motor_get.call_async( + MotorCommandGet.Request( + type="current", + can_id=self.TILT_ACTUATOR_ID, + ) + ) + rclpy.spin_until_future_complete(self, motor_get_future) + + if motor_get_future.result().success: + if ( + abs(motor_get_future.result().data) + < self.TILT_ACTUATOR_CURRENT_THRESHOLD + ): + break + else: + self.get_logger().info("WARNING: Failed to read tilt actuator position") + + time.sleep(0.1) + + return True + + def stop_actuator_tilt(self) -> bool: + """Stop the auger angular position of the auger motor.""" + self.get_logger().info("Stopping tilt actuator") + motor_set_future = self.cli_motor_set.call_async( + MotorCommandSet.Request( + type="duty_cycle", + can_id=self.TILT_ACTUATOR_ID, + value=0.0, + ) + ) + rclpy.spin_until_future_complete(self, motor_set_future) + return motor_set_future.result().success + + def set_motor_push_position( + self, speed: float, desired_position: float, power_limit: float + ) -> bool: + """ + Set the target position of the motor that pushes the auger into the ground. + This will spin until the motor reaches given setpoint. + This will fail if the screw is not spinning fast enough + Caller is responsible for timeouts. + """ + if self.tilt_actuator_position < self.TILT_ACTUATOR_MIN_EXTENSION: + self.get_logger().warn( + "WARNING: Push motor will not move because the tilt actuator is not extended" + ) + return False + + get_screw_speed_future = self.cli_motor_get.call_async( + MotorCommandGet.Request( + type="velocity", + can_id=self.SPIN_MOTOR_ID, + ) + ) + rclpy.spin_until_future_complete(self, get_screw_speed_future) + + if not get_screw_speed_future.result().success: + self.get_logger().warn( + "WARNING: Push motor will not move because the screw speed could not be determined" + ) + return False + elif get_screw_speed_future.result().data < self.MIN_SCREW_DIG_SPEED: + self.get_logger().warn( + "WARNING: Push motor will not move because the screw is not spinning fast enough" + ) + return False + + if ( + desired_position > self.MAX_PUSH_MOTOR_POSITION + or desired_position < self.MIN_PUSH_MOTOR_POSITION + ): + self.get_logger().warn( + f"WARNING: Requested push motor position is out of range, clamping value; requested: {desired_position}" + ) + desired_position = max( + self.MIN_PUSH_MOTOR_POSITION, + min(desired_position, self.MAX_PUSH_MOTOR_POSITION), + ) # clamp the value to be within range + self.get_logger().info( + "Setting auger push motor position to: " + str(desired_position) + ) + + motor_set_future = self.cli_motor_set.call_async( + MotorCommandSet.Request( + type="velocity", + power_limit=power_limit, + can_id=self.PUSH_MOTOR_ID, + value=float(speed), + ) + ) + rclpy.spin_until_future_complete(self, motor_set_future) + + if not motor_set_future.result().success: + self.get_logger().warn("WARNING: Failed to set push motor voltage") + return False + + # wait till motor reaches desired position + while True: + motor_get_pos_future = self.cli_motor_get.call_async( + MotorCommandGet.Request( + type="position", + can_id=self.PUSH_MOTOR_ID, + ) + ) + rclpy.spin_until_future_complete(self, motor_get_pos_future) + + if motor_get_pos_future.result().success: + current_pos = motor_get_pos_future.result().data + if (speed <= 0 and current_pos <= desired_position) or ( + speed > 0 and current_pos >= desired_position + ): + break + else: + self.get_logger().warn("WARNING: Failed to read push motor position") + + time.sleep(0.1) + + stop_motor_future = self.cli_motor_set.call_async( + MotorCommandSet.Request( + type="velocity", + power_limit=power_limit, + can_id=self.PUSH_MOTOR_ID, + value=0, + ) + ) + stop_motor_response = rclpy.spin_until_future_complete(self, stop_motor_future) + if not stop_motor_response.result().success: + self.get_logger().warn("WARNING: Failed to stop the auger screw") + return False + + return True + + def extend_motor_push(self) -> bool: + """ + Extends the push motor at max extend speed, returns false + if it is not able to due to the tilt actuator not being fully extended. + """ + return self.set_motor_push_position( + self.DEFAULT_PUSH_MOTOR_SPEED, self.MAX_PUSH_MOTOR_POSITION, 0.5 + ) + + def retract_motor_push(self) -> bool: + """ + Run the push motor at max retract speed. + Returns false if it is not able to due to the tilt actuator not being fully extended. + """ + return self.set_motor_push_position( + -self.DEFAULT_PUSH_MOTOR_SPEED, self.MIN_PUSH_MOTOR_POSITION, 0.5 + ) + + def stop_motor_push(self) -> bool: + """Stop the motor that pushes the auger into the ground.""" + motor_set_future = self.cli_motor_set.call_async( + MotorCommandSet.Request( + type="duty_cycle", + can_id=self.PUSH_MOTOR_ID, + value=0.0, + ) + ) + rclpy.spin_until_future_complete(self, motor_set_future) + return motor_set_future.result().success + + def run_auger_spin_velocity(self, desired_speed: float, power_limit: float) -> bool: + """Set the auger spin velocity of the auger motor.""" + if abs(desired_speed) > self.MAX_SCREW_SPEED: + self.get_logger().info( + f"WARNING: Requested auger screw speed is too fast: {desired_speed}" + ) + # This is the same as signum(desired_speed) * self.MAX_SCREW_SPEED + desired_speed = math.copysign(self.MAX_SCREW_SPEED, desired_speed) + + self.get_logger().info(f"Running auger spin at velocity: {desired_speed}") + + motor_set_future = self.cli_motor_set.call_async( + MotorCommandSet.Request( + type="velocity", + can_id=self.SPIN_MOTOR_ID, + value=float(desired_speed), + power_limit=power_limit, + ) + ) + rclpy.spin_until_future_complete(self, motor_set_future) + return motor_set_future.result().success + + def stop_auger_spin(self) -> bool: + """Stop the auger motor from spinning.""" + self.get_logger().info("Stopping auger spin") + + motor_set_future = self.cli_motor_set.call_async( + MotorCommandSet.Request( + type="duty_cycle", + can_id=self.SPIN_MOTOR_ID, + value=0.0, + ) + ) + rclpy.spin_until_future_complete(self, motor_set_future) + return motor_set_future.result().success + + def extend_digger(self) -> bool: + """Tilt and extend""" + + tilt_success = self.set_actuator_tilt_extension(True) + if not tilt_success: + return False + + spin_success = self.run_auger_spin_velocity(self.SCREW_SPEED, self.POWER_LIMIT) + + if not spin_success: + return False + + extend_success = self.extend_motor_push() + if not extend_success: + return False + + return True + + def retract_digger(self) -> bool: + """Tilt and retract""" + + retract_success = self.retract_motor_push() + if not retract_success: + return False + + spin_success = self.stop_auger_spin() + if not spin_success: + return False + + tilt_success = self.set_actuator_tilt_extension(False) + if not tilt_success: + return False + + return True + + def stop_all(self) -> bool: + # This does not short circit but still returns false if any do + return ( + self.stop_actuator_tilt() & self.stop_auger_spin() & self.stop_motor_push() + ) + + # TODO Define service callback methods here + def set_tilt_extension_callback(self, request, response): + """This service request sets position of the angular motor.""" + response.success = self.set_actuator_tilt_extension(request.extension) + return response + + def stop_tilt_callback(self, request, response): + """This service request stops the angular motor.""" + self.stop_actuator_tilt() + response.success = True + return response + + def set_push_position_callback(self, request, response): + """ + This service request sets position of the motor that pushes the auger into the ground. + It will fail if the tilt actuator is not fully extended + """ + response.success = self.set_motor_push_position( + request.speed, request.position, request.power_limit + ) + return response + + def stop_push_callback(self, request, response): + """This service request stops the motor that pushes the auger into the ground.""" + response.success = self.stop_motor_push() + return response + + def run_auger_spin_velocity_callback(self, request, response): + """This service request sets the turn velocity of the auger""" + response.success = self.run_auger_spin_velocity( + request.speed, request.power_limit + ) + return response + + def stop_spin_callback(self, request, response): + """This service request stops the motor that spins the auger.""" + response.success = self.stop_auger_spin() + return response + + def push_motor_position_callback(self, msg): + self.tilt_actuator_position = msg.data + + def extend_push_callback(self, request, response): + """ + This service requests to extend the push motor at full speed. + It will fail if the tilt actuator is not fully extended + """ + response.success = self.extend_motor_push() + return response + + def retract_push_callback(self, request, response): + """ + This service requests to retract the push motor at full speed. + It will fail if the tilt actuator is not fully extended + """ + response.success = self.retract_motor_push() + return response + + def extend_digger_callback(self, request, response): + """This service will both tilt and extend the auger.""" + response.success = self.extend_digger() + return response + + def retract_digger_callback(self, request, response): + """This service will both tilt and retract the auger.""" + response.success = self.retract_digger() + return response + + def stop_all_callback(self, request, response): + """This Service will stop all three motors""" + response.success = self.stop_all() + return response + + +def main(args=None): + """The main function.""" + rclpy.init(args=args) + + node = Auger() + executor = MultiThreadedExecutor() + executor.add_node(node) + + node.get_logger().info("Initializing the Auger subsystem!") + try: + executor.spin() + # TODO this exception needed? Could this ever happen? + except KeyboardInterrupt: + node.get_logger().info("Shutting down the Auger subsystem.") + finally: + node.destroy_node() + + rclpy.shutdown() + + +# This code does NOT run if this file is imported as a module +if __name__ == "__main__": + main() diff --git a/src/digger/package.xml b/src/auger/package.xml similarity index 89% rename from src/digger/package.xml rename to src/auger/package.xml index ae5a1dd1..95ef8760 100644 --- a/src/digger/package.xml +++ b/src/auger/package.xml @@ -1,9 +1,9 @@ - digger + auger 0.2.0 - This package is for the digger subsystem. + This package is for the auger subsystem. Anthony Brogni MIT License diff --git a/src/auger/resource/auger b/src/auger/resource/auger new file mode 100644 index 00000000..e69de29b diff --git a/src/auger/setup.cfg b/src/auger/setup.cfg new file mode 100644 index 00000000..a04f6278 --- /dev/null +++ b/src/auger/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script_dir=$base/lib/auger +[install] +install_scripts=$base/lib/auger diff --git a/src/digger/setup.py b/src/auger/setup.py similarity index 82% rename from src/digger/setup.py rename to src/auger/setup.py index 37752556..e26bdd3c 100644 --- a/src/digger/setup.py +++ b/src/auger/setup.py @@ -2,7 +2,7 @@ from glob import glob from setuptools import setup -package_name = "digger" +package_name = "auger" setup( name=package_name, @@ -17,12 +17,12 @@ zip_safe=True, maintainer="Anthony", maintainer_email="anthonybrog@gmail.com", - description="This package is for the digger subsystem.", + description="This package is for the auger subsystem.", license="MIT License", tests_require=["pytest"], entry_points={ "console_scripts": [ - "digger_node = digger.digger_node:main", + "auger_node = auger.auger_node:main", ], }, ) diff --git a/src/digger/test/test_copyright.py b/src/auger/test/test_copyright.py similarity index 100% rename from src/digger/test/test_copyright.py rename to src/auger/test/test_copyright.py diff --git a/src/digger/test/test_flake8.py b/src/auger/test/test_flake8.py similarity index 94% rename from src/digger/test/test_flake8.py rename to src/auger/test/test_flake8.py index 49c1644f..ee79f31a 100644 --- a/src/digger/test/test_flake8.py +++ b/src/auger/test/test_flake8.py @@ -20,4 +20,6 @@ @pytest.mark.linter def test_flake8(): rc, errors = main_with_errors(argv=[]) - assert rc == 0, "Found %d code style errors / warnings:\n" % len(errors) + "\n".join(errors) + assert rc == 0, "Found %d code style errors / warnings:\n" % len( + errors + ) + "\n".join(errors) diff --git a/src/digger/test/test_pep257.py b/src/auger/test/test_pep257.py similarity index 100% rename from src/digger/test/test_pep257.py rename to src/auger/test/test_pep257.py diff --git a/src/behaviortree b/src/behaviortree new file mode 160000 index 00000000..6c6aa078 --- /dev/null +++ b/src/behaviortree @@ -0,0 +1 @@ +Subproject commit 6c6aa078ee7bc52fec98984bed4964556abf5beb diff --git a/src/behaviortree_cpp b/src/behaviortree_cpp new file mode 160000 index 00000000..14589e58 --- /dev/null +++ b/src/behaviortree_cpp @@ -0,0 +1 @@ +Subproject commit 14589e589c5049fbb7f1fb30ccaa3efc66c84f18 diff --git a/src/behaviortree_executor/CMakeLists.txt b/src/behaviortree_executor/CMakeLists.txt new file mode 100644 index 00000000..ca780f58 --- /dev/null +++ b/src/behaviortree_executor/CMakeLists.txt @@ -0,0 +1,48 @@ +cmake_minimum_required(VERSION 3.8) +project(behaviortree_executor) + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +# find dependencies +find_package(ament_cmake REQUIRED) +find_package(behaviortree_cpp REQUIRED) +find_package(behaviortree_ros2 REQUIRED) +find_package(rclcpp REQUIRED) +find_package(rovr_interfaces REQUIRED) +find_package(std_srvs REQUIRED) +find_package(action_msgs REQUIRED) + +add_executable(behaviortree_executor src/behaviortree_executor.cpp) +ament_target_dependencies(behaviortree_executor rclcpp std_srvs behaviortree_ros2 behaviortree_cpp rovr_interfaces) + +install(DIRECTORY + behavior_trees + config + launch + DESTINATION share/${PROJECT_NAME}/ +) + +install(TARGETS + behaviortree_executor + DESTINATION lib/${PROJECT_NAME} +) + +target_include_directories(behaviortree_executor PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/include + ${CMAKE_CURRENT_SOURCE_DIR}/src/action) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + # the following line skips the linter which checks for copyrights + # comment the line when a copyright and license is added to all source files + set(ament_cmake_copyright_FOUND TRUE) + # the following line skips cpplint (only works in a git repo) + # comment the line when this package is in a git repo and when + # a copyright and license is added to all source files + set(ament_cmake_cpplint_FOUND TRUE) + ament_lint_auto_find_test_dependencies() +endif() + +ament_package() diff --git a/src/behaviortree_executor/behavior_trees/main_tree.btproj b/src/behaviortree_executor/behavior_trees/main_tree.btproj new file mode 100644 index 00000000..8ce5793c --- /dev/null +++ b/src/behaviortree_executor/behavior_trees/main_tree.btproj @@ -0,0 +1,311 @@ + + + + + + + Error code + Error codes to check, user defined + + + Allowed time for spinning + If true recovery count will be incremented + Service name + Server timeout + Assisted teleop error code + + + Name of the auto dig action server + Distance to backup after digging + + + Name of the auto offload action server + + + Distance to backup + Speed at which to backup + Allowed time for reversing + Server name + Server timeout + Disable collision checking + "Back up error code" + + + Name of the calibrate field coordinates action server + + + Name of the return to coordinate action server + X coordinate to return to + Y coordinate to return to + + + Name of the ROS2 action server to cancel all goals on + + + Server name to cancel the assisted teleop behavior + Server timeout + + + Server name to cancel the backup behavior + Server timeout + + + Server name to cancel the controller server + Server timeout + + + Service name to cancel the drive on heading behavior + Server timeout + + + Server name to cancel the spin behavior + Server timeout + + + Server name to cancel the wait behavior + Server timeout + + + Service name + Server timeout + Distance from the robot under which obstacles are cleared + + + Service name + Server timeout + Distance from the robot above which obstacles are cleared + + + Service name + Server timeout + + + Destinations to plan through + Start pose of the path if overriding current robot pose + Server name + Server timeout + Mapped name to the planner plugin type to use + Path created by ComputePathToPose node + "Compute path through poses error code" + + + Destination to plan to + Start pose of the path if overriding current robot pose + Mapped name to the planner plugin type to use + Server name + Server timeout + Path created by ComputePathToPose node + "Compute path to pose error code" + + + Name of the topic to receive controller selection commands + Default controller of the controller selector + Name of the selected controller received from the topic subcription + + + Distance + Reference frame + Robot base frame + + + Distance to check if passed + reference frame to check in + Robot frame to check relative to global_frame + + + Distance to travel + Speed at which to travel + Allowed time for reversing + Server name + Server timeout + Disable collision checking + "Drive on heading error code" + + + + Path to follow + Goal checker + Progress checker + Service name + Server timeout + Follow Path error code + + + Vector of navigation goals + Navigation goal + + + Name of the go to dig location action server + X coordinate of the dig location + Y coordinate of the dig location + + + Name of the topic to receive goal checker selection commands + Default goal checker of the controller selector + Name of the selected goal checker received from the topic subcription + + + Destination + Reference frame + Robot base frame + + + Vector of navigation goals + Navigation goal + + + Vector of navigation goals + Navigation goal + + + Original goal in + Output goal set by subscription + + + SUCCESS if initial_pose_received true + + + Topic for battery info + + + Min battery % or voltage before triggering + Topic for battery info + Bool if check based on voltage or total % + + + Path to validate + Server timeout + + + Velocity threshold below which robot is considered stopped + Duration (ms) the velocity must remain below the threshold + + + + Goals + Server name + Server timeout + Behavior tree to run + Navigate through poses error code + + + Goal + Server name + Server timeout + Behavior tree to run + Navigate to pose error code + + + Time to check if expired + Check if path has been updated to enable timer reset + + + Planned Path + Proximity length (m) for the path to be longer on approach + Length multiplication factor to check if the path is significantly longer + + + + Name of the topic to receive planner selection commands + Default planner of the planner selector + Name of the selected planner received from the topic subcription + + + Name of the topic to receive progress checker selection commands + Default progress checker of the controller selector + Name of the selected progress checker received from the topic subcription + + + Rate + + + Number of retries + + + Service name + Server timeout + + + Costmap service name responsible for getting the cost + A vector of goals to check if in collision + Whether to use the footprint cost or the point cost. + The cost threshold above which a waypoint is considered in collision and should be removed. + A vector of goals containing only those that are not in collision. + + + Input goals to remove if passed + Radius tolerance on a goal to consider it passed + Global frame + Robot base frame + Set of goals after removing any passed + + + + + + Path to be smoothed + Maximum smoothing duration + Bool if collision check should be performed + Smoothed path + Smoothing duration + True if smoothing was not interrupted by time limit + + + Name of the topic to receive smoother selection commands + Default smoother of the smoother selector + Name of the selected smoother received from the topic subcription + + + Minimum rate + Maximum rate + Minimum speed + Maximum speed + Vector of navigation goals + Navigation goal + + + Spin distance + Allowed time for spinning + Server name + Server timeout + Disable collision checking + Spin error code + + + Time to check if expired + + + Child frame for transform + Parent frame for transform + + + Distance before goal to truncate + Path to truncate + Truncated path to utilize + + + Distance in forward direction + Distance in backward direction + Robot base frame id + Transform lookup tolerance + Manually specified pose to be used if overriding current robot pose + Weight of angular distance relative to positional distance when finding which path pose is closest to robot. Not applicable on paths without orientations assigned + Maximum forward integrated distance along the path (starting from the last detected pose) to bound the search for the closest pose to the robot. When set to infinity (default), whole path is searched every time + Truncated path to utilize + + + Wait time + Server name + Server timeout + + + Error code + + + Error code + + + Error code + + + diff --git a/src/behaviortree_executor/behavior_trees/main_tree.xml b/src/behaviortree_executor/behavior_trees/main_tree.xml new file mode 100644 index 00000000..5b011ccf --- /dev/null +++ b/src/behaviortree_executor/behavior_trees/main_tree.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + +