From 9017024707db930a31ae43f155853fa24e63f7b9 Mon Sep 17 00:00:00 2001 From: Rooholla-Khorrambakht Date: Fri, 3 May 2024 20:51:20 -0400 Subject: [PATCH] go2py devcontainer is added --- .devcontainer/Dockerfile | 31 --- .devcontainer/devcontainer.json | 72 ++++++- .devcontainer/docker-compose.yaml | 17 -- .flake8 | 2 + .gitattributes | 10 + container_provisioning/README.md | 6 + .../scripts/init-directory.sh | 23 +++ .../scripts/install-docker.sh | 48 +++++ .../scripts/install-nvidia-driver.sh | 24 +++ .../scripts/install-tools.sh | 34 ++++ container_provisioning/system-setup.sh | 8 + docker/Dockerfile.x86 | 65 ++++++ docker/build.sh | 12 ++ docker/config/docker.bashrc | 149 ++++++++++++++ docker/scripts/build-librealsense.sh | 188 ++++++++++++++++++ docker/scripts/hotplug-realsense.sh | 79 ++++++++ docker/scripts/install-3rdparty.sh | 74 +++++++ docker/scripts/install-clang.sh | 23 +++ docker/scripts/install-python-requirements.sh | 12 ++ .../scripts/install-realsense-dependencies.sh | 24 +++ docker/scripts/install-ros2.sh | 32 +++ docker/scripts/install-rosdep.sh | 10 + docker/scripts/install-tools.sh | 23 +++ docker/scripts/install-unitree-ros2.sh | 16 ++ docker/scripts/workspace-entrypoint.sh | 21 ++ .../tao/tao-converter-aarch64-tensorrt8.4.zip | Bin 0 -> 35923 bytes .../99-realsense-libusb-custom.rules | 11 + 27 files changed, 956 insertions(+), 58 deletions(-) delete mode 100644 .devcontainer/Dockerfile delete mode 100644 .devcontainer/docker-compose.yaml create mode 100644 .flake8 create mode 100644 .gitattributes create mode 100644 container_provisioning/README.md create mode 100755 container_provisioning/scripts/init-directory.sh create mode 100755 container_provisioning/scripts/install-docker.sh create mode 100755 container_provisioning/scripts/install-nvidia-driver.sh create mode 100755 container_provisioning/scripts/install-tools.sh create mode 100755 container_provisioning/system-setup.sh create mode 100644 docker/Dockerfile.x86 create mode 100644 docker/build.sh create mode 100644 docker/config/docker.bashrc create mode 100644 docker/scripts/build-librealsense.sh create mode 100755 docker/scripts/hotplug-realsense.sh create mode 100644 docker/scripts/install-3rdparty.sh create mode 100644 docker/scripts/install-clang.sh create mode 100644 docker/scripts/install-python-requirements.sh create mode 100644 docker/scripts/install-realsense-dependencies.sh create mode 100644 docker/scripts/install-ros2.sh create mode 100755 docker/scripts/install-rosdep.sh create mode 100644 docker/scripts/install-tools.sh create mode 100644 docker/scripts/install-unitree-ros2.sh create mode 100755 docker/scripts/workspace-entrypoint.sh create mode 100644 docker/tao/tao-converter-aarch64-tensorrt8.4.zip create mode 100644 docker/udev_rules/99-realsense-libusb-custom.rules diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile deleted file mode 100644 index 64887d8..0000000 --- a/.devcontainer/Dockerfile +++ /dev/null @@ -1,31 +0,0 @@ -FROM ros:humble -ENV DEBIAN_FRONTEND=noninteractive -SHELL ["/bin/bash", "-c"] -RUN apt-get update && apt-get install -y -qq --no-install-recommends \ - libglvnd-dev \ - libgl1-mesa-dev \ - libegl1-mesa-dev \ - libgles2-mesa-dev \ - libxext6 \ - libx11-6 \ - freeglut3-dev \ - git \ - python3-pip \ - ros-humble-rmw-cyclonedds-cpp ros-humble-rosidl-generator-dds-idl \ - libyaml-cpp-dev \ - ros-humble-xacro \ - libboost-all-dev\ - build-essential \ - cmake \ - && rm -rf /var/lib/apt/lists/* - - -RUN pip3 install mujoco pin matplotlib - -RUN cd / && git clone https://github.com/unitreerobotics/unitree_ros2 && cd /unitree_ros2/cyclonedds_ws/src && \ -git clone https://github.com/ros2/rmw_cyclonedds -b humble && git clone https://github.com/eclipse-cyclonedds/cyclonedds -b releases/0.10.x &&\ -cd .. && colcon build --packages-select cyclonedds && source /opt/ros/humble/setup.bash && colcon build - -# Env vars for the nvidia-container-runtime. -ENV NVIDIA_VISIBLE_DEVICES all -ENV NVIDIA_DRIVER_CAPABILITIES graphics,utility,compute \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 14e7378..02bdfc8 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,13 +1,65 @@ -// https://containers.dev/implementors/json_reference/ { - "name": "go2py", - "dockerComposeFile": "docker-compose.yaml", - "workspaceFolder": "/home/Go2Py", - "service": "go2py-dev", - "remoteUser": "root", - "customizations": { - "vscode": { - "extensions": ["dbaeumer.vscode-eslint"] + "name": "go2-devcontainer", + "build": { + "context": "..", + "dockerfile": "../docker/build.sh" + }, + "containerEnv": { + "DISPLAY": "unix:0", + "ROS_LOCALHOST_ONLY": "0", + "ROS_AUTOMATIC_DISCOVERY_RANGE": "SUBNET", + "ROS_DOMAIN_ID": "0", + "SHELL": "/bin/bash" + }, + "runArgs": [ + "--privileged", + "--gpus", + "all", + "-v", + "/dev:/dev", + "--net=host", + "-e", + "DISPLAY=${env:DISPLAY}", + "--ulimit=core=-1" + ], + "mounts": [ + "source=/tmp/.X11-unix,target=/tmp/.X11-unix,type=bind,consistency=cached", + "source=/dev/dri,target=/dev/dri,type=bind,consistency=cached", + "source=${localEnv:HOME}/go2-devcontainer/logging/,target=/workspace/logging,type=bind,consistency=cached", + "source=${localEnv:HOME}/go2-devcontainer/.bash_history,target=/home/dev/.bash_history,type=bind,consistency=cached", + ], + "workspaceMount": "source=${localWorkspaceFolder},target=/workspace/go2-devcontainer/src/go2-devcontainer,type=bind", + "workspaceFolder": "/workspace/go2-devcontainer/src", + "customizations": { + "vscode": { + "settings": { + "extensions.verifySignature": false + }, + "extensions": [ + "cheshirekow.cmake-format", + "llvm-vs-code-extensions.vscode-clangd", + "ms-azuretools.vscode-docker", + "ms-iot.vscode-ros", + "ms-python.black-formatter", + "ms-python.flake8", + "ms-python.isort", + "ms-python.pylint", + "ms-python.python", + "ms-toolsai.jupyter", + "ms-vscode.cmake-tools", + "mutantdino.resourcemonitor", + "vadimcn.vscode-lldb", + "xaver.clang-format" + ] + } + }, + "forwardPorts": [ + 3000 + ], + "portsAttributes": { + "3000": { + "label": "Application", + "onAutoForward": "notify" + } } - } } \ No newline at end of file diff --git a/.devcontainer/docker-compose.yaml b/.devcontainer/docker-compose.yaml deleted file mode 100644 index 54d71e4..0000000 --- a/.devcontainer/docker-compose.yaml +++ /dev/null @@ -1,17 +0,0 @@ -version: "3.9" -services: - go2py-dev: - build: . - container_name: go2py-dev - network_mode: host - privileged: true - command: bash - volumes: - - /tmp/.X11-unix:/tmp/.X11-unix - - ..:/home/Go2Py - environment: - - DISPLAY=${DISPLAY} - - QT_X11_NO_MITSHM=1 - runtime: nvidia - stdin_open: true - tty: true \ No newline at end of file diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..bf750d2 --- /dev/null +++ b/.flake8 @@ -0,0 +1,2 @@ +[flake8] +max-line-length = 99 \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..43751c6 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,10 @@ +*.dae filter=lfs diff=lfs merge=lfs -text +*.jpeg filter=lfs diff=lfs merge=lfs -text +*.jpg filter=lfs diff=lfs merge=lfs -text +*.obj filter=lfs diff=lfs merge=lfs -text +*.pcd filter=lfs diff=lfs merge=lfs -text +*.png filter=lfs diff=lfs merge=lfs -text +*.so filter=lfs diff=lfs merge=lfs -text +*.so.* filter=lfs diff=lfs merge=lfs -text +*.stl filter=lfs diff=lfs merge=lfs -text +*.STL filter=lfs diff=lfs merge=lfs -text \ No newline at end of file diff --git a/container_provisioning/README.md b/container_provisioning/README.md new file mode 100644 index 0000000..1a97807 --- /dev/null +++ b/container_provisioning/README.md @@ -0,0 +1,6 @@ +# Container Provisioning + +Container provisioning contains scripts to set up a PC ready for deployment. + +- System environemnt setup +- Logging directory creation diff --git a/container_provisioning/scripts/init-directory.sh b/container_provisioning/scripts/init-directory.sh new file mode 100755 index 0000000..4079bfa --- /dev/null +++ b/container_provisioning/scripts/init-directory.sh @@ -0,0 +1,23 @@ +#!/bin/bash +set -e + +REQUIRED_DIRS=( + "$HOME/go2-devcontainer/logging/coredump" + "$HOME/go2-devcontainer/logging/logs" + "$HOME/go2-devcontainer/logging/data" + "$HOME/go2-devcontainer/logging/plan/data/scene" + "$HOME/go2-devcontainer/logging/plan/data/plan_results" + "$HOME/go2-devcontainer/models/yolov8" +) + +for dir in "${REQUIRED_DIRS[@]}"; do + if [ ! -d "$dir" ]; then + echo "Creating directory $dir" + mkdir -p $dir + fi +done + +if [ ! -f "$HOME/go2-devcontainer/.bash_history" ]; then + echo "Creating file $HOME/go2-devcontainer/.bash_history" + touch $HOME/go2-devcontainer/.bash_history +fi \ No newline at end of file diff --git a/container_provisioning/scripts/install-docker.sh b/container_provisioning/scripts/install-docker.sh new file mode 100755 index 0000000..a0e61c9 --- /dev/null +++ b/container_provisioning/scripts/install-docker.sh @@ -0,0 +1,48 @@ +#!/bin/bash +set -e + +if ! [ -x "$(command -v curl)" ]; then + sudo apt-get install curl +fi + +# If docker is not installed, install it +if ! [ -x "$(command -v docker)" ]; then + echo "Docker is not installed. Installing Docker" + curl -fsSL https://get.docker.com -o get-docker.sh + sudo sh get-docker.sh + + # Post-installation steps: https://docs.docker.com/engine/install/linux-postinstall/ + if ! [ $(getent group docker) ]; then + sudo groupadd docker + fi + sudo usermod -aG docker $USER + + echo "==============================================" + echo "Docker installed. Please restart your computer." + echo "==============================================" + exit 1 +fi + +# Check if docker is running +if ! systemctl is-active --quiet docker; then + echo "Docker is not running. Please start Docker and run this script again." + exit 1 +fi + +# If nvidia-container-toolkit is not installed, install it +# Follow https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html#installing-with-apt +if ! [ -x "$(command -v nvidia-container-toolkit)" ]; then + # Install nvidia-container-toolkit + curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg \ + && curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \ + sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \ + sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list + sudo apt-get update + sudo apt-get install -y nvidia-container-toolkit + # Configure nvidia-container-toolkit + sudo nvidia-ctk runtime configure --runtime=docker + sudo systemctl restart docker + nvidia-ctk runtime configure --runtime=docker --config=$HOME/.config/docker/daemon.json + systemctl --user restart docker + sudo nvidia-ctk config --set nvidia-container-cli.no-cgroups --in-place +fi \ No newline at end of file diff --git a/container_provisioning/scripts/install-nvidia-driver.sh b/container_provisioning/scripts/install-nvidia-driver.sh new file mode 100755 index 0000000..951ce5d --- /dev/null +++ b/container_provisioning/scripts/install-nvidia-driver.sh @@ -0,0 +1,24 @@ +#!/bin/bash +set -e + +if ! command -v nvidia-smi &> /dev/null +then + echo "Installing NVIDIA driver" +else + echo "NVIDIA driver is already installed" + exit 0 +fi + +# Prompt user to confirm to install NVIDIA driver 535 +NVIDIA_DRIVER_VERSION=535 +while true; do + read -p "Do you want to install NVIDIA driver ${NVIDIA_DRIVER_VERSION}? (y/n)" yn + case $yn in + [Yy]* ) break;; + [Nn]* ) echo "Please install NVIDIA driver manually!"; exit 1;; + * ) echo "Please answer yes or no.";; + esac +done + +sudo apt-get update +sudo apt-get install -y nvidia-driver-${NVIDIA_DRIVER_VERSION} \ No newline at end of file diff --git a/container_provisioning/scripts/install-tools.sh b/container_provisioning/scripts/install-tools.sh new file mode 100755 index 0000000..b0085d7 --- /dev/null +++ b/container_provisioning/scripts/install-tools.sh @@ -0,0 +1,34 @@ +#!/bin/bash +set -e + +if ! command -v git-lfs &> /dev/null +then + echo "Installing git-lfs" + sudo apt-get update + sudo apt-get install \ + git-lfs + echo "==============================================" + echo "git-lfs is installed. Please remove cloned go2-devcontainer and re-clone it." + echo "==============================================" + exit 1 +else + echo "git-lfs is already installed" +fi + +# # If foxglove-studio is not installed, install it +# if ! command -v foxglove-studio &> /dev/null +# then +# echo "Installing foxglove-studio" +# sudo snap install foxglove-studio +# else +# echo "foxglove-studio is already installed" +# fi + +# If VSCode is not installed, install it +if ! command -v code &> /dev/null +then + echo "Installing VSCode" + sudo snap install code --classic +else + echo "VSCode is already installed" +fi diff --git a/container_provisioning/system-setup.sh b/container_provisioning/system-setup.sh new file mode 100755 index 0000000..4e2538b --- /dev/null +++ b/container_provisioning/system-setup.sh @@ -0,0 +1,8 @@ +#!/bin/bash +set -e + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +${SCRIPT_DIR}/scripts/install-tools.sh +${SCRIPT_DIR}/scripts/install-nvidia-driver.sh +${SCRIPT_DIR}/scripts/install-docker.sh +${SCRIPT_DIR}/scripts/init-directory.sh \ No newline at end of file diff --git a/docker/Dockerfile.x86 b/docker/Dockerfile.x86 new file mode 100644 index 0000000..d7e3b95 --- /dev/null +++ b/docker/Dockerfile.x86 @@ -0,0 +1,65 @@ +FROM nvidia/cuda:12.1.1-devel-ubuntu22.04 + +SHELL ["/bin/bash", "-c"] + +# Install dependencies +RUN --mount=type=cache,target=/var/cache/apt \ + --mount=type=bind,source=docker/scripts/install-tools.sh,target=/tmp/install-tools.sh \ + bash /tmp/install-tools.sh +RUN --mount=type=cache,target=/var/cache/apt \ + --mount=type=bind,source=docker/scripts/install-ros2.sh,target=/tmp/install-ros2.sh \ + bash /tmp/install-ros2.sh +RUN --mount=type=cache,target=/var/cache/apt \ + --mount=type=bind,source=docker/scripts/install-clang.sh,target=/tmp/install-clang.sh \ + bash /tmp/install-clang.sh +RUN --mount=type=cache,target=/root/.cache/pip \ + --mount=type=bind,source=docker/scripts/install-python-requirements.sh,target=/tmp/install-python-requirements.sh \ + bash /tmp/install-python-requirements.sh +RUN --mount=type=cache,target=/var/cache/apt \ + --mount=type=bind,source=docker/scripts/install-3rdparty.sh,target=/tmp/install-3rdparty.sh \ + bash /tmp/install-3rdparty.sh + +# Last installation layer as we update ROS dependencies often +RUN --mount=type=cache,target=/var/cache/apt \ + --mount=type=bind,source=.,target=/tmp/ros_ws \ + /tmp/ros_ws/docker/scripts/install-rosdep.sh + +# Clean cache +RUN apt-get autoremove -y \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Create a non-root user +ARG USERNAME=dev +ARG USER_UID=1000 +ARG USER_GID=$USER_UID +RUN groupadd --gid $USER_GID $USERNAME \ + && useradd --uid $USER_UID --gid $USER_GID -m $USERNAME \ + && apt-get update \ + && apt-get install -y sudo \ + && echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \ + && chmod 0440 /etc/sudoers.d/$USERNAME \ + && apt-get autoremove -y \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Create a workspace directory +RUN mkdir -p /workspace/go2-devcontainer/src/go2-devcontainer \ + && cd /workspace/go2-devcontainer \ + && chown -R $USERNAME:$USERNAME /workspace/go2-devcontainer/ + +RUN --mount=type=bind,source=docker/config/docker.bashrc,target=/tmp/docker.bashrc \ + cat /tmp/docker.bashrc >> /home/$USERNAME/.bashrc + +# Clone unitree_ros2 +RUN --mount=type=cache,target=/var/cache/apt \ + --mount=type=bind,source=docker/scripts/install-unitree-ros2.sh,target=/tmp/install-unitree-ros2.sh \ + bash /tmp/install-unitree-ros2.sh + +# NVIDIA-related environment variables +ENV NVIDIA_VISIBLE_DEVICES \ + ${NVIDIA_VISIBLE_DEVICES:-all} +ENV NVIDIA_DRIVER_CAPABILITIES \ + ${NVIDIA_DRIVER_CAPABILITIES:+$NVIDIA_DRIVER_CAPABILITIES,}graphics + +USER $USERNAME \ No newline at end of file diff --git a/docker/build.sh b/docker/build.sh new file mode 100644 index 0000000..5a96e03 --- /dev/null +++ b/docker/build.sh @@ -0,0 +1,12 @@ +#!/bin/bash +ARCH=$(uname -m) +if [ "$ARCH" = "x86_64" ]; then + DOCKERFILE_PATH="docker/Dockerfile.x86" +elif [ "$ARCH" = "aarch64" ]; then + DOCKERFILE_PATH="docker/Dockerfile.arm64" +else + echo "Unsupported architecture: $ARCH" + exit 1 +fi + +docker build --file $DOCKERFILE_PATH --tag go2py-container . diff --git a/docker/config/docker.bashrc b/docker/config/docker.bashrc new file mode 100644 index 0000000..ec7a490 --- /dev/null +++ b/docker/config/docker.bashrc @@ -0,0 +1,149 @@ +############################################## +## Bash Script +############################################## + +PROMPT_COMMAND='history -a' +HISTFILE=/home/dev/.bash_history +LOG_WORKSPACE=/workspace/logging +ROS_WORKSPACE=/workspace/go2-devcontainer + +export CC=clang +export CXX=clang++ +export MAKEFLAGS="-j 8" + +export RCUTILS_COLORIZED_OUTPUT=1 +export ROS_LOG_DIR=/workspace/logging/logs +export ROS_DATA_DIR=/workspace/logging/data +export ROS_BAG_MAX_SIZE=1073741824 +export RCUTILS_CONSOLE_OUTPUT_FORMAT="[{severity}] [{time}] [{name}:{line_number}]: {message}" + +# unitree_ros2 +export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp +export CYCLONEDDS_URI=' + + ' + +# https://github.com/ament/ament_cmake/issues/382 +PYTHONWARNINGS="ignore:easy_install command is deprecated,ignore:setup.py install is deprecated" +export PYTHONWARNINGS + +echo '/workspace/logging/coredump/core.%e.%p.%h.%t' | sudo tee /proc/sys/kernel/core_pattern >/dev/null + +_show_workspace_logging_usage() { + # For each dir under logging workspace, show disk usages. + echo "===================================================" + echo "Disk usage for each directory under $LOG_WORKSPACE:" + for dir in $(find $LOG_WORKSPACE -maxdepth 1 -mindepth 1 -type d); do + du -sh $dir + done + echo "Make sure to clean up old logs to save disk space." + echo "===================================================" +} + +_update_dependencies() { + source /opt/ros/humble/setup.bash + sudo apt-get update + cd $ROS_WORKSPACE \ + && rosdep update \ + && rosdep install --from-paths src --ignore-src -y +} + +_ensure_not_in_workspace_src() { + # If current dir ends with "src", prompt user to switch to workspace dir. + # colcon build will stupidly build inside src folder. + if [[ "$(basename "$(pwd)")" == "src" ]]; then + echo "It looks like you're in a 'src' directory. Please switch to ${ROS_WORKSPACE}." + return 1 + fi + return 0 +} + +COMMON_CMAKE_ARGS="\ + -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ + -GNinja \ + -DCMAKE_EXE_LINKER_FLAGS=-fuse-ld=lld \ + -DCMAKE_MODULE_LINKER_FLAGS=-fuse-ld=lld \ + -DCMAKE_SHARED_LINKER_FLAGS=-fuse-ld=lld + " +COMMON_COLCON_ARGS="--symlink-install --event-handlers console_cohesion+" +RELEASE_CMAKE_ARGS="$COMMON_CMAKE_ARGS -DCMAKE_BUILD_TYPE=RelWithDebInfo" +DEBUG_CMAKE_ARGS="$COMMON_CMAKE_ARGS -DCMAKE_BUILD_TYPE=Debug" + +_colcon_release_build() { + _ensure_not_in_workspace_src || return 1 + + # Run 'colcon build' with passed arguments + colcon build \ + ${COMMON_COLCON_ARGS} \ + --cmake-args $RELEASE_CMAKE_ARGS \ + "$@" +} + +_colcon_debug_build() { + _ensure_not_in_workspace_src || return 1 + + # Run 'colcon build' with passed arguments + colcon build \ + ${COMMON_COLCON_ARGS} \ + --cmake-args $DEBUG_CMAKE_ARGS \ + "$@" +} + +_colcon_test() { + _ensure_not_in_workspace_src || return 1 + + # Run 'colcon test' with passed arguments + colcon test --event-handlers console_cohesion+ "$@" +} + +_remove_directories() { + if [ -d "build" ]; then + rm -r build + fi + if [ -d "install" ]; then + rm -r install + fi + if [ -d "log" ]; then + rm -r log + fi +} + +colcon_clean() { + _ensure_not_in_workspace_src || return 1 + + if [ -d "build" ] || [ -d "install" ] || [ -d "log" ]; then + while true; do + read -p "Do you wish to remove build, install, and log directories? [y/n] " yn + case $yn in + [Yy]* ) _remove_directories; break;; + [Nn]* ) break;; + * ) echo "Please answer yes or no.";; + esac + done + fi +} + +# Colcon build aliases +alias update_dep=_update_dependencies +alias source_ws='source install/setup.bash' +alias colcon_build='_colcon_release_build' +alias colcon_debug_build='_colcon_debug_build' +alias colcon_build_package='_colcon_release_build --packages-select' +alias colcon_debug_build_package='_colcon_debug_build --packages-select' +alias colcon_build_up_to='_colcon_release_build --packages-up-to' +alias colcon_debug_build_up_to='_colcon_debug_build --packages-up-to' +alias colcon_test='_colcon_test' +alias colcon_test_package='_colcon_test --packages-select' +alias colcon_test_up_to='_colcon_test --packages-up-to' + +source /opt/ros/humble/setup.bash +# Source ROS workspace if it exists +if [ -f "/workspace/go2-devcontainer/install/setup.bash" ]; then + source /workspace/go2-devcontainer/install/setup.bash +fi + +# colcon_cd +source /usr/share/colcon_cd/function/colcon_cd.sh +export _colcon_cd_root=/opt/ros/humble/ + +_show_workspace_logging_usage \ No newline at end of file diff --git a/docker/scripts/build-librealsense.sh b/docker/scripts/build-librealsense.sh new file mode 100644 index 0000000..67fab42 --- /dev/null +++ b/docker/scripts/build-librealsense.sh @@ -0,0 +1,188 @@ +#!/bin/bash +# Builds the Intel Realsense library librealsense on a Jetson Nano Development Kit +# Copyright (c) 2016-21 Jetsonhacks +# MIT License + +LIBREALSENSE_DIRECTORY=${HOME}/librealsense +INSTALL_DIR=$PWD +NVCC_PATH=/usr/local/cuda/bin/nvcc + +USE_CUDA=true + +function usage () +{ + echo "Usage: ./build-librealsense.sh [-n | -no_cuda] [-v | -version ] [-j | --jobs ] [-h | --help] " + echo "-n | --no_cuda Build with no CUDA (Defaults to with CUDA)" + echo "-v | --version Version of librealsense to build + (defaults to latest release)" + echo "-j | --jobs Number of concurrent jobs (Default 1 on <= 4GB RAM + #of cores-1 otherwise)" + echo "-h | --help This message" + exit 2 +} + +PARSED_ARGUMENTS=$(getopt -a -n build-librealsense.sh -o nv:j:h --longoptions version:,no_cuda,jobs:,help -- "$@" ) +VALID_ARGUMENTS=$? + +if [ "$VALID_ARGUMENTS" != "0" ]; then + echo "" + usage +fi + +eval set -- "$PARSED_ARGUMENTS" + +LIBREALSENSE_VERSION="" +USE_CUDA=true +NUM_PROCS="" + +while : +do + case "$1" in + -n | --build_no_cuda) USE_CUDA=false ; shift ;; + -v | --version ) LIBREALSENSE_VERSION="$2" ; shift 2 ;; + -j | --jobs) NUM_PROCS="$2" ; + shift 2 ; + re_isanum='^[0-9]+$' + if ! [[ $NUM_PROCS =~ $re_isanum ]] ; then + echo "Number of jobs must be a positive, whole number" + usage + else + if [ $NUM_PROCS -eq "0" ]; then + echo "Number of jobs must be a positive, whole number" + fi + fi ; + ;; + -h | --help ) usage ; shift ;; + # -- means the end of arguments + --) shift; break ;; + esac +done + +# From lukechilds gist discussion: https://gist.github.com/lukechilds/a83e1d7127b78fef38c2914c4ececc3c +# We use wget instead of curl here +# Sample usage: +# VERSION_STRINGS=$(get_latest_release IntelRealSense/librealsense) + +function get_latest_release () { + # redirect wget to standard out and grep out the tag_name + wget -qO- https://api.github.com/repos/$1/releases/latest | + grep -Po '"tag_name": "\K.*?(?=")' +} + +if [[ $LIBREALSENSE_VERSION == "" ]] ; then + echo "Getting latest librealsense version number" + LIBREALSENSE_VERSION=$(get_latest_release IntelRealSense/librealsense) +fi + +echo "Build with CUDA: "$USE_CUDA +echo "Librealsense Version: $LIBREALSENSE_VERSION" + +red=`tput setaf 1` +green=`tput setaf 2` +reset=`tput sgr0` +# e.g. echo "${red}The red tail hawk ${green}loves the green grass${reset}" + +if [ ! -d "$LIBREALSENSE_DIRECTORY" ] ; then + # clone librealsense + cd ${HOME} + echo "${green}Cloning librealsense${reset}" + git clone https://github.com/IntelRealSense/librealsense.git +fi + +# Is the version of librealsense current enough? +cd $LIBREALSENSE_DIRECTORY +VERSION_TAG=$(git tag -l $LIBREALSENSE_VERSION) +if [ ! $VERSION_TAG ] ; then + echo "" + tput setaf 1 + echo "==== librealsense Version Mismatch! =============" + tput sgr0 + echo "" + echo "The installed version of librealsense is not current enough for these scripts." + echo "This script needs librealsense tag version: "$LIBREALSENSE_VERSION "but it is not available." + echo "Please upgrade librealsense or remove the librealsense folder before attempting to install again." + echo "" + exit 1 +fi + +# Checkout version the last tested version of librealsense +git checkout $LIBREALSENSE_VERSION + +# Install the dependencies +cd $INSTALL_DIR + +cd $LIBREALSENSE_DIRECTORY +git checkout $LIBREALSENSE_VERSION + +# Now compile librealsense and install +mkdir build +cd build +# Build examples, including graphical ones +echo "${green}Configuring Make system${reset}" +# Build with CUDA (default), the CUDA flag is USE_CUDA, ie -DUSE_CUDA=true +export CUDACXX=$NVCC_PATH +export PATH=${PATH}:/usr/local/cuda/bin +export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/cuda/lib64 + +/usr/bin/cmake ../ -DBUILD_EXAMPLES=true -DFORCE_RSUSB_BACKEND=true -DBUILD_WITH_CUDA="$USE_CUDA" -DCMAKE_BUILD_TYPE=release -DBUILD_PYTHON_BINDINGS=bool:true + +# The library will be installed in /usr/local/lib, header files in /usr/local/include +# The demos, tutorials and tests will located in /usr/local/bin. +echo "${green}Building librealsense, headers, tools and demos${reset}" + +# If user didn't set # of jobs and we have > 4GB memory then +# set # of jobs to # of cores-1, otherwise 1 +if [[ $NUM_PROCS == "" ]] ; then + TOTAL_MEMORY=$(free | awk '/Mem\:/ { print $2 }') + if [ $TOTAL_MEMORY -gt 4051048 ] ; then + NUM_CPU=$(nproc) + NUM_PROCS=$(($NUM_CPU - 1)) + else + NUM_PROCS=1 + fi +fi + +time make -j$NUM_PROCS +if [ $? -eq 0 ] ; then + echo "librealsense make successful" +else + # Try to make again; Sometimes there are issues with the build + # because of lack of resources or concurrency issues + echo "librealsense did not build " >&2 + echo "Retrying ... " + # Single thread this time + time make + if [ $? -eq 0 ] ; then + echo "librealsense make successful" + else + # Try to make again + echo "librealsense did not successfully build" >&2 + echo "Please fix issues and retry build" + exit 1 + fi +fi +echo "${green}Installing librealsense, headers, tools and demos${reset}" +sudo make install + +if grep -Fxq 'export PYTHONPATH=$PYTHONPATH:/usr/local/lib' ~/.bashrc ; then + echo "PYTHONPATH already exists in .bashrc file" +else + echo 'export PYTHONPATH=$PYTHONPATH:/usr/local/lib' >> ~/.bashrc + echo "PYTHONPATH added to ~/.bashrc. Pyhon wrapper is now available for importing pyrealsense2" +fi + +cd $LIBREALSENSE_DIRECTORY +echo "${green}Applying udev rules${reset}" +# Copy over the udev rules so that camera can be run from user space +sudo cp config/99-realsense-libusb.rules /etc/udev/rules.d/ +sudo udevadm control --reload-rules && udevadm trigger + +echo "${green}Library Installed${reset}" +echo " " +echo " -----------------------------------------" +echo "The library is installed in /usr/local/lib" +echo "The header files are in /usr/local/include" +echo "The demos and tools are located in /usr/local/bin" +echo " " +echo " -----------------------------------------" +echo " " diff --git a/docker/scripts/hotplug-realsense.sh b/docker/scripts/hotplug-realsense.sh new file mode 100755 index 0000000..7d515e5 --- /dev/null +++ b/docker/scripts/hotplug-realsense.sh @@ -0,0 +1,79 @@ +#!/usr/bin/env bash + +# Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. +# +# NVIDIA CORPORATION and its licensors retain all intellectual property +# and proprietary rights in and to this software, related documentation +# and any modifications thereto. Any use, reproduction, disclosure or +# distribution of this software and related documentation without an express +# license agreement from NVIDIA CORPORATION is strictly prohibited. + + +# This script is triggered when a corresponding udev rule is matched with action +function usage() { + echo "Usage: hotplug-realsense.sh -a -d [options]" + echo "-a | --action Action determines whether the device has been added or removed. Valid values are 'add' or 'remove'." + echo "-d | --dev-path Device path that will be used to mount the device." + echo "-M | --major-version The kernel major number for the device. This will be used when action=add." + echo "-m | --minor-version The kernel minor number for the device. This will be used when action=add." + echo "-p | --parent-path Parent node path of the device pointed by the -d flag." + echo "-h | --help Display this message." +} + +function check_mandatory_param() { +VAR=$1 +MSG=$2 +if [[ -z ${VAR} ]] ; then + echo "${MSG}" + usage + exit 1 +fi +} + +function timestamp() { + echo [$EPOCHREALTIME] [$(date)] +} + +ARGUMENTS=$(getopt -n hotplug-realsense.sh -o a:d:M:m:p:h -l action:,dev-path:,major-version:,minor-version:,parent-path:,help -- "$@" ) + +if [[ $? -ne 0 ]]; then + usage +fi + +eval set -- "$ARGUMENTS" + +while [ : ]; do + case "$1" in + -a | --action) + ACTION=$2 ; shift 2 ;; + -d | --dev-path) + DEV_PATH=$2 ; shift 2 ;; + -M | --major-version) + MAJOR_VERSION=$2 ; shift 2 ;; + -m | --minor-version) + MINOR_VERSION=$2 ; shift 2 ;; + -p | --parent-path) + PARENT_PATH=/dev/$2 ; shift 2 ;; + -h | --help) + usage ; shift ;; + --) shift; break ;; + esac +done + +check_mandatory_param ${ACTION:-""} "Please provide valid value for action" +check_mandatory_param ${DEV_PATH:-""} "Please provide valid value for device path" + +if [[ "${ACTION}" == "add" ]]; then + check_mandatory_param ${MAJOR_VERSION:-""} "Please provide valid value for major number" + check_mandatory_param ${MINOR_VERSION:-""} "Please provide valid value for minor number" + sudo mknod -m a=rw ${DEV_PATH} c ${MAJOR_VERSION} ${MINOR_VERSION} + sudo chown root:plugdev ${DEV_PATH} + echo $(timestamp) "Added ${DEV_PATH} with major version: ${MAJOR_VERSION} and minor version: ${MINOR_VERSION} to docker" >> /tmp/docker_usb.log +elif [[ "$ACTION" == "remove" ]]; then + sudo rm ${DEV_PATH} ${PARENT_PATH} + echo $(timestamp) "Removed ${DEV_PATH} ${PARENT_PATH} from docker" >> /tmp/docker_usb.log +else + echo "Cannot recognize action=${ACTION}" + usage + exit 1 +fi diff --git a/docker/scripts/install-3rdparty.sh b/docker/scripts/install-3rdparty.sh new file mode 100644 index 0000000..d01902b --- /dev/null +++ b/docker/scripts/install-3rdparty.sh @@ -0,0 +1,74 @@ +#!/bin/bash + +set -e + +# Install thirdparty libraries that CANNOT be installed using rosdep +# Perfer to use rosdep to install thirdparty libraries if possible + +mkdir -p /workspace/thirdparty +cd /workspace/thirdparty + +export CC=clang +export CXX=clang++ +apt-get update + +# ============================================ +# Install magic_enum v0.9.5 +# For some reason, ros-humble-magic-enum doesn't work +cd /workspace/thirdparty +git clone https://github.com/Neargye/magic_enum.git +cd magic_enum && git checkout v0.9.5 +mkdir build && cd build +cmake \ + -DCMAKE_BUILD_TYPE=Release \ + -DMAGIC_ENUM_OPT_BUILD_TESTS=OFF \ + -DMAGIC_ENUM_OPT_BUILD_EXAMPLES=OFF \ + .. +make -j$(nproc) +make install +cd /workspace/thirdparty && rm -rf magic_enum + +# ============================================ +# Install Open3D v.0.18.0 +# The libopen3d-dev package is too old (0.14.0). +# cd /workspace/thirdparty +# git clone https://github.com/isl-org/Open3D.git +# cd Open3D && git checkout v0.18.0 +# apt-get install -y libc++-dev libc++abi-dev +# mkdir build && cd build +# cmake \ +# -DCMAKE_BUILD_TYPE=Release \ +# -DBUILD_SHARED_LIBS=ON \ +# -DBUILD_EXAMPLES=OFF \ +# -DBUILD_PYTHON_MODULE=OFF \ +# .. +# make -j$(nproc) +# make install +# cd /workspace/thirdparty && rm -rf Open3D + + +# ============================================ +# Install google-glog v.0.7.0 +# Some packages depend on it and we can't use libgoogle-glog-dev due to +# https://github.com/isl-org/Open3D/discussions/6515 +cd /workspace/thirdparty +git clone https://github.com/google/glog.git +cd glog && git checkout v0.7.0 +cmake -S . -B build -G "Unix Makefiles" +cmake --build build +cmake --build build --target install +cd /workspace/thirdparty && rm -rf glog + +# ============================================ +# Install librealsense2 +apt-get install -y --no-install-recommends \ + ros-humble-librealsense2* \ + ros-humble-realsense2-* + +# ============================================ +# Install unitree_sdk2 +cd /workspace/thirdparty +git clone https://github.com/unitreerobotics/unitree_sdk2.git +cd unitree_sdk2 +./install.sh +cd /workspace/thirdparty && rm -rf unitree_sdk2 \ No newline at end of file diff --git a/docker/scripts/install-clang.sh b/docker/scripts/install-clang.sh new file mode 100644 index 0000000..e9fb069 --- /dev/null +++ b/docker/scripts/install-clang.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +set -e + +# Install LLVM family: compiler, formatter, debugger, etc + +apt-get update +apt-get install -y --no-install-recommends \ + clangd-15 \ + clang-15 \ + clang-format-15 \ + lld-15 \ + lldb-15 +update-alternatives --install /usr/bin/clang clang /usr/bin/clang-15 100 +update-alternatives --install /usr/bin/clangd clangd /usr/bin/clangd-15 100 +update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-15 100 +update-alternatives --install /usr/bin/clang-format clang-format /usr/bin/clang-format-15 100 +update-alternatives --install /usr/bin/lld lld /usr/bin/lld-15 100 +update-alternatives --install /usr/bin/lldb lldb /usr/bin/lldb-15 100 +update-alternatives --install /usr/bin/lldb-server lldb-server /usr/bin/lldb-server-15 100 + +# Fix https://github.com/llvm/llvm-project/issues/55575 +ln -s /usr/lib/llvm-15/lib/python3.10/dist-packages/lldb/* /usr/lib/python3/dist-packages/lldb/ \ No newline at end of file diff --git a/docker/scripts/install-python-requirements.sh b/docker/scripts/install-python-requirements.sh new file mode 100644 index 0000000..1fc9c58 --- /dev/null +++ b/docker/scripts/install-python-requirements.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +set -e + +pip install \ + "torch==2.2.1" \ + "torchvision==0.17.1" \ + "numpy>=1.26.4" \ + "pybullet>=3.2.6" \ + "proxsuite==0.6.3" \ + "ipykernel==6.29.3" \ + "open3d==0.18.0" \ No newline at end of file diff --git a/docker/scripts/install-realsense-dependencies.sh b/docker/scripts/install-realsense-dependencies.sh new file mode 100644 index 0000000..db81a0c --- /dev/null +++ b/docker/scripts/install-realsense-dependencies.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# install-realsense-dependencies.sh +# Install dependencies for the Intel Realsense library librealsense2 on a Jetson Nano Developer Kit +# Copyright (c) 2016-19 Jetsonhacks +# MIT License + +red=`tput setaf 1` +green=`tput setaf 2` +reset=`tput sgr0` +# e.g. echo "${red}red text ${green}green text${reset}" +echo "${green}Adding Universe repository and updating${reset}" +apt-add-repository universe +apt-get update +echo "${green}Adding dependencies, graphics libraries and tools${reset}" +apt-get install libssl-dev libusb-1.0-0-dev pkg-config -y + +# Graphics libraries - for SDK's OpenGL-enabled examples +apt-get install libgtk-3-dev libglfw3-dev libgl1-mesa-dev libglu1-mesa-dev -y + +# QtCreator for development; not required for librealsense core library +apt-get install qtcreator -y + +# Add Python 3 support +apt-get install -y python3 python3-dev diff --git a/docker/scripts/install-ros2.sh b/docker/scripts/install-ros2.sh new file mode 100644 index 0000000..2aa3f8a --- /dev/null +++ b/docker/scripts/install-ros2.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +set -e + +export DEBIAN_FRONTEND=noninteractive +apt-get update && apt-get install -y locales +locale-gen en_US en_US.UTF-8 +update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 +export LANG=en_US.UTF-8 + +curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg +echo "deb [arch=amd64 signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu jammy main" | tee /etc/apt/sources.list.d/ros2.list > /dev/null + +apt-get update && apt-get install -y --no-install-recommends \ + ros-humble-desktop \ + ros-dev-tools \ + python3-colcon-common-extensions \ + python3-flake8-docstrings \ + python3-pip \ + python3-pytest-cov \ + ros-dev-tools \ + python3-flake8-blind-except \ + python3-flake8-builtins \ + python3-flake8-class-newline \ + python3-flake8-comprehensions \ + python3-flake8-deprecated \ + python3-flake8-import-order \ + python3-flake8-quotes \ + python3-pytest-repeat \ + python3-pytest-rerunfailures \ + ros-humble-rmw-cyclonedds-cpp \ + ros-humble-rosidl-generator-dds-idl \ No newline at end of file diff --git a/docker/scripts/install-rosdep.sh b/docker/scripts/install-rosdep.sh new file mode 100755 index 0000000..d9aa47e --- /dev/null +++ b/docker/scripts/install-rosdep.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +set -e + +# Make sure workspace is mounted in /tmp/ros_ws +source /opt/ros/humble/setup.bash +apt-get update +rosdep init +rosdep update +rosdep install --from-paths /tmp/ros_ws --ignore-src -y \ No newline at end of file diff --git a/docker/scripts/install-tools.sh b/docker/scripts/install-tools.sh new file mode 100644 index 0000000..7b4a7d3 --- /dev/null +++ b/docker/scripts/install-tools.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +set -e + +# Install tools, utilities, and etc. + +apt-get update +apt-get install -y --no-install-recommends \ + curl \ + git-lfs \ + ninja-build \ + psmisc \ + python3-pip \ + software-properties-common \ + unzip \ + usbutils \ + vim \ + wget \ + net-tools \ + iputils-ping + +pip3 install \ + cmakelang==0.6.13 \ No newline at end of file diff --git a/docker/scripts/install-unitree-ros2.sh b/docker/scripts/install-unitree-ros2.sh new file mode 100644 index 0000000..ed9ac8c --- /dev/null +++ b/docker/scripts/install-unitree-ros2.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +set -e + +# clone unitree_ros2 +cd /workspace/go2-devcontainer/src +git clone https://github.com/unitreerobotics/unitree_ros2 + +# clone cyclonedds related packaged +cd /workspace/go2-devcontainer/src/unitree_ros2/cyclonedds_ws/src +git clone https://github.com/ros2/rmw_cyclonedds -b humble +git clone https://github.com/eclipse-cyclonedds/cyclonedds -b releases/0.10.x + +# build cyclonedds +cd /workspace/go2-devcontainer/src/unitree_ros2/cyclonedds_ws +colcon build --packages-select cyclonedds \ No newline at end of file diff --git a/docker/scripts/workspace-entrypoint.sh b/docker/scripts/workspace-entrypoint.sh new file mode 100755 index 0000000..466cd88 --- /dev/null +++ b/docker/scripts/workspace-entrypoint.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# +# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved. +# +# NVIDIA CORPORATION and its licensors retain all intellectual property +# and proprietary rights in and to this software, related documentation +# and any modifications thereto. Any use, reproduction, disclosure or +# distribution of this software and related documentation without an express +# license agreement from NVIDIA CORPORATION is strictly prohibited. + +# Build ROS dependency +echo "source /opt/ros/${ROS_DISTRO}/setup.bash" >> ~/.bashrc +source /opt/ros/${ROS_DISTRO}/setup.bash + +sudo apt-get update +rosdep update + +# Restart udev daemon +sudo service udev restart + +$@ diff --git a/docker/tao/tao-converter-aarch64-tensorrt8.4.zip b/docker/tao/tao-converter-aarch64-tensorrt8.4.zip new file mode 100644 index 0000000000000000000000000000000000000000..94e8c6bf5dccd896de708bd387930d8737ee2902 GIT binary patch literal 35923 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 RWl)gaWxxCMr-U!3KLBTHVFLgF literal 0 HcmV?d00001 diff --git a/docker/udev_rules/99-realsense-libusb-custom.rules b/docker/udev_rules/99-realsense-libusb-custom.rules new file mode 100644 index 0000000..9279e0d --- /dev/null +++ b/docker/udev_rules/99-realsense-libusb-custom.rules @@ -0,0 +1,11 @@ +##Version=0.1## +# Device rules for Intel RealSense devices (D435 D435i D455) +# For D435 +ACTION=="add" SUBSYSTEMS=="usb", ATTRS{idVendor}=="8086", ATTRS{idProduct}=="0b07", MODE:="0666", GROUP:="plugdev", RUN+="/opt/realsense/hotplug-realsense.sh -a add -d '%N' -M '%M' -m '%m'" +ACTION=="remove" SUBSYSTEMS=="usb", ATTRS{idVendor}=="8086", ATTRS{idProduct}=="0b07", MODE:="0666", GROUP:="plugdev", RUN+="/opt/realsense/hotplug-realsense.sh -a remove -d '%N' -p '%P'" +# For D435i +ACTION=="add" SUBSYSTEMS=="usb", ATTRS{idVendor}=="8086", ATTRS{idProduct}=="0b3a", MODE:="0666", GROUP:="plugdev", RUN+="/opt/realsense/hotplug-realsense.sh -a add -d '%N' -M '%M' -m '%m'" +ACTION=="remove" SUBSYSTEMS=="usb", ATTRS{idVendor}=="8086", ATTRS{idProduct}=="0b3a", MODE:="0666", GROUP:="plugdev", RUN+="/opt/realsense/hotplug-realsense.sh -a remove -d '%N' -p '%P'" +# For D455 +ACTION=="add" SUBSYSTEMS=="usb", ATTRS{idVendor}=="8086", ATTRS{idProduct}=="0b5c", MODE:="0666", GROUP:="plugdev", RUN+="/opt/realsense/hotplug-realsense.sh -a add -d '%N' -M '%M' -m '%m'" +ACTION=="remove" SUBSYSTEMS=="usb", ATTRS{idVendor}=="8086", ATTRS{idProduct}=="0b5c", MODE:="0666", GROUP:="plugdev", RUN+="/opt/realsense/hotplug-realsense.sh -a remove -d '%N' -p '%P'"