From ba3aabcdf9aaf84d4555db6ae0af2c3fccf486d4 Mon Sep 17 00:00:00 2001 From: Chris Iverach-Brereton Date: Fri, 5 Jul 2024 16:58:08 -0400 Subject: [PATCH 01/40] Initial migration to Jazzy. Not all packages exist as binaries yet --- .github/ISSUE_TEMPLATE/1-bug.yml | 2 ++ .github/workflows/ci.yml | 8 ++++---- README.md | 10 +++++----- etc/turtlebot4/discovery.sh | 2 +- etc/turtlebot4/setup.bash | 4 ++-- etc/turtlebot4/system | 2 +- scripts/{humble.sh => jazzy.sh} | 4 ++-- scripts/turtlebot4_setup.sh | 14 +++++++------- turtlebot4_discovery/configure_discovery.sh | 4 ++-- turtlebot4_setup/conf.py | 6 +++--- turtlebot4_setup/turtlebot4_setup | 10 +++++----- 11 files changed, 34 insertions(+), 32 deletions(-) rename scripts/{humble.sh => jazzy.sh} (95%) diff --git a/.github/ISSUE_TEMPLATE/1-bug.yml b/.github/ISSUE_TEMPLATE/1-bug.yml index 46fcde9..801c9b4 100644 --- a/.github/ISSUE_TEMPLATE/1-bug.yml +++ b/.github/ISSUE_TEMPLATE/1-bug.yml @@ -30,6 +30,7 @@ body: - Select One - Galactic - Humble + - Jazzy validations: required: true - type: dropdown @@ -52,6 +53,7 @@ body: - Select One - Ubuntu 20.04 - Ubuntu 22.04 + - Ubuntyu 24.04 - Other Linux - Windows / MAC validations: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 15f3120..043e79f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,11 +3,11 @@ name: turtlebot4_setup_ci on: [push, pull_request] jobs: - turtlebot4_humble_ci: - name: Humble - runs-on: ubuntu-22.04 + turtlebot4_jazzy_ci: + name: Jazzy + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v2.3.4 - uses: ros-tooling/setup-ros@v0.3 with: - required-ros-distributions: humble + required-ros-distributions: jazzy diff --git a/README.md b/README.md index 00c2fb1..fd99b6e 100644 --- a/README.md +++ b/README.md @@ -12,14 +12,14 @@ Follow these instructions if you wish to create a Turtlebot4 image manually. First install the [Raspberry Pi Imager](https://www.raspberrypi.com/software/). -- Insert your SD card into your PC and run the Raspberry Pi Imager. Follow the instructions and install Ubuntu 22.04 Server (64-bit) onto the SD card. -- Ensure your Raspberry Pi 4 is not powered before inserting the flashed SD card. +- Insert your SD card into your PC and run the Raspberry Pi Imager. Follow the instructions and install Ubuntu 24.04 Server (64-bit) onto the SD card. +- Ensure your Raspberry Pi 4 is not powered before inserting the flashed SD card. - You can set up the Raspberry Pi by either connecting it to your network via Ethernet or by using a keyboard and HDMI monitor via a micro HDMI cable. ### Ethernet Setup - Connect the Raspberry Pi to your Network with an Ethernet cable. -- Boot the Raspberry Pi. +- Boot the Raspberry Pi. - Find the Raspberry Pi's IP using your router's portal. - SSH into the Raspberry Pi using the IP address. ```bash @@ -62,7 +62,7 @@ ssh ubuntu@xxx.xxx.xxx.xxx ## Download and run the setup script ``` -wget -qO - https://raw.githubusercontent.com/turtlebot/turtlebot4_setup/humble/scripts/turtlebot4_setup.sh | bash +wget -qO - https://raw.githubusercontent.com/turtlebot/turtlebot4_setup/jazzy/scripts/turtlebot4_setup.sh | bash ``` -The script will automatically install ROS 2 Humble, TurtleBot 4 packages, and other important apt packages. It will also configure the RPi4 to work in a TurtleBot 4. Once complete, the RPi4 should be rebooted with `sudo reboot`. Then, run `turtlebot4-setup` to configure the robot with the setup tool. +The script will automatically install ROS 2 Jazzy, TurtleBot 4 packages, and other important apt packages. It will also configure the RPi4 to work in a TurtleBot 4. Once complete, the RPi4 should be rebooted with `sudo reboot`. Then, run `turtlebot4-setup` to configure the robot with the setup tool. diff --git a/etc/turtlebot4/discovery.sh b/etc/turtlebot4/discovery.sh index 2fdf915..54ab52e 100755 --- a/etc/turtlebot4/discovery.sh +++ b/etc/turtlebot4/discovery.sh @@ -1,3 +1,3 @@ #!/bin/bash -source /opt/ros/humble/setup.bash +source /opt/ros/jazzy/setup.bash fastdds discovery -i 0 -p 11811 diff --git a/etc/turtlebot4/setup.bash b/etc/turtlebot4/setup.bash index 1098367..0085979 100644 --- a/etc/turtlebot4/setup.bash +++ b/etc/turtlebot4/setup.bash @@ -5,7 +5,7 @@ export ROS_DOMAIN_ID=0 export ROS_DISCOVERY_SERVER= export RMW_IMPLEMENTATION=rmw_fastrtps_cpp export TURTLEBOT4_DIAGNOSTICS=1 -export WORKSPACE_SETUP=/opt/ros/humble/setup.bash +export WORKSPACE_SETUP=/opt/ros/jazzy/setup.bash export ROS_SUPER_CLIENT=False -source $WORKSPACE_SETUP \ No newline at end of file +source $WORKSPACE_SETUP diff --git a/etc/turtlebot4/system b/etc/turtlebot4/system index 0d27143..efc36e3 100644 --- a/etc/turtlebot4/system +++ b/etc/turtlebot4/system @@ -1,3 +1,3 @@ MODEL:standard VERSION:1.0.0 -ROS:Humble \ No newline at end of file +ROS:Jazzy diff --git a/scripts/humble.sh b/scripts/jazzy.sh similarity index 95% rename from scripts/humble.sh rename to scripts/jazzy.sh index cd1003a..3d02875 100755 --- a/scripts/humble.sh +++ b/scripts/jazzy.sh @@ -4,7 +4,7 @@ sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key - echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(source /etc/os-release && echo $UBUNTU_CODENAME) main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null sudo apt update sudo apt install -y \ -ros-humble-ros-base \ +ros-jazzy-ros-base \ build-essential \ cmake \ git \ @@ -12,4 +12,4 @@ wget \ ros-dev-tools \ socat \ network-manager \ -chrony \ No newline at end of file +chrony diff --git a/scripts/turtlebot4_setup.sh b/scripts/turtlebot4_setup.sh index c70787e..c29f26d 100755 --- a/scripts/turtlebot4_setup.sh +++ b/scripts/turtlebot4_setup.sh @@ -31,15 +31,15 @@ echo "Setting up Turtlebot4"; sudo apt update && sudo apt upgrade -wget -qO - https://raw.githubusercontent.com/turtlebot/turtlebot4_setup/humble/scripts/humble.sh | bash +wget -qO - https://raw.githubusercontent.com/turtlebot/turtlebot4_setup/jazzy/scripts/jazzy.sh | bash sudo apt update && sudo apt upgrade -sudo apt install -y ros-humble-ros-base \ -ros-humble-turtlebot4-setup \ -ros-humble-turtlebot4-robot \ -ros-humble-irobot-create-control \ -ros-humble-turtlebot4-navigation \ +sudo apt install -y ros-jazzy-ros-base \ +ros-jazzy-turtlebot4-setup \ +ros-jazzy-turtlebot4-robot \ +ros-jazzy-irobot-create-control \ +ros-jazzy-turtlebot4-navigation \ ros-dev-tools \ socat \ network-manager \ @@ -47,7 +47,7 @@ chrony sudo rm /etc/netplan/50-cloud-init.yaml -git clone https://github.com/turtlebot/turtlebot4_setup.git -b humble && \ +git clone https://github.com/turtlebot/turtlebot4_setup.git -b jazzy && \ sudo mv turtlebot4_setup/boot/firmware/* /boot/firmware && rm turtlebot4_setup/ -rf echo "export ROBOT_SETUP=/etc/turtlebot4/setup.bash" | sudo tee -a ~/.bashrc diff --git a/turtlebot4_discovery/configure_discovery.sh b/turtlebot4_discovery/configure_discovery.sh index 13f8023..2e6c270 100755 --- a/turtlebot4_discovery/configure_discovery.sh +++ b/turtlebot4_discovery/configure_discovery.sh @@ -116,7 +116,7 @@ do # Prompt the user to offer the ability to correct the last server info or add additional servers while [ 1 ] do - read -p "Re-enter the last server (r), add another server (a), or done (d): " option + read -p "Re-enter the last server (r), add another server (a), or done (d): " option if [[ $option =~ ^[r,R].* ]]; then echo "Removing last server entry, re-enter the correct server information" @@ -173,7 +173,7 @@ sudo mkdir -p /etc/turtlebot4_discovery/ # Create setup.bash file setup_file_temp="/tmp/turtlebot4_discovery_setup.bash" -echo "source /opt/ros/humble/setup.bash" > $setup_file_temp +echo "source /opt/ros/jazzy/setup.bash" > $setup_file_temp echo "export RMW_IMPLEMENTATION=rmw_fastrtps_cpp" >> $setup_file_temp echo "[ -t 0 ] && export ROS_SUPER_CLIENT=True || export ROS_SUPER_CLIENT=False" >> $setup_file_temp diff --git a/turtlebot4_setup/conf.py b/turtlebot4_setup/conf.py index b45ad35..7271c5e 100644 --- a/turtlebot4_setup/conf.py +++ b/turtlebot4_setup/conf.py @@ -53,7 +53,7 @@ class Conf(): default_system_conf = { SystemOptions.MODEL: 'lite', SystemOptions.VERSION: '1.0.0', - SystemOptions.ROS: 'Humble', + SystemOptions.ROS: 'Jazzy', SystemOptions.HOSTNAME: 'ubuntu', } @@ -75,7 +75,7 @@ class Conf(): BashOptions.DISCOVERY_SERVER: None, BashOptions.RMW: 'rmw_fastrtps_cpp', BashOptions.DIAGNOSTICS: '1', - BashOptions.WORKSPACE: '/opt/ros/humble/setup.bash', + BashOptions.WORKSPACE: '/opt/ros/jazzy/setup.bash', BashOptions.SUPER_CLIENT: False } @@ -393,7 +393,7 @@ def get_discovery_str(self) -> str: discovery_str += f"{s['ip']}:{s['port']};" i += 1 return discovery_str - + def get_create3_server_str(self) -> str: # Create3 should only point at the local server on the pi discovery_str = '' diff --git a/turtlebot4_setup/turtlebot4_setup b/turtlebot4_setup/turtlebot4_setup index 60beb39..2de70b4 100755 --- a/turtlebot4_setup/turtlebot4_setup +++ b/turtlebot4_setup/turtlebot4_setup @@ -13,7 +13,7 @@ from turtlebot4_setup.conf import Conf, SystemOptions, BashOptions, WifiOptions, class Turtlebot4Setup(): title = """ - _____ _ _ ___ _ _ _ ___ _ + _____ _ _ ___ _ _ _ ___ _ |_ _| _ _ _| |_| |___| _ ) ___| |_| | | / __| ___| |_ _ _ _ __ | || || | '_| _| / -_) _ \/ _ \ _|_ _| \__ \/ -_) _| || | '_ \\ |_| \_,_|_| \__|_\___|___/\___/\__| |_| |___/\___|\__|\_,_| .__/ @@ -46,7 +46,7 @@ class Turtlebot4Setup(): if o.show() == 'Yes': subprocess.run(shlex.split('sudo apt update')) - subprocess.run(shlex.split('sudo apt install ros-humble-turtlebot4-setup')) + subprocess.run(shlex.split('sudo apt install ros-jazzy-turtlebot4-setup')) input() def view_settings(self): @@ -103,7 +103,7 @@ class Turtlebot4Setup(): if text == '': text = 'No changes made.\n' text = """ - _ _ ___ _ _ _ + _ _ ___ _ _ _ /_\ _ __ _ __| |_ _ / __| ___| |_| |_(_)_ _ __ _ ___ / _ \| '_ \ '_ \ | || | \__ \/ -_) _| _| | ' \/ _` (_-< /_/ \_\ .__/ .__/_|\_, | |___/\___|\__|\__|_|_||_\__, /__/ @@ -184,7 +184,7 @@ class Turtlebot4Setup(): # Set create3 rmw profile command = shlex.split(f'curl -d {shlex.quote(create3_rmw_profile)} -X POST http://192.168.186.2/rmw-profile-override-save') - + result = subprocess.run(command, capture_output=True) # If the curl command fails then return and indicate the error. @@ -194,7 +194,7 @@ class Turtlebot4Setup(): # Set time syncing to Raspberry PI config = f'config=server 192.168.186.3 prefer iburst minpoll 4 maxpoll 6 # Use RPi4 server' command = shlex.split(f'curl -d "{config}" -X POST http://192.168.186.2/beta-ntp-conf-save') - + result = subprocess.run(command, capture_output=True) # If the curl command fails then return and indicate the error. From dcab033968d551390ed7a59a7f49ee01cb68409e Mon Sep 17 00:00:00 2001 From: Chris Iverach-Brereton Date: Tue, 9 Jul 2024 13:12:31 -0400 Subject: [PATCH 02/40] Add docker files --- etc/turtlebot4/docker-compose.yml | 12 +++++ etc/turtlebot4/docker/Dockerfile | 50 +++++++++++++++++++ etc/turtlebot4/docker/build.sh | 3 ++ etc/turtlebot4/docker/config/create3.env | 5 ++ .../docker/config/fastrtps-repub.xml | 22 ++++++++ 5 files changed, 92 insertions(+) create mode 100644 etc/turtlebot4/docker-compose.yml create mode 100644 etc/turtlebot4/docker/Dockerfile create mode 100755 etc/turtlebot4/docker/build.sh create mode 100644 etc/turtlebot4/docker/config/create3.env create mode 100644 etc/turtlebot4/docker/config/fastrtps-repub.xml diff --git a/etc/turtlebot4/docker-compose.yml b/etc/turtlebot4/docker-compose.yml new file mode 100644 index 0000000..0460cd8 --- /dev/null +++ b/etc/turtlebot4/docker-compose.yml @@ -0,0 +1,12 @@ +services: + create3-republisher: + image: create3-republisher:latest + restart: no + env_file: + - config/create3.env + command: ros2 launch create3_republisher create3_republisher_launch.py robot_ns:=/ republisher_ns:=/create3_repub + profiles: + - create3 + volumes: + - ./docker/config:/opt/config + network_mode: host diff --git a/etc/turtlebot4/docker/Dockerfile b/etc/turtlebot4/docker/Dockerfile new file mode 100644 index 0000000..76b23d7 --- /dev/null +++ b/etc/turtlebot4/docker/Dockerfile @@ -0,0 +1,50 @@ +FROM ros:humble + +# install ros package +RUN apt-get update && apt-get install -y \ + ros-${ROS_DISTRO}-irobot-create-msgs \ + ros-${ROS_DISTRO}-domain-bridge \ + nano && \ + rm -rf /var/lib/apt/lists/* + +ARG USERNAME=ros +ARG USER_UID=1000 +ARG USER_GID=$USER_UID + +# Create a non-root user +RUN groupadd --gid $USER_GID $USERNAME \ + && useradd -s /bin/bash --uid $USER_UID --gid $USER_GID -m $USERNAME \ + # [Optional] Add sudo support for the non-root user + && apt-get update \ + && apt-get install -y sudo \ + && echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME\ + && chmod 0440 /etc/sudoers.d/$USERNAME \ + # Cleanup + && rm -rf /var/lib/apt/lists/* \ + && echo "source /usr/share/bash-completion/completions/git" >> /home/$USERNAME/.bashrc \ + && echo "if [ -f /opt/ros/${ROS_DISTRO}/setup.bash ]; then source /opt/ros/${ROS_DISTRO}/setup.bash; fi" >> /home/$USERNAME/.bashrc + + +#ENV ROS_WS=/opt/ws +#RUN mkdir -p $ROS_WS/src +# +#WORKDIR $ROS_WS/src +#RUN git clone https://github.com/ros2/domain_bridge.git -b humble +# +#WORKDIR $ROS_WS +#RUN . /opt/ros/$ROS_DISTRO/setup.sh && \ +# colcon build --symlink-install + + +# #!/bin/bash +# set -e +# +# # setup ros2 environment +# source "/opt/ros/$ROS_DISTRO/setup.bash" -- +# exec "$@" +#RUN echo "#!/bin/bash" > /ros_entrypoint.sh && \ +# echo "set -e" >> /ros_entrypoint.sh && \ +# echo "source /opt/ros/$ROS_DISTRO/setup.bash" >> /ros_entrypoint.sh && \ +# echo "source $ROS_WS/install/setup.bash" >> /ros_entrypoint.sh && \ +# echo "exec \"\$@\"" >> /ros_entrypoint.sh && \ +# chmod +x /ros_entrypoint.sh diff --git a/etc/turtlebot4/docker/build.sh b/etc/turtlebot4/docker/build.sh new file mode 100755 index 0000000..fe748a7 --- /dev/null +++ b/etc/turtlebot4/docker/build.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +docker build -t create3-bridge . diff --git a/etc/turtlebot4/docker/config/create3.env b/etc/turtlebot4/docker/config/create3.env new file mode 100644 index 0000000..530d04d --- /dev/null +++ b/etc/turtlebot4/docker/config/create3.env @@ -0,0 +1,5 @@ +export FASTRTPS_DEFAULT_PROFILES_FILE=/opt/config/fastrtps-repub.xml +export ROBOT_NAMESPACE= +export ROS_DOMAIN_ID=0 +export ROS_DISCOVERY_SERVER= +export RMW_IMPLEMENTATION=rmw_fastrtps_cpp diff --git a/etc/turtlebot4/docker/config/fastrtps-repub.xml b/etc/turtlebot4/docker/config/fastrtps-repub.xml new file mode 100644 index 0000000..d1ca7b0 --- /dev/null +++ b/etc/turtlebot4/docker/config/fastrtps-repub.xml @@ -0,0 +1,22 @@ + + + + + + + + + +
127.0.0.1
+
+ +
192.168.186.2
+
+
+
+
+ UDPv4 +
+
+
+
From e8c929e364f2bd80c55548e736e503cd630b25d3 Mon Sep 17 00:00:00 2001 From: Chris Iverach-Brereton Date: Tue, 9 Jul 2024 13:14:39 -0400 Subject: [PATCH 03/40] Use the complete path for the volume --- etc/turtlebot4/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/turtlebot4/docker-compose.yml b/etc/turtlebot4/docker-compose.yml index 0460cd8..3124ac2 100644 --- a/etc/turtlebot4/docker-compose.yml +++ b/etc/turtlebot4/docker-compose.yml @@ -8,5 +8,5 @@ services: profiles: - create3 volumes: - - ./docker/config:/opt/config + - /etc/turtlebot4/docker/config:/opt/config network_mode: host From 32375b49a9c6f678ab8559a1f1f8cb4677abd0d3 Mon Sep 17 00:00:00 2001 From: Chris Iverach-Brereton Date: Tue, 9 Jul 2024 13:15:57 -0400 Subject: [PATCH 04/40] Fix the env file path --- etc/turtlebot4/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/turtlebot4/docker-compose.yml b/etc/turtlebot4/docker-compose.yml index 3124ac2..f948e7a 100644 --- a/etc/turtlebot4/docker-compose.yml +++ b/etc/turtlebot4/docker-compose.yml @@ -3,7 +3,7 @@ services: image: create3-republisher:latest restart: no env_file: - - config/create3.env + - /etc/turtlebot4/docker/config/create3.env command: ros2 launch create3_republisher create3_republisher_launch.py robot_ns:=/ republisher_ns:=/create3_repub profiles: - create3 From 798a1b39e532123f32e0144c614e95d6dda08422 Mon Sep 17 00:00:00 2001 From: Chris Iverach-Brereton Date: Tue, 9 Jul 2024 14:42:57 -0400 Subject: [PATCH 05/40] Fix the dockerfile & build script; i copied the wrong ones in before --- etc/turtlebot4/docker/Dockerfile | 58 +++++++++++--------------------- etc/turtlebot4/docker/build.sh | 2 +- 2 files changed, 20 insertions(+), 40 deletions(-) diff --git a/etc/turtlebot4/docker/Dockerfile b/etc/turtlebot4/docker/Dockerfile index 76b23d7..7d8a241 100644 --- a/etc/turtlebot4/docker/Dockerfile +++ b/etc/turtlebot4/docker/Dockerfile @@ -1,10 +1,15 @@ FROM ros:humble -# install ros package +# Add ROS 2 testing server +RUN echo "deb [ signed-by=/usr/share/keyrings/ros2-latest-archive-keyring.gpg ] http://packages.ros.org/ros2-testing/ubuntu jammy main" > /etc/apt/sources.list.d/ros2-testing.list + +# install ros packages RUN apt-get update && apt-get install -y \ ros-${ROS_DISTRO}-irobot-create-msgs \ - ros-${ROS_DISTRO}-domain-bridge \ - nano && \ + ros-${ROS_DISTRO}-create3-republisher \ + git \ + nano \ + net-tools && \ rm -rf /var/lib/apt/lists/* ARG USERNAME=ros @@ -12,39 +17,14 @@ ARG USER_UID=1000 ARG USER_GID=$USER_UID # Create a non-root user -RUN groupadd --gid $USER_GID $USERNAME \ - && useradd -s /bin/bash --uid $USER_UID --gid $USER_GID -m $USERNAME \ - # [Optional] Add sudo support for the non-root user - && apt-get update \ - && apt-get install -y sudo \ - && echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME\ - && chmod 0440 /etc/sudoers.d/$USERNAME \ - # Cleanup - && rm -rf /var/lib/apt/lists/* \ - && echo "source /usr/share/bash-completion/completions/git" >> /home/$USERNAME/.bashrc \ - && echo "if [ -f /opt/ros/${ROS_DISTRO}/setup.bash ]; then source /opt/ros/${ROS_DISTRO}/setup.bash; fi" >> /home/$USERNAME/.bashrc - - -#ENV ROS_WS=/opt/ws -#RUN mkdir -p $ROS_WS/src -# -#WORKDIR $ROS_WS/src -#RUN git clone https://github.com/ros2/domain_bridge.git -b humble -# -#WORKDIR $ROS_WS -#RUN . /opt/ros/$ROS_DISTRO/setup.sh && \ -# colcon build --symlink-install - - -# #!/bin/bash -# set -e -# -# # setup ros2 environment -# source "/opt/ros/$ROS_DISTRO/setup.bash" -- -# exec "$@" -#RUN echo "#!/bin/bash" > /ros_entrypoint.sh && \ -# echo "set -e" >> /ros_entrypoint.sh && \ -# echo "source /opt/ros/$ROS_DISTRO/setup.bash" >> /ros_entrypoint.sh && \ -# echo "source $ROS_WS/install/setup.bash" >> /ros_entrypoint.sh && \ -# echo "exec \"\$@\"" >> /ros_entrypoint.sh && \ -# chmod +x /ros_entrypoint.sh +#RUN groupadd --gid $USER_GID $USERNAME \ +# && useradd -s /bin/bash --uid $USER_UID --gid $USER_GID -m $USERNAME \ +# # [Optional] Add sudo support for the non-root user +# && apt-get update \ +# && apt-get install -y sudo \ +# && echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME\ +# && chmod 0440 /etc/sudoers.d/$USERNAME \ +# # Cleanup +# && rm -rf /var/lib/apt/lists/* \ +# && echo "source /usr/share/bash-completion/completions/git" >> /home/$USERNAME/.bashrc \ +# && echo "if [ -f /opt/ros/${ROS_DISTRO}/setup.bash ]; then source /opt/ros/${ROS_DISTRO}/setup.bash; fi" >> /home/$USERNAME/.bashrc diff --git a/etc/turtlebot4/docker/build.sh b/etc/turtlebot4/docker/build.sh index fe748a7..63529b7 100755 --- a/etc/turtlebot4/docker/build.sh +++ b/etc/turtlebot4/docker/build.sh @@ -1,3 +1,3 @@ #!/bin/bash -docker build -t create3-bridge . +docker build -t create3-republisher . From c4bb7792425f6126f3ae722ccba553a4765f2d10 Mon Sep 17 00:00:00 2001 From: Chris Iverach-Brereton Date: Tue, 9 Jul 2024 15:48:50 -0400 Subject: [PATCH 06/40] Use macvlan to give the container a unique, public address. Add a macvlan bridge to allow the container to communicate with the host --- etc/rc.local | 11 +++++++++++ etc/turtlebot4/docker-compose.yml | 16 ++++++++++++++-- etc/turtlebot4/docker/Dockerfile | 3 ++- 3 files changed, 27 insertions(+), 3 deletions(-) create mode 100755 etc/rc.local diff --git a/etc/rc.local b/etc/rc.local new file mode 100755 index 0000000..c4675d7 --- /dev/null +++ b/etc/rc.local @@ -0,0 +1,11 @@ +#!/bin/bash + +# Give ourselves some swap to deal with RAM issues +if [ -f /swapfile ]; then + swapon /swapfile +fi + +# Create a macvlan bridge to communicate with the docker container +ip link add vlan-shim link usb0 type macvlan mode bridge +ip addr add 192.168.186.10/24 dev vlan-shim +ip link set vlan-shim up diff --git a/etc/turtlebot4/docker-compose.yml b/etc/turtlebot4/docker-compose.yml index f948e7a..c4996ef 100644 --- a/etc/turtlebot4/docker-compose.yml +++ b/etc/turtlebot4/docker-compose.yml @@ -1,7 +1,7 @@ services: create3-republisher: image: create3-republisher:latest - restart: no + restart: always env_file: - /etc/turtlebot4/docker/config/create3.env command: ros2 launch create3_republisher create3_republisher_launch.py robot_ns:=/ republisher_ns:=/create3_repub @@ -9,4 +9,16 @@ services: - create3 volumes: - /etc/turtlebot4/docker/config:/opt/config - network_mode: host + networks: + vlan: + ipv4_address: 192.168.186.100 + +networks: + vlan: + driver: macvlan + driver_opts: + parent: usb0 + ipam: + config: + - subnet: 192.168.186.0/24 + ip_range: 192.168.186.0/24 diff --git a/etc/turtlebot4/docker/Dockerfile b/etc/turtlebot4/docker/Dockerfile index 7d8a241..0c21467 100644 --- a/etc/turtlebot4/docker/Dockerfile +++ b/etc/turtlebot4/docker/Dockerfile @@ -9,7 +9,8 @@ RUN apt-get update && apt-get install -y \ ros-${ROS_DISTRO}-create3-republisher \ git \ nano \ - net-tools && \ + net-tools \ + iputils-ping && \ rm -rf /var/lib/apt/lists/* ARG USERNAME=ros From c22d543151155d8a35f9bb69b86fa4dc2e7f4915 Mon Sep 17 00:00:00 2001 From: Chris Iverach-Brereton Date: Thu, 11 Jul 2024 12:56:43 -0400 Subject: [PATCH 07/40] Update the RMW configuration files, remove the vlan-shim interface. Block the host Jazzy environment from communicating on the 192.168.186.0/24 subnet, create a second bridge interface for host/container communication. Action feedback & results don't seem to be working, but the robot does appear to be mobile now --- etc/rc.local | 5 --- etc/turtlebot4/docker-compose.yml | 15 +++++++- .../docker/config/fastrtps-repub.xml | 12 ------ etc/turtlebot4/fastdds_rpi.xml | 38 +++++++++++++++---- 4 files changed, 45 insertions(+), 25 deletions(-) diff --git a/etc/rc.local b/etc/rc.local index c4675d7..1634c50 100755 --- a/etc/rc.local +++ b/etc/rc.local @@ -4,8 +4,3 @@ if [ -f /swapfile ]; then swapon /swapfile fi - -# Create a macvlan bridge to communicate with the docker container -ip link add vlan-shim link usb0 type macvlan mode bridge -ip addr add 192.168.186.10/24 dev vlan-shim -ip link set vlan-shim up diff --git a/etc/turtlebot4/docker-compose.yml b/etc/turtlebot4/docker-compose.yml index c4996ef..bbf1464 100644 --- a/etc/turtlebot4/docker-compose.yml +++ b/etc/turtlebot4/docker-compose.yml @@ -4,7 +4,7 @@ services: restart: always env_file: - /etc/turtlebot4/docker/config/create3.env - command: ros2 launch create3_republisher create3_republisher_launch.py robot_ns:=/ republisher_ns:=/create3_repub + command: ros2 launch create3_republisher create3_republisher_launch.py robot_ns:=/create3 republisher_ns:=/ profiles: - create3 volumes: @@ -12,8 +12,12 @@ services: networks: vlan: ipv4_address: 192.168.186.100 + tbt4_container_bridge: + ipv4_address: 192.168.187.100 networks: + # Give the docker container a public-facing IP address on the usb0 network to allow it to + # communicate with the Create3 hardware. vlan: driver: macvlan driver_opts: @@ -22,3 +26,12 @@ networks: config: - subnet: 192.168.186.0/24 ip_range: 192.168.186.0/24 + + # Define a second interface for the container to allow it to talk with the host without + # risking spilling over into the 192.168.186.0/24 subnet + tbt4_container_bridge: + driver: bridge + ipam: + config: + - subnet: 192.168.187.0/24 + ip_range: 192.168.187.0/24 diff --git a/etc/turtlebot4/docker/config/fastrtps-repub.xml b/etc/turtlebot4/docker/config/fastrtps-repub.xml index d1ca7b0..ea74c33 100644 --- a/etc/turtlebot4/docker/config/fastrtps-repub.xml +++ b/etc/turtlebot4/docker/config/fastrtps-repub.xml @@ -3,18 +3,6 @@ - - - - -
127.0.0.1
-
- -
192.168.186.2
-
-
-
-
UDPv4
diff --git a/etc/turtlebot4/fastdds_rpi.xml b/etc/turtlebot4/fastdds_rpi.xml index f5ffbac..fe5124a 100644 --- a/etc/turtlebot4/fastdds_rpi.xml +++ b/etc/turtlebot4/fastdds_rpi.xml @@ -1,8 +1,32 @@ - - - - - - - + + + + + + udp_transport + UDPv4 + + + + + + + + + + + + + + udp_transport + + false + + + From 05f7f7573013c4d5fa04a8f326a0a85a25bef712 Mon Sep 17 00:00:00 2001 From: Chris Iverach-Brereton Date: Thu, 11 Jul 2024 13:03:14 -0400 Subject: [PATCH 08/40] Add docker installation to jazzy.sh --- scripts/jazzy.sh | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/scripts/jazzy.sh b/scripts/jazzy.sh index 3d02875..d74efda 100755 --- a/scripts/jazzy.sh +++ b/scripts/jazzy.sh @@ -1,7 +1,20 @@ #!/usr/bin/env bash sudo apt update && sudo apt install curl gnupg lsb-release -y + +# Add ROS sources 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 $(source /etc/os-release && echo $UBUNTU_CODENAME) main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null + +# Add Docker sources +sudo install -m 0755 -d /etc/apt/keyrings +sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc +sudo chmod a+r /etc/apt/keyrings/docker.asc +echo \ + "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \ + $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ + sudo tee /etc/apt/sources.list.d/docker.list > /dev/null + +# Install the packages sudo apt update sudo apt install -y \ ros-jazzy-ros-base \ @@ -12,4 +25,12 @@ wget \ ros-dev-tools \ socat \ network-manager \ -chrony +chrony \ +docker-ce \ +docker-ce-cli \ +containerd.io \ +docker-buildx-plugin \ +docker-compose-plugin + +# Docker post-install configuration +sudo usermod -a -G docker $(whoami) From b1cb8c5caef83313d0f1542f0f5412e2a261b152 Mon Sep 17 00:00:00 2001 From: Chris Iverach-Brereton Date: Wed, 4 Sep 2024 15:00:21 -0400 Subject: [PATCH 09/40] Remove Docker; it's no longer necessary with the I.*.* firmware. --- etc/turtlebot4/docker/Dockerfile | 31 ------------------- etc/turtlebot4/docker/build.sh | 3 -- etc/turtlebot4/docker/config/create3.env | 5 --- .../docker/config/fastrtps-repub.xml | 10 ------ scripts/create_update.sh | 2 +- scripts/jazzy.sh | 19 +----------- 6 files changed, 2 insertions(+), 68 deletions(-) delete mode 100644 etc/turtlebot4/docker/Dockerfile delete mode 100755 etc/turtlebot4/docker/build.sh delete mode 100644 etc/turtlebot4/docker/config/create3.env delete mode 100644 etc/turtlebot4/docker/config/fastrtps-repub.xml diff --git a/etc/turtlebot4/docker/Dockerfile b/etc/turtlebot4/docker/Dockerfile deleted file mode 100644 index 0c21467..0000000 --- a/etc/turtlebot4/docker/Dockerfile +++ /dev/null @@ -1,31 +0,0 @@ -FROM ros:humble - -# Add ROS 2 testing server -RUN echo "deb [ signed-by=/usr/share/keyrings/ros2-latest-archive-keyring.gpg ] http://packages.ros.org/ros2-testing/ubuntu jammy main" > /etc/apt/sources.list.d/ros2-testing.list - -# install ros packages -RUN apt-get update && apt-get install -y \ - ros-${ROS_DISTRO}-irobot-create-msgs \ - ros-${ROS_DISTRO}-create3-republisher \ - git \ - nano \ - net-tools \ - iputils-ping && \ - rm -rf /var/lib/apt/lists/* - -ARG USERNAME=ros -ARG USER_UID=1000 -ARG USER_GID=$USER_UID - -# Create a non-root user -#RUN groupadd --gid $USER_GID $USERNAME \ -# && useradd -s /bin/bash --uid $USER_UID --gid $USER_GID -m $USERNAME \ -# # [Optional] Add sudo support for the non-root user -# && apt-get update \ -# && apt-get install -y sudo \ -# && echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME\ -# && chmod 0440 /etc/sudoers.d/$USERNAME \ -# # Cleanup -# && rm -rf /var/lib/apt/lists/* \ -# && echo "source /usr/share/bash-completion/completions/git" >> /home/$USERNAME/.bashrc \ -# && echo "if [ -f /opt/ros/${ROS_DISTRO}/setup.bash ]; then source /opt/ros/${ROS_DISTRO}/setup.bash; fi" >> /home/$USERNAME/.bashrc diff --git a/etc/turtlebot4/docker/build.sh b/etc/turtlebot4/docker/build.sh deleted file mode 100755 index 63529b7..0000000 --- a/etc/turtlebot4/docker/build.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -docker build -t create3-republisher . diff --git a/etc/turtlebot4/docker/config/create3.env b/etc/turtlebot4/docker/config/create3.env deleted file mode 100644 index 530d04d..0000000 --- a/etc/turtlebot4/docker/config/create3.env +++ /dev/null @@ -1,5 +0,0 @@ -export FASTRTPS_DEFAULT_PROFILES_FILE=/opt/config/fastrtps-repub.xml -export ROBOT_NAMESPACE= -export ROS_DOMAIN_ID=0 -export ROS_DISCOVERY_SERVER= -export RMW_IMPLEMENTATION=rmw_fastrtps_cpp diff --git a/etc/turtlebot4/docker/config/fastrtps-repub.xml b/etc/turtlebot4/docker/config/fastrtps-repub.xml deleted file mode 100644 index ea74c33..0000000 --- a/etc/turtlebot4/docker/config/fastrtps-repub.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - UDPv4 - - - - diff --git a/scripts/create_update.sh b/scripts/create_update.sh index 5847051..86f30c1 100755 --- a/scripts/create_update.sh +++ b/scripts/create_update.sh @@ -5,7 +5,7 @@ Help() { - echo "Create 3 update script for robots running H.1.0 or higher" + echo "Create 3 update script for robots running I.*.*" echo echo "usage: bash create_update.sh /path/to/image.swu [-h]" echo "options:" diff --git a/scripts/jazzy.sh b/scripts/jazzy.sh index d74efda..86cdeba 100755 --- a/scripts/jazzy.sh +++ b/scripts/jazzy.sh @@ -5,15 +5,6 @@ sudo apt update && sudo apt install curl gnupg lsb-release -y 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 $(source /etc/os-release && echo $UBUNTU_CODENAME) main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null -# Add Docker sources -sudo install -m 0755 -d /etc/apt/keyrings -sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc -sudo chmod a+r /etc/apt/keyrings/docker.asc -echo \ - "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \ - $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ - sudo tee /etc/apt/sources.list.d/docker.list > /dev/null - # Install the packages sudo apt update sudo apt install -y \ @@ -25,12 +16,4 @@ wget \ ros-dev-tools \ socat \ network-manager \ -chrony \ -docker-ce \ -docker-ce-cli \ -containerd.io \ -docker-buildx-plugin \ -docker-compose-plugin - -# Docker post-install configuration -sudo usermod -a -G docker $(whoami) +chrony From cfd0ecae220850ffad304f5e4cad7b2f42188e2c Mon Sep 17 00:00:00 2001 From: Chris Iverach-Brereton Date: Wed, 4 Sep 2024 15:04:05 -0400 Subject: [PATCH 10/40] Add a note about firmware compatibility to the readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index fd99b6e..2fd221c 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ Setup scripts and tools for the TurtleBot 4 Raspberry Pi. Visit the [TurtleBot 4 User Manual](https://turtlebot.github.io/turtlebot4-user-manual/software/turtlebot4_setup.html) for more details. +Make sure your Create® 3 is updated to the `I.*.*` firmware; older versions of the firmware are not compatible with ROS 2 Jazzy. + # Create an image manually Follow these instructions if you wish to create a Turtlebot4 image manually. From ae6de0757eb741835b2851c0127c34e0b248e3eb Mon Sep 17 00:00:00 2001 From: Chris Iverach-Brereton Date: Thu, 5 Sep 2024 12:15:41 -0400 Subject: [PATCH 11/40] Add exception handling to the file i/o so the node doesn't just crash if we're missing a file --- turtlebot4_setup/conf.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/turtlebot4_setup/conf.py b/turtlebot4_setup/conf.py index 7271c5e..e7bf36b 100644 --- a/turtlebot4_setup/conf.py +++ b/turtlebot4_setup/conf.py @@ -3,6 +3,7 @@ import yaml import subprocess import shlex +import sys from enum import Enum @@ -137,16 +138,24 @@ def apply_default(self, conf): self.discovery_conf = copy.deepcopy(self.default_discovery_conf) def read(self): - self.read_system() - self.read_wifi() - self.read_bash() - self.read_discovery() # Must come after read_bash in order to have the discovery server envar + try: + self.read_system() + self.read_wifi() + self.read_bash() + self.read_discovery() # Must come after read_bash in order to have the discovery server envar + except Exception as err: + print(f'Error reading configuration: {err}. Terminating') + sys.exit(1) def write(self): - self.write_system() - self.write_wifi() - self.write_discovery() - self.write_bash() + try: + self.write_system() + self.write_wifi() + self.write_discovery() + self.write_bash() + except Exception as err: + print(f'Error writing configuration: {err}. Configuration may be incomplete') + sys.exit(1) def read_system(self): with open(self.system_file, 'r') as f: From ed94f15ce226a1744ebfcf11c77c3ab4b0697218 Mon Sep 17 00:00:00 2001 From: Chris Iverach-Brereton Date: Thu, 5 Sep 2024 12:16:10 -0400 Subject: [PATCH 12/40] Add proper escape characters to title backslashes --- turtlebot4_setup/turtlebot4_setup | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/turtlebot4_setup/turtlebot4_setup b/turtlebot4_setup/turtlebot4_setup index 2de70b4..f017bf2 100755 --- a/turtlebot4_setup/turtlebot4_setup +++ b/turtlebot4_setup/turtlebot4_setup @@ -15,8 +15,8 @@ class Turtlebot4Setup(): title = """ _____ _ _ ___ _ _ _ ___ _ |_ _| _ _ _| |_| |___| _ ) ___| |_| | | / __| ___| |_ _ _ _ __ - | || || | '_| _| / -_) _ \/ _ \ _|_ _| \__ \/ -_) _| || | '_ \\ - |_| \_,_|_| \__|_\___|___/\___/\__| |_| |___/\___|\__|\_,_| .__/ + | || || | '_| _| / -_) _ \\/ _ \\ _|_ _| \\__ \\/ -_) _| || | '_ \\ + |_| \\_,_|_| \\__|_\\___|___/\\___/\\__| |_| |___/\\___|\\__|\\_,_| .__/ |_| """ @@ -104,9 +104,9 @@ class Turtlebot4Setup(): text = 'No changes made.\n' text = """ _ _ ___ _ _ _ - /_\ _ __ _ __| |_ _ / __| ___| |_| |_(_)_ _ __ _ ___ - / _ \| '_ \ '_ \ | || | \__ \/ -_) _| _| | ' \/ _` (_-< - /_/ \_\ .__/ .__/_|\_, | |___/\___|\__|\__|_|_||_\__, /__/ + /_\\ _ __ _ __| |_ _ / __| ___| |_| |_(_)_ _ __ _ ___ + / _ \\| '_ \\ '_ \\ | || | \\__ \\/ -_) _| _| | ' \\/ _` (_-< + /_/ \\_\\ .__/ .__/_|\\_, | |___/\\___|\\__|\\__|_|_||_\\__, /__/ |_| |_| |__/ |___/ \n\n""" + text text += '\nApply these settings?\n' From 92cab31970825d15cbaab186f49b52d7b2844b66 Mon Sep 17 00:00:00 2001 From: Chris Iverach-Brereton Date: Thu, 5 Sep 2024 12:23:49 -0400 Subject: [PATCH 13/40] Add improved exception handling to the wifi settings parser --- turtlebot4_setup/conf.py | 54 ++++++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/turtlebot4_setup/conf.py b/turtlebot4_setup/conf.py index e7bf36b..420a95a 100644 --- a/turtlebot4_setup/conf.py +++ b/turtlebot4_setup/conf.py @@ -195,36 +195,42 @@ def write_system(self): subprocess.run(shlex.split('sudo mv /tmp' + self.hostname_file + ' ' + self.hostname_file)) def read_wifi(self): - netplan = yaml.load(open(self.netplan_wifis_file, 'r'), yaml.SafeLoader) - # wlan0 Config - wlan0 = netplan['network']['wifis']['wlan0'] + try: + # Try to open the existing wifi configuration, but if it doesn't exist we can carry on + netplan = yaml.load(open(self.netplan_wifis_file, 'r'), yaml.SafeLoader) - # Get SSID - self.set(WifiOptions.SSID, list(wlan0['access-points'])[0]) - # SSID settings - ssid_settings = wlan0['access-points'][self.get(WifiOptions.SSID)] + # wlan0 Config + wlan0 = netplan['network']['wifis']['wlan0'] - self.set(WifiOptions.PASSWORD, ssid_settings.get('password')) + # Get SSID + self.set(WifiOptions.SSID, list(wlan0['access-points'])[0]) + # SSID settings + ssid_settings = wlan0['access-points'][self.get(WifiOptions.SSID)] - if wlan0.get('addresses'): - self.set(WifiOptions.IP, wlan0['addresses'][0]) - else: - self.set(WifiOptions.IP, None) + self.set(WifiOptions.PASSWORD, ssid_settings.get('password')) - if wlan0.get('dhcp4') is True: - self.set(WifiOptions.DHCP, True) - else: - self.set(WifiOptions.DHCP, False) + if wlan0.get('addresses'): + self.set(WifiOptions.IP, wlan0['addresses'][0]) + else: + self.set(WifiOptions.IP, None) - if ssid_settings.get('mode') == 'ap': - self.set(WifiOptions.WIFI_MODE, 'Access Point') - else: - self.set(WifiOptions.WIFI_MODE, 'Client') + if wlan0.get('dhcp4') is True: + self.set(WifiOptions.DHCP, True) + else: + self.set(WifiOptions.DHCP, False) - if ssid_settings.get('band'): - self.set(WifiOptions.BAND, ssid_settings.get('band')) - else: - self.set(WifiOptions.BAND, 'Any') + if ssid_settings.get('mode') == 'ap': + self.set(WifiOptions.WIFI_MODE, 'Access Point') + else: + self.set(WifiOptions.WIFI_MODE, 'Client') + + if ssid_settings.get('band'): + self.set(WifiOptions.BAND, ssid_settings.get('band')) + else: + self.set(WifiOptions.BAND, 'Any') + except: + # If the wifi configuration doesn't have a wlan0 configuration, just skip this + pass def write_wifi(self): ssid = self.get(WifiOptions.SSID) From 209f6a255e3063f5a600362dfaa50e24a9bee379 Mon Sep 17 00:00:00 2001 From: Chris Iverach-Brereton Date: Tue, 10 Sep 2024 11:53:34 -0400 Subject: [PATCH 14/40] Update CI --- .github/workflows/ci.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 043e79f..996fee0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,6 +8,14 @@ jobs: runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v2.3.4 - - uses: ros-tooling/setup-ros@v0.3 + - uses: ros-tooling/setup-ros@v0.7 with: required-ros-distributions: jazzy + - uses: ros-tooling/action-ros-ci@v0.3 + id: action_ros_ci_step + with: + target-ros2-distro: jazzy + import-token: ${{ secrets.GITHUB_TOKEN }} + skip-tests: false + package-name: + turtlebot4_setup From 07418fd4f4b673e0316dfcb12c251ee1e0b9eedf Mon Sep 17 00:00:00 2001 From: Chris Iverach-Brereton Date: Tue, 10 Sep 2024 12:25:23 -0400 Subject: [PATCH 15/40] Properly escape all \ characters in stylized titles, add translation & link to generator page in comments --- turtlebot4_setup/menu.py | 12 ++++---- turtlebot4_setup/ros_setup.py | 49 ++++++++++++++++--------------- turtlebot4_setup/turtlebot4_setup | 2 ++ turtlebot4_setup/wifi.py | 11 +++---- 4 files changed, 38 insertions(+), 36 deletions(-) diff --git a/turtlebot4_setup/menu.py b/turtlebot4_setup/menu.py index 5a1c945..8de0a20 100644 --- a/turtlebot4_setup/menu.py +++ b/turtlebot4_setup/menu.py @@ -7,8 +7,6 @@ import os -import readline - class MenuEntry(): @@ -123,13 +121,13 @@ def show(self): class HelpMenu(Menu): - + # Help -- https://patorjk.com/software/taag/#p=display&v=0&f=Small title = """ - _ _ _ - | || |___| |_ __ + _ _ _ + | || |___| |_ __ | __ / -_) | '_ \\ - |_||_\___|_| .__/ - |_| + |_||_\\___|_| .__/ + |_| """ def __init__(self, text: str, display_help_title=True) -> None: diff --git a/turtlebot4_setup/ros_setup.py b/turtlebot4_setup/ros_setup.py index 93a3edb..68b8c04 100644 --- a/turtlebot4_setup/ros_setup.py +++ b/turtlebot4_setup/ros_setup.py @@ -9,13 +9,13 @@ class RosSetup(): - + # ROS Setup -- https://patorjk.com/software/taag/#p=display&v=0&f=Small title = """ - ___ ___ ___ ___ _ - | _ \/ _ \/ __| / __| ___| |_ _ _ _ __ - | / (_) \__ \ \__ \/ -_) _| || | '_ \\ - |_|_\\\___/|___/ |___/\\___|\\__|\\_,_| .__/ - |_| + ___ ___ ___ ___ _ + | _ \\\\/ _ \\\\/ __| / __| ___| |_ _ _ _ __ + | / (_) \\\\__ \\\\ \\\\__ \\\\/ -_) _| || | '_ \\\\ + |_|_\\\\\\\\___/|___/ |___/\\\\___|\\\\__|\\\\_,_| .__/ + |_| """ setup_dir = '/etc/turtlebot4/' @@ -38,13 +38,13 @@ def show(self): class BashSetup(): - + # Bash Setup -- https://patorjk.com/software/taag/#p=display&v=0&f=Small title = """ - ___ _ ___ _ - | _ ) __ _ __| |_ / __| ___| |_ _ _ _ __ - | _ \/ _` (_-< ' \ \__ \/ -_) _| || | '_ \\ - |___/\__,_/__/_||_| |___/\___|\__|\_,_| .__/ - |_| + ___ _ ___ _ + | _ ) __ _ __| |_ / __| ___| |_ _ _ _ __ + | _ \\/ _` (_-< ' \\ \\__ \\/ -_) _| || | '_ \\ + |___/\\__,_/__/_||_| |___/\\___|\\__|\\_,_| .__/ + |_| """ def __init__(self, conf: Conf) -> None: @@ -99,7 +99,7 @@ def set_ros_domain_id(self): default_response=self.conf.get(BashOptions.DOMAIN_ID), response_type=int, note='ROS Domain ID (0-101) or (215-232)') - domain_id = p.show() + domain_id = p.show() domain_id = max(0, min(int(domain_id), 232)) if (domain_id > 101 and domain_id < 215): domain_id = 101 @@ -157,12 +157,13 @@ def apply_defaults(self): class DiscoveryServer(): + # Discovery Server -- https://patorjk.com/software/taag/#p=display&v=0&f=Small title = """ - ___ _ ___ - | \(_)___ __ _____ _____ _ _ _ _ / __| ___ _ ___ _____ _ _ - | |) | (_- None: @@ -261,13 +262,13 @@ def save_settings(self): class RobotUpstart(): - + # Robot Upstart -- https://patorjk.com/software/taag/#p=display&v=0&f=Small title = """ - ___ _ _ _ _ _ _ - | _ \___| |__ ___| |_ | | | |_ __ __| |_ __ _ _ _| |_ - | / _ \ '_ \/ _ \ _| | |_| | '_ (_-< _/ _` | '_| _| - |_|_\___/_.__/\___/\__| \___/| .__/__/\__\__,_|_| \__| - |_| + ___ _ _ _ _ _ _ + | _ \\___| |__ ___| |_ | | | |_ __ __| |_ __ _ _ _| |_ + | / _ \\ '_ \\/ _ \\ _| | |_| | '_ (_-< _/ _` | '_| _| + |_|_\\___/_.__/\\___/\\__| \\___/| .__/__/\\__\\__,_|_| \\__| + |_| """ def __init__(self, configs: Conf) -> None: diff --git a/turtlebot4_setup/turtlebot4_setup b/turtlebot4_setup/turtlebot4_setup index f017bf2..45c7b6e 100755 --- a/turtlebot4_setup/turtlebot4_setup +++ b/turtlebot4_setup/turtlebot4_setup @@ -12,6 +12,7 @@ from turtlebot4_setup.conf import Conf, SystemOptions, BashOptions, WifiOptions, class Turtlebot4Setup(): + # TurtleBot4 Setup -- https://patorjk.com/software/taag/#p=display&v=0&f=Small title = """ _____ _ _ ___ _ _ _ ___ _ |_ _| _ _ _| |_| |___| _ ) ___| |_| | | / __| ___| |_ _ _ _ __ @@ -102,6 +103,7 @@ class Turtlebot4Setup(): if text == '': text = 'No changes made.\n' + # Apply Settings -- https://patorjk.com/software/taag/#p=display&v=0&f=Small text = """ _ _ ___ _ _ _ /_\\ _ __ _ __| |_ _ / __| ___| |_| |_(_)_ _ __ _ ___ diff --git a/turtlebot4_setup/wifi.py b/turtlebot4_setup/wifi.py index 1202322..58f60c2 100644 --- a/turtlebot4_setup/wifi.py +++ b/turtlebot4_setup/wifi.py @@ -6,12 +6,13 @@ class WifiSetup(): + # WiFi Setup -- https://patorjk.com/software/taag/#p=display&v=0&f=Small title = """ - __ ___ ___ _ ___ _ - \ \ / (_) __(_) / __| ___| |_ _ _ _ __ - \ \/\/ /| | _|| | \__ \/ -_) _| || | '_ \\ - \_/\_/ |_|_| |_| |___/\___|\__|\_,_| .__/ - |_| + __ ___ ___ _ ___ _ + \\ \\ / (_) __(_) / __| ___| |_ _ _ _ __ + \\ \\/\\/ /| | _|| | \\__ \\/ -_) _| || | '_ \\ + \\_/\\_/ |_|_| |_| |___/\\___|\\__|\\_,_| .__/ + |_| """ def __init__(self, configs: Conf) -> None: From 84c0f14a2cd07c5dce37bef5ed8943b6b7d50e2f Mon Sep 17 00:00:00 2001 From: Chris Iverach-Brereton Date: Tue, 10 Sep 2024 14:37:14 -0400 Subject: [PATCH 16/40] Add copyright & contribution notices, fix up code formatting, import ordering. Disable linting for some specific lines where appropriate --- CONTRIBUTING.md | 15 ++++ turtlebot4_setup/conf.py | 64 ++++++++++++----- turtlebot4_setup/menu.py | 38 +++++++--- turtlebot4_setup/ros_setup.py | 115 +++++++++++++++++++----------- turtlebot4_setup/turtlebot4_setup | 24 ++++++- turtlebot4_setup/wifi.py | 65 +++++++++++------ 6 files changed, 229 insertions(+), 92 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..35ba863 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,15 @@ +# Contributing to TurtleBot4 Setup + +Any contribution that you make to this repository will +be under the Apache 2 License, as dictated by that +[license](http://www.apache.org/licenses/LICENSE-2.0.html): + +~~~ +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. +~~~ diff --git a/turtlebot4_setup/conf.py b/turtlebot4_setup/conf.py index 420a95a..43696f3 100644 --- a/turtlebot4_setup/conf.py +++ b/turtlebot4_setup/conf.py @@ -1,11 +1,33 @@ +#!/usr/bin/env python3 + +# Copyright 2024 Clearpath Robotics +# +# 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. + import copy +from enum import Enum import os -import yaml -import subprocess import shlex +import subprocess import sys -from enum import Enum +import yaml + + +__author__ = 'Roni Kreinin' +__email__ = 'rkreinin@clearpathrobotics.com' +__copyright__ = 'Copyright © 2023 Clearpath Robotics. All rights reserved.' +__license__ = 'Apache 2.0' class SystemOptions(str, Enum): @@ -117,7 +139,7 @@ def get(self, conf): return self.discovery_conf.get(conf) return None - def set(self, conf, value): + def set(self, conf, value): # noqa: A003 if isinstance(conf, SystemOptions): self.system_conf[conf] = value elif isinstance(conf, WifiOptions): @@ -142,7 +164,8 @@ def read(self): self.read_system() self.read_wifi() self.read_bash() - self.read_discovery() # Must come after read_bash in order to have the discovery server envar + # Must come after read_bash in order to have the discovery server envar + self.read_discovery() except Exception as err: print(f'Error reading configuration: {err}. Terminating') sys.exit(1) @@ -228,7 +251,7 @@ def read_wifi(self): self.set(WifiOptions.BAND, ssid_settings.get('band')) else: self.set(WifiOptions.BAND, 'Any') - except: + except Exception: # If the wifi configuration doesn't have a wlan0 configuration, just skip this pass @@ -270,7 +293,7 @@ def write_wifi(self): } with open('/tmp' + self.netplan_wifis_file, 'w') as f: - f.write('# This file was automatically created by the turtlebot4-setup tool and should not be manually modified\n\n') + f.write('# This file was automatically created by the turtlebot4-setup tool and should not be manually modified\n\n') # noqa: E501 yaml.dump(netplan, stream=open('/tmp' + self.netplan_wifis_file, 'a'), @@ -279,7 +302,8 @@ def write_wifi(self): default_flow_style=False, default_style=None) - subprocess.run(shlex.split('sudo mv /tmp' + self.netplan_wifis_file + ' ' + self.netplan_wifis_file)) + subprocess.run(shlex.split( + 'sudo mv /tmp' + self.netplan_wifis_file + ' ' + self.netplan_wifis_file)) def read_bash(self): with open(self.setup_bash_file, 'r') as f: @@ -314,7 +338,8 @@ def write_bash(self): # Ensure super client is only applied on user terminals bash[i] = f'[ -t 0 ] && export {k}={v} || export {k}=False\n' else: - # Quotations required around v to handle multiple servers in discovery server + # Quotations required around v to handle multiple servers + # in discovery server bash[i] = f'export {k}=\"{v}\"\n' found = True @@ -322,14 +347,15 @@ def write_bash(self): if not found: if (k == BashOptions.SUPER_CLIENT and str(v) == 'True'): # Ensure super client is only applied on user terminals - bash.insert(0,f'[ -t 0 ] && export {k}={v} || export {k}=False\n') + bash.insert(0, f'[ -t 0 ] && export {k}={v} || export {k}=False\n') else: - # Quotations required around v to handle multiple servers in discovery server - bash.insert(0,f'export {k}=\"{v}\"\n') + # Quotations required around v to handle multiple servers + # in discovery server + bash.insert(0, f'export {k}=\"{v}\"\n') with open('/tmp' + self.setup_bash_file, 'w') as f: f.writelines(bash) - subprocess.run(shlex.split('sudo mv /tmp' + self.setup_bash_file + ' ' + self.setup_bash_file)) + subprocess.run(shlex.split(f'sudo mv /tmp{self.setup_bash_file} {self.setup_bash_file}')) for k, v in self.bash_conf.items(): if v is None: @@ -359,10 +385,11 @@ def read_discovery(self): self.set(DiscoveryOptions.OFFBOARD_ID, i) self.set(DiscoveryOptions.OFFBOARD_IP, server[0].strip('\'"')) if len(server) > 1: - self.set(DiscoveryOptions.OFFBOARD_PORT, int(server[1].strip('\'"'))) + self.set( + DiscoveryOptions.OFFBOARD_PORT, int(server[1].strip('\'"'))) else: self.set(DiscoveryOptions.OFFBOARD_PORT, 11811) - except: + except Exception: self.discovery_conf = self.default_discovery_conf def write_discovery(self): @@ -373,10 +400,11 @@ def write_discovery(self): with open('/tmp' + self.discovery_sh_file, 'w') as f: f.write('#!/bin/bash\n') - f.write('# This file was automatically created by the turtlebot4-setup tool and should not be manually modified\n\n') + f.write('# This file was automatically created by the turtlebot4-setup tool and should not be manually modified\n\n') # noqa: E501 f.write(f'source {self.get(BashOptions.WORKSPACE)}\n') - f.write(f'fastdds discovery -i {self.get(DiscoveryOptions.SERVER_ID)} -p {self.get(DiscoveryOptions.PORT)}') - subprocess.run(shlex.split('sudo mv /tmp' + self.discovery_sh_file + ' ' + self.discovery_sh_file)) + f.write(f'fastdds discovery -i {self.get(DiscoveryOptions.SERVER_ID)} -p {self.get(DiscoveryOptions.PORT)}') # noqa: E501 + subprocess.run(shlex.split( + 'sudo mv /tmp' + self.discovery_sh_file + ' ' + self.discovery_sh_file)) else: self.set(BashOptions.DISCOVERY_SERVER, None) self.set(BashOptions.SUPER_CLIENT, False) diff --git a/turtlebot4_setup/menu.py b/turtlebot4_setup/menu.py index 8de0a20..546a28d 100644 --- a/turtlebot4_setup/menu.py +++ b/turtlebot4_setup/menu.py @@ -1,11 +1,33 @@ -from simple_term_menu_vendor.simple_term_menu import TerminalMenu +#!/usr/bin/env python3 + +# Copyright 2024 Clearpath Robotics +# +# 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. + +import os -from typing import List, Callable, Union +from typing import Callable, List, Union from pygments import formatters, highlight, lexers from pygments.util import ClassNotFound -import os +from simple_term_menu_vendor.simple_term_menu import TerminalMenu + + +__author__ = 'Roni Kreinin' +__email__ = 'rkreinin@clearpathrobotics.com' +__copyright__ = 'Copyright © 2023 Clearpath Robotics. All rights reserved.' +__license__ = 'Apache 2.0' class MenuEntry(): @@ -78,7 +100,7 @@ def reset_term_menu(self): self.menu = self.create_term_menu() self.menu_sel = 0 - def exit(self): + def exit(self): # noqa: A003 self.menu_exit = True def show(self, reset=True): @@ -96,7 +118,7 @@ def show(self, reset=True): class OptionsMenu(Menu): - def __init__(self, title: Union[str, Callable], menu_entries: List[str], default_option=None) -> None: + def __init__(self, title: Union[str, Callable], menu_entries: List[str], default_option=None) -> None: # noqa: E501 self.option = default_option self.menu_entries = [] @@ -197,12 +219,12 @@ def list_files(self): return files def highlight_file(self, filepath): - with open(filepath, "r") as f: + with open(filepath, 'r') as f: file_content = f.read() try: lexer = lexers.get_lexer_for_filename(filepath, stripnl=False, stripall=False) except ClassNotFound: - lexer = lexers.get_lexer_by_name("text", stripnl=False, stripall=False) - formatter = formatters.TerminalFormatter(bg="dark") # dark or light + lexer = lexers.get_lexer_by_name('text', stripnl=False, stripall=False) + formatter = formatters.TerminalFormatter(bg='dark') # dark or light highlighted_file_content = highlight(file_content, lexer, formatter) return highlighted_file_content diff --git a/turtlebot4_setup/ros_setup.py b/turtlebot4_setup/ros_setup.py index 68b8c04..c334500 100644 --- a/turtlebot4_setup/ros_setup.py +++ b/turtlebot4_setup/ros_setup.py @@ -1,11 +1,32 @@ -from turtlebot4_setup.menu import Menu, OptionsMenu, MenuEntry, Prompt -from turtlebot4_setup.conf import Conf, SystemOptions, BashOptions, DiscoveryOptions +#!/usr/bin/env python3 + +# Copyright 2024 Clearpath Robotics +# +# 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. import os - -import subprocess, shlex +import shlex +import subprocess import robot_upstart +from turtlebot4_setup.conf import BashOptions, Conf, DiscoveryOptions, SystemOptions +from turtlebot4_setup.menu import Menu, MenuEntry, OptionsMenu, Prompt + + +__author__ = 'Roni Kreinin' +__email__ = 'rkreinin@clearpathrobotics.com' +__copyright__ = 'Copyright © 2023 Clearpath Robotics. All rights reserved.' +__license__ = 'Apache 2.0' class RosSetup(): @@ -138,14 +159,15 @@ def set_robot_namespace(self): note='ROS2 namespace') # Add '/' if needed ns = p.show() - if ns != None and ns[0] != '/': + if ns is not None and ns[0] != '/': ns = '/' + ns self.conf.set(BashOptions.NAMESPACE, ns) def set_turtlebot4_diagnostics(self): - options = OptionsMenu(title=BashOptions.DIAGNOSTICS, - menu_entries=['Enabled', 'Disabled'], - default_option='Enabled' if self.conf.get(BashOptions.DIAGNOSTICS) == '1' else 'Disabled') + options = OptionsMenu( + title=BashOptions.DIAGNOSTICS, + menu_entries=['Enabled', 'Disabled'], + default_option='Enabled' if self.conf.get(BashOptions.DIAGNOSTICS) == '1' else 'Disabled') # noqa: E501 self.conf.set(BashOptions.DIAGNOSTICS, '1' if options.show() == 'Enabled' else '0') def save_settings(self): @@ -169,21 +191,28 @@ class DiscoveryServer(): def __init__(self, configs: Conf) -> None: self.conf = configs - self.entries = [MenuEntry(entry=self.format_entry('Enabled', DiscoveryOptions.ENABLED), - function=self.set_enabled), - MenuEntry(entry=self.format_entry('Onboard Server - Port', DiscoveryOptions.PORT), - function=self.set_port), - MenuEntry(entry=self.format_entry('Onboard Server - Server ID', DiscoveryOptions.SERVER_ID), - function=self.set_server_id), - MenuEntry(entry=self.format_entry('Offboard Server - IP', DiscoveryOptions.OFFBOARD_IP), - function=self.set_offboard_ip), - MenuEntry(entry=self.format_entry('Offboard Server - Port', DiscoveryOptions.OFFBOARD_PORT), - function=self.set_offboard_port), - MenuEntry(entry=self.format_entry('Offboard Server - Server ID', DiscoveryOptions.OFFBOARD_ID), - function=self.set_offboard_server_id), - MenuEntry('', None), - MenuEntry(entry='Apply Defaults', function=self.apply_defaults), - MenuEntry(entry='Save', function=self.save_settings)] + self.entries = [ + MenuEntry( + entry=self.format_entry('Enabled', DiscoveryOptions.ENABLED), + function=self.set_enabled), + MenuEntry( + entry=self.format_entry('Onboard Server - Port', DiscoveryOptions.PORT), + function=self.set_port), + MenuEntry( + entry=self.format_entry('Onboard Server - Server ID', DiscoveryOptions.SERVER_ID), + function=self.set_server_id), + MenuEntry( + entry=self.format_entry('Offboard Server - IP', DiscoveryOptions.OFFBOARD_IP), + function=self.set_offboard_ip), + MenuEntry( + entry=self.format_entry('Offboard Server - Port', DiscoveryOptions.OFFBOARD_PORT), + function=self.set_offboard_port), + MenuEntry( + entry=self.format_entry('Offboard Server - Server ID', DiscoveryOptions.OFFBOARD_ID), # noqa: E501 + function=self.set_offboard_server_id), + MenuEntry('', None), + MenuEntry(entry='Apply Defaults', function=self.apply_defaults), + MenuEntry(entry='Save', function=self.save_settings)] self.menu = Menu(title=self.title, menu_entries=self.entries) @@ -218,7 +247,7 @@ def set_server_id(self): note='Onboard Discovery Server ID (0-255)') server_id = p.show() server_id = max(0, min(int(server_id), 255)) - if (self.conf.get(DiscoveryOptions.OFFBOARD_IP) and (server_id == int(self.conf.get(DiscoveryOptions.OFFBOARD_ID)))): + if (self.conf.get(DiscoveryOptions.OFFBOARD_IP) and (server_id == int(self.conf.get(DiscoveryOptions.OFFBOARD_ID)))): # noqa: 501 return self.conf.set(DiscoveryOptions.SERVER_ID, server_id) @@ -246,7 +275,7 @@ def set_offboard_server_id(self): p = Prompt(prompt='Server ID [{0}]: '.format(self.conf.get(DiscoveryOptions.OFFBOARD_ID)), default_response=self.conf.get(DiscoveryOptions.OFFBOARD_ID), response_type=int, - note='Offboard Discovery Server ID (0-255) - Cannot be the same as the onboard server') + note='Offboard Discovery Server ID (0-255) - Cannot be the same as the onboard server') # noqa: 501 server_id = p.show() server_id = max(0, min(int(server_id), 255)) if (server_id == int(self.conf.get(DiscoveryOptions.SERVER_ID))): @@ -283,7 +312,8 @@ def __init__(self, configs: Conf) -> None: function=self.install), MenuEntry(entry='Uninstall', function=self.uninstall), - MenuEntry(entry='',function=None), + MenuEntry(entry='', + function=None), MenuEntry(entry='Status', function=self.view_service_status)] @@ -350,7 +380,8 @@ def uninstall(self): # Uninstall Discovery Server Service if os.path.exists('/lib/systemd/system/discovery.service'): - subprocess.run(shlex.split('sudo systemctl stop discovery.service'), capture_output=True) + subprocess.run(shlex.split( + 'sudo systemctl stop discovery.service'), capture_output=True) discovery_job = robot_upstart.Job(workspace_setup=os.environ['ROBOT_SETUP']) discovery_job.uninstall(Provider=TurtleBot4Extras) @@ -367,26 +398,26 @@ def generate_install(self): with open('/etc/turtlebot4/discovery.sh') as f: discovery_sh_contents = f.read() return { - "/lib/systemd/system/discovery.service": { - "content": discovery_conf_contents, - "mode": 0o644 + '/lib/systemd/system/discovery.service': { + 'content': discovery_conf_contents, + 'mode': 0o644 }, - "/usr/sbin/discovery": { - "content": discovery_sh_contents, - "mode": 0o755 + '/usr/sbin/discovery': { + 'content': discovery_sh_contents, + 'mode': 0o755 }, - "/etc/systemd/system/multi-user.target.wants/discovery.service": { - "symlink": "/lib/systemd/system/discovery.service" + '/etc/systemd/system/multi-user.target.wants/discovery.service': { + 'symlink': '/lib/systemd/system/discovery.service' }} def generate_uninstall(self): return { - "/lib/systemd/system/discovery.service": { - "remove": True + '/lib/systemd/system/discovery.service': { + 'remove': True }, - "/usr/sbin/discovery": { - "remove": True + '/usr/sbin/discovery': { + 'remove': True }, - "/etc/systemd/system/multi-user.target.wants/discovery.service": { - "remove": True - }} \ No newline at end of file + '/etc/systemd/system/multi-user.target.wants/discovery.service': { + 'remove': True + }} diff --git a/turtlebot4_setup/turtlebot4_setup b/turtlebot4_setup/turtlebot4_setup index 45c7b6e..5afc354 100755 --- a/turtlebot4_setup/turtlebot4_setup +++ b/turtlebot4_setup/turtlebot4_setup @@ -1,14 +1,34 @@ #!/usr/bin/env python3 +# Copyright 2024 Clearpath Robotics +# +# 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. + import copy import os import subprocess import shlex -from turtlebot4_setup.wifi import WifiSetup +from turtlebot4_setup.conf import Conf, SystemOptions, BashOptions, WifiOptions, DiscoveryOptions from turtlebot4_setup.menu import Menu, MenuEntry, OptionsMenu, Prompt, HelpMenu, PreviewMenu from turtlebot4_setup.ros_setup import RosSetup -from turtlebot4_setup.conf import Conf, SystemOptions, BashOptions, WifiOptions, DiscoveryOptions +from turtlebot4_setup.wifi import WifiSetup + + +__author__ = 'Roni Kreinin' +__email__ = 'rkreinin@clearpathrobotics.com' +__copyright__ = 'Copyright © 2023 Clearpath Robotics. All rights reserved.' +__license__ = 'Apache 2.0' class Turtlebot4Setup(): diff --git a/turtlebot4_setup/wifi.py b/turtlebot4_setup/wifi.py index 58f60c2..8d68071 100644 --- a/turtlebot4_setup/wifi.py +++ b/turtlebot4_setup/wifi.py @@ -1,8 +1,27 @@ -from turtlebot4_setup.menu import Menu, MenuEntry, OptionsMenu, Prompt, PreviewMenu - -import subprocess, shlex +#!/usr/bin/env python3 + +# Copyright 2024 Clearpath Robotics +# +# 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 turtlebot4_setup.conf import Conf, WifiOptions +from turtlebot4_setup.menu import Menu, MenuEntry, OptionsMenu, Prompt + + +__author__ = 'Roni Kreinin' +__email__ = 'rkreinin@clearpathrobotics.com' +__copyright__ = 'Copyright © 2023 Clearpath Robotics. All rights reserved.' +__license__ = 'Apache 2.0' class WifiSetup(): @@ -20,24 +39,26 @@ def __init__(self, configs: Conf) -> None: self.conf.read() - self.entries = [MenuEntry(entry=self.format_entry('Wi-Fi Mode', WifiOptions.WIFI_MODE), - function=self.set_wifi_mode), - MenuEntry(entry=self.format_entry('SSID', WifiOptions.SSID), - function=self.set_ssid), - MenuEntry(entry=self.format_entry('Password', WifiOptions.PASSWORD), - function=self.set_password), - # TODO(rkreinin): Set Reg Domain in 22.04 - # MenuEntry(entry=self.format_entry('Regulatory Domain', WifiOptions.REG_DOMAIN), - # function=self.set_reg_domain), - MenuEntry(entry=self.format_entry('Band', WifiOptions.BAND), - function=self.set_band), - MenuEntry(entry=self.format_entry('IP Address', WifiOptions.IP), - function=self.set_ip_address), - MenuEntry(entry=self.format_entry('DHCP', WifiOptions.DHCP), - function=self.set_dhcp), - MenuEntry('', None), - MenuEntry(entry='Apply Defaults', function=self.apply_defaults), - MenuEntry(entry='Save', function=self.save_settings),] + self.entries = [ + MenuEntry(entry=self.format_entry('Wi-Fi Mode', WifiOptions.WIFI_MODE), + function=self.set_wifi_mode), + MenuEntry(entry=self.format_entry('SSID', WifiOptions.SSID), + function=self.set_ssid), + MenuEntry(entry=self.format_entry('Password', WifiOptions.PASSWORD), + function=self.set_password), + # TODO(rkreinin): Set Reg Domain in 22.04 + # MenuEntry(entry=self.format_entry('Regulatory Domain', WifiOptions.REG_DOMAIN), + # function=self.set_reg_domain), + MenuEntry(entry=self.format_entry('Band', WifiOptions.BAND), + function=self.set_band), + MenuEntry(entry=self.format_entry('IP Address', WifiOptions.IP), + function=self.set_ip_address), + MenuEntry(entry=self.format_entry('DHCP', WifiOptions.DHCP), + function=self.set_dhcp), + MenuEntry('', None), + MenuEntry(entry='Apply Defaults', function=self.apply_defaults), + MenuEntry(entry='Save', function=self.save_settings), + ] self.menu = Menu(self.title, self.entries) @@ -67,7 +88,7 @@ def set_password(self): self.conf.set(WifiOptions.PASSWORD, p.show()) def set_reg_domain(self): - p = Prompt(prompt='Regulatory Domain ({0}): '.format(self.conf.get(WifiOptions.REG_DOMAIN)), + p = Prompt(prompt='Regulatory Domain ({0}): '.format(self.conf.get(WifiOptions.REG_DOMAIN)), # noqa: 501 default_response=self.conf.get(WifiOptions.REG_DOMAIN), note='Wireless regulatory domain. \n' + 'Common options:\n' + From 4f7130cf9adb8378daa6711fb85f30e75e007417 Mon Sep 17 00:00:00 2001 From: Chris Iverach-Brereton Date: Tue, 10 Sep 2024 15:01:11 -0400 Subject: [PATCH 17/40] Remove the docker-compose file; we don't need it anymore --- etc/turtlebot4/docker-compose.yml | 37 ------------------------------- 1 file changed, 37 deletions(-) delete mode 100644 etc/turtlebot4/docker-compose.yml diff --git a/etc/turtlebot4/docker-compose.yml b/etc/turtlebot4/docker-compose.yml deleted file mode 100644 index bbf1464..0000000 --- a/etc/turtlebot4/docker-compose.yml +++ /dev/null @@ -1,37 +0,0 @@ -services: - create3-republisher: - image: create3-republisher:latest - restart: always - env_file: - - /etc/turtlebot4/docker/config/create3.env - command: ros2 launch create3_republisher create3_republisher_launch.py robot_ns:=/create3 republisher_ns:=/ - profiles: - - create3 - volumes: - - /etc/turtlebot4/docker/config:/opt/config - networks: - vlan: - ipv4_address: 192.168.186.100 - tbt4_container_bridge: - ipv4_address: 192.168.187.100 - -networks: - # Give the docker container a public-facing IP address on the usb0 network to allow it to - # communicate with the Create3 hardware. - vlan: - driver: macvlan - driver_opts: - parent: usb0 - ipam: - config: - - subnet: 192.168.186.0/24 - ip_range: 192.168.186.0/24 - - # Define a second interface for the container to allow it to talk with the host without - # risking spilling over into the 192.168.186.0/24 subnet - tbt4_container_bridge: - driver: bridge - ipam: - config: - - subnet: 192.168.187.0/24 - ip_range: 192.168.187.0/24 From fb9520e0dbf516dd2cc971d791660cd08843048e Mon Sep 17 00:00:00 2001 From: Chris Iverach-Brereton Date: Tue, 10 Sep 2024 15:01:28 -0400 Subject: [PATCH 18/40] Add XML namespaces & version to cyclone DDS config --- etc/turtlebot4/cyclonedds_rpi.xml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/etc/turtlebot4/cyclonedds_rpi.xml b/etc/turtlebot4/cyclonedds_rpi.xml index da8d8c0..7f0e73d 100644 --- a/etc/turtlebot4/cyclonedds_rpi.xml +++ b/etc/turtlebot4/cyclonedds_rpi.xml @@ -1,4 +1,8 @@ - + + From 8f81cb7c7938d6ed6f060cec682f74748c38ca68 Mon Sep 17 00:00:00 2001 From: Chris Iverach-Brereton Date: Tue, 10 Sep 2024 15:01:45 -0400 Subject: [PATCH 19/40] Revert FastDDS config now that we've removed the docker container --- etc/turtlebot4/fastdds_rpi.xml | 34 +++++----------------------------- 1 file changed, 5 insertions(+), 29 deletions(-) diff --git a/etc/turtlebot4/fastdds_rpi.xml b/etc/turtlebot4/fastdds_rpi.xml index fe5124a..7df14f3 100644 --- a/etc/turtlebot4/fastdds_rpi.xml +++ b/etc/turtlebot4/fastdds_rpi.xml @@ -1,32 +1,8 @@ - - - - udp_transport - UDPv4 - - - - - - - - - - - - - - udp_transport - - false - - - + + + + + From 861f877281f8b7ce4d8745ce24083f39d2c286d3 Mon Sep 17 00:00:00 2001 From: Chris Iverach-Brereton Date: Tue, 10 Sep 2024 15:02:07 -0400 Subject: [PATCH 20/40] Omit XML linting (for now); it's consistently timing out and failing --- CMakeLists.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0b5239a..35a3707 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,13 @@ install( DESTINATION lib/${PROJECT_NAME} ) +# disable XML linting; it consistently times out +# TODO (civerachb-cpr) -- figure out why it's timing out and re-enable +# hypothesis: it's related to the additional XML files in etc/turtlebot4 +list(APPEND AMENT_LINT_AUTO_EXCLUDE + ament_cmake_xmllint +) + if(BUILD_TESTING) find_package(ament_lint_auto REQUIRED) ament_lint_auto_find_test_dependencies() From 8fd68363e7aefaf54749c234b06545bd740c5091 Mon Sep 17 00:00:00 2001 From: Chris Iverach-Brereton Date: Tue, 10 Sep 2024 15:11:11 -0400 Subject: [PATCH 21/40] Class newline --- turtlebot4_setup/ros_setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/turtlebot4_setup/ros_setup.py b/turtlebot4_setup/ros_setup.py index c334500..714b3a9 100644 --- a/turtlebot4_setup/ros_setup.py +++ b/turtlebot4_setup/ros_setup.py @@ -389,6 +389,7 @@ def uninstall(self): class TurtleBot4Extras(robot_upstart.providers.Generic): + def post_install(self): pass From 84dea4d9d382c5c392e550687eb77c67e9a92991 Mon Sep 17 00:00:00 2001 From: Chris Iverach-Brereton Date: Wed, 11 Sep 2024 12:28:55 -0400 Subject: [PATCH 22/40] Add exception handling to the file preview --- turtlebot4_setup/menu.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/turtlebot4_setup/menu.py b/turtlebot4_setup/menu.py index 546a28d..4f630bd 100644 --- a/turtlebot4_setup/menu.py +++ b/turtlebot4_setup/menu.py @@ -219,12 +219,19 @@ def list_files(self): return files def highlight_file(self, filepath): - with open(filepath, 'r') as f: - file_content = f.read() try: - lexer = lexers.get_lexer_for_filename(filepath, stripnl=False, stripall=False) - except ClassNotFound: - lexer = lexers.get_lexer_by_name('text', stripnl=False, stripall=False) - formatter = formatters.TerminalFormatter(bg='dark') # dark or light - highlighted_file_content = highlight(file_content, lexer, formatter) - return highlighted_file_content + with open(filepath, 'r') as f: + file_content = f.read() + try: + lexer = lexers.get_lexer_for_filename(filepath, stripnl=False, stripall=False) + except ClassNotFound: + lexer = lexers.get_lexer_by_name('text', stripnl=False, stripall=False) + formatter = formatters.TerminalFormatter(bg='dark') # dark or light + highlighted_file_content = highlight(file_content, lexer, formatter) + return highlighted_file_content + except PermissionError: + return 'Permission denied.\nPlease check file permissions' + except FileNotFoundError: + return f'{filepath} was deleted' + except Exception as err: + return f'Error reading {filepath}:\n{err}' From 0008c94c5e03ac41762a0a7b6b341bab57479090 Mon Sep 17 00:00:00 2001 From: Chris Iverach-Brereton Date: Wed, 11 Sep 2024 12:34:27 -0400 Subject: [PATCH 23/40] Apply formatting to exception text --- turtlebot4_setup/menu.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/turtlebot4_setup/menu.py b/turtlebot4_setup/menu.py index 4f630bd..6a8bd68 100644 --- a/turtlebot4_setup/menu.py +++ b/turtlebot4_setup/menu.py @@ -219,19 +219,21 @@ def list_files(self): return files def highlight_file(self, filepath): + try: + lexer = lexers.get_lexer_for_filename(filepath, stripnl=False, stripall=False) + except ClassNotFound: + lexer = lexers.get_lexer_by_name('text', stripnl=False, stripall=False) + formatter = formatters.TerminalFormatter(bg='dark') # dark or light + try: with open(filepath, 'r') as f: file_content = f.read() - try: - lexer = lexers.get_lexer_for_filename(filepath, stripnl=False, stripall=False) - except ClassNotFound: - lexer = lexers.get_lexer_by_name('text', stripnl=False, stripall=False) - formatter = formatters.TerminalFormatter(bg='dark') # dark or light - highlighted_file_content = highlight(file_content, lexer, formatter) - return highlighted_file_content except PermissionError: - return 'Permission denied.\nPlease check file permissions' + file_content = 'Permission denied.\nPlease check file permissions' except FileNotFoundError: - return f'{filepath} was deleted' + file_content = f'{filepath} was deleted' except Exception as err: - return f'Error reading {filepath}:\n{err}' + file_content = f'Error reading {filepath}:\n{err}' + + highlighted_file_content = highlight(file_content, lexer, formatter) + return highlighted_file_content From af7bc7a1798db26d3e573300eb0daef410f09b8b Mon Sep 17 00:00:00 2001 From: Chris Iverach-Brereton Date: Wed, 11 Sep 2024 12:37:35 -0400 Subject: [PATCH 24/40] Fix accidental over-escaping of \ characters --- turtlebot4_setup/ros_setup.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/turtlebot4_setup/ros_setup.py b/turtlebot4_setup/ros_setup.py index 714b3a9..48a3916 100644 --- a/turtlebot4_setup/ros_setup.py +++ b/turtlebot4_setup/ros_setup.py @@ -33,9 +33,9 @@ class RosSetup(): # ROS Setup -- https://patorjk.com/software/taag/#p=display&v=0&f=Small title = """ ___ ___ ___ ___ _ - | _ \\\\/ _ \\\\/ __| / __| ___| |_ _ _ _ __ - | / (_) \\\\__ \\\\ \\\\__ \\\\/ -_) _| || | '_ \\\\ - |_|_\\\\\\\\___/|___/ |___/\\\\___|\\\\__|\\\\_,_| .__/ + | _ \\/ _ \\/ __| / __| ___| |_ _ _ _ __ + | / (_) \\__ \\ \\__ \\/ -_) _| || | '_ \\ + |_|_\\\\___/|___/ |___/\\___|\\__|\\_,_| .__/ |_| """ From 095d09b403cbdfe8395a3eb4109954b710bdbdc5 Mon Sep 17 00:00:00 2001 From: Chris Iverach-Brereton Date: Wed, 11 Sep 2024 12:55:48 -0400 Subject: [PATCH 25/40] Fix copyright date in comment blocks --- turtlebot4_setup/conf.py | 2 +- turtlebot4_setup/menu.py | 2 +- turtlebot4_setup/ros_setup.py | 2 +- turtlebot4_setup/turtlebot4_setup | 2 +- turtlebot4_setup/wifi.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/turtlebot4_setup/conf.py b/turtlebot4_setup/conf.py index 43696f3..d3e5dbc 100644 --- a/turtlebot4_setup/conf.py +++ b/turtlebot4_setup/conf.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -# Copyright 2024 Clearpath Robotics +# Copyright 2023 Clearpath Robotics # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/turtlebot4_setup/menu.py b/turtlebot4_setup/menu.py index 6a8bd68..a33e4c8 100644 --- a/turtlebot4_setup/menu.py +++ b/turtlebot4_setup/menu.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -# Copyright 2024 Clearpath Robotics +# Copyright 2023 Clearpath Robotics # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/turtlebot4_setup/ros_setup.py b/turtlebot4_setup/ros_setup.py index 48a3916..03c6c9c 100644 --- a/turtlebot4_setup/ros_setup.py +++ b/turtlebot4_setup/ros_setup.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -# Copyright 2024 Clearpath Robotics +# Copyright 2023 Clearpath Robotics # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/turtlebot4_setup/turtlebot4_setup b/turtlebot4_setup/turtlebot4_setup index 5afc354..707eac7 100755 --- a/turtlebot4_setup/turtlebot4_setup +++ b/turtlebot4_setup/turtlebot4_setup @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -# Copyright 2024 Clearpath Robotics +# Copyright 2023 Clearpath Robotics # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/turtlebot4_setup/wifi.py b/turtlebot4_setup/wifi.py index 8d68071..af14a07 100644 --- a/turtlebot4_setup/wifi.py +++ b/turtlebot4_setup/wifi.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -# Copyright 2024 Clearpath Robotics +# Copyright 2023 Clearpath Robotics # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. From 52d65e2c1f3d480f0996f027d29a17997e262cef Mon Sep 17 00:00:00 2001 From: Chris Iverach-Brereton Date: Wed, 11 Sep 2024 15:11:36 -0400 Subject: [PATCH 26/40] Add an option to force the Create3 settings to be reapplied, even if we haven't changed anything else. Always apply the _do_not_use namespace, as we're universally using the republisher now --- turtlebot4_setup/turtlebot4_setup | 140 +++++++++++++++++++----------- 1 file changed, 87 insertions(+), 53 deletions(-) diff --git a/turtlebot4_setup/turtlebot4_setup b/turtlebot4_setup/turtlebot4_setup index 707eac7..a06825d 100755 --- a/turtlebot4_setup/turtlebot4_setup +++ b/turtlebot4_setup/turtlebot4_setup @@ -52,6 +52,7 @@ class Turtlebot4Setup(): MenuEntry('', None), MenuEntry(entry='View Settings', function=self.view_settings), MenuEntry(entry='Apply Settings', function=self.apply_settings), + MenuEntry(entry='Reset Create3', function=self.apply_create3), MenuEntry('', None), MenuEntry(entry='About', function=self.about), MenuEntry(entry='Help', function=self.help), @@ -172,75 +173,108 @@ class Turtlebot4Setup(): reinstall_job = True if update_create3: - ros_domain_id = 'ros_domain_id=' + os.environ[BashOptions.DOMAIN_ID] - ros_namespace = '&ros_namespace=' + os.environ[BashOptions.NAMESPACE] - if self.conf.get(DiscoveryOptions.ENABLED): - # TODO(hilary-luo): Should be moved out of the if statement when the republisher is used for simple discovery - ros_namespace += '/_do_not_use' - rmw_implementation = '&rmw_implementation=' + os.environ[BashOptions.RMW] + (error, result) = self.update_create3() + if error: + return (error, result) + + if reinstall_job: + self.ros.robot_upstart_menu.install() + self.ros.robot_upstart_menu.start() + + return (0, "Success") + + def apply_wifi_settings(self): + # Run netplan apply if WiFi options have changed + if len(self.get_settings_diff(WifiOptions)) > 0: + subprocess.run(shlex.split('sudo netplan apply')) + os.system('sudo reboot') + + def create3_diff(self): + # Reset Create3 -- https://patorjk.com/software/taag/#p=display&v=0&f=Small + text = """ + ___ _ ___ _ ____ + | _ \\___ ___ ___| |_ / __|_ _ ___ __ _| |_ ___|__ / + | / -_|_- 0: - subprocess.run(shlex.split('sudo netplan apply')) - os.system('sudo reboot') + # If the curl command fails then return and indicate the error. + if (result.returncode != 0): + return (result.returncode, "Error requesting Create3 to reboot\n\n" + result.stderr.decode("utf-8")) + + return (0, 'Success') def run(self): self.menu.show() From 7f3a23fdca171687271d539a9a8f51b73ef0fae6 Mon Sep 17 00:00:00 2001 From: Chris Iverach-Brereton Date: Wed, 11 Sep 2024 15:13:28 -0400 Subject: [PATCH 27/40] Remove superfluous concatenation --- turtlebot4_setup/turtlebot4_setup | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/turtlebot4_setup/turtlebot4_setup b/turtlebot4_setup/turtlebot4_setup index a06825d..73785b5 100755 --- a/turtlebot4_setup/turtlebot4_setup +++ b/turtlebot4_setup/turtlebot4_setup @@ -196,7 +196,7 @@ class Turtlebot4Setup(): | _ \\___ ___ ___| |_ / __|_ _ ___ __ _| |_ ___|__ / | / -_|_- Date: Wed, 11 Sep 2024 15:14:41 -0400 Subject: [PATCH 28/40] Fix trailing newlines --- turtlebot4_setup/turtlebot4_setup | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/turtlebot4_setup/turtlebot4_setup b/turtlebot4_setup/turtlebot4_setup index 73785b5..238182a 100755 --- a/turtlebot4_setup/turtlebot4_setup +++ b/turtlebot4_setup/turtlebot4_setup @@ -195,8 +195,7 @@ class Turtlebot4Setup(): ___ _ ___ _ ____ | _ \\___ ___ ___| |_ / __|_ _ ___ __ _| |_ ___|__ / | / -_|_- Date: Wed, 11 Sep 2024 16:35:37 -0400 Subject: [PATCH 29/40] Enable testing packages for CI --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 996fee0..cdb950c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,6 +11,7 @@ jobs: - uses: ros-tooling/setup-ros@v0.7 with: required-ros-distributions: jazzy + use-ros2-testing: true - uses: ros-tooling/action-ros-ci@v0.3 id: action_ros_ci_step with: From 11451e4c8da03c721d11bef0a833cd5779ab1ec5 Mon Sep 17 00:00:00 2001 From: Chris Iverach-Brereton Date: Thu, 12 Sep 2024 12:48:20 -0400 Subject: [PATCH 30/40] Write the value of the enum to the bash file, use a regex to match existing items in the file --- turtlebot4_setup/conf.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/turtlebot4_setup/conf.py b/turtlebot4_setup/conf.py index d3e5dbc..1e5b57d 100644 --- a/turtlebot4_setup/conf.py +++ b/turtlebot4_setup/conf.py @@ -17,6 +17,7 @@ import copy from enum import Enum import os +import re import shlex import subprocess import sys @@ -333,25 +334,26 @@ def write_bash(self): if v is None: v = '' for i, line in enumerate(bash): - if f'export {k}' in line: + export_re = re.compile(rf'^\s*export\s+{k.value}=.*') + if export_re.match(line): if (k == BashOptions.SUPER_CLIENT and str(v) == 'True'): # Ensure super client is only applied on user terminals - bash[i] = f'[ -t 0 ] && export {k}={v} || export {k}=False\n' + bash[i] = f'[ -t 0 ] && export {k.value}={v} || export {k.value}=False\n' else: # Quotations required around v to handle multiple servers # in discovery server - bash[i] = f'export {k}=\"{v}\"\n' + bash[i] = f'export {k.value}=\"{v}\"\n' found = True # If the setting is missing from the setup.bash, add it to the beginning if not found: if (k == BashOptions.SUPER_CLIENT and str(v) == 'True'): # Ensure super client is only applied on user terminals - bash.insert(0, f'[ -t 0 ] && export {k}={v} || export {k}=False\n') + bash.insert(0, f'[ -t 0 ] && export {k.value}={v} || export {k.value}=False\n') else: # Quotations required around v to handle multiple servers # in discovery server - bash.insert(0, f'export {k}=\"{v}\"\n') + bash.insert(0, f'export {k.value}=\"{v}\"\n') with open('/tmp' + self.setup_bash_file, 'w') as f: f.writelines(bash) From 44f3642741471aec20c00dd27fa3ea04320fd91e Mon Sep 17 00:00:00 2001 From: Chris Iverach-Brereton Date: Thu, 12 Sep 2024 12:53:42 -0400 Subject: [PATCH 31/40] Disable checks on two lines with long format strings --- turtlebot4_setup/conf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/turtlebot4_setup/conf.py b/turtlebot4_setup/conf.py index 1e5b57d..a0ac129 100644 --- a/turtlebot4_setup/conf.py +++ b/turtlebot4_setup/conf.py @@ -338,7 +338,7 @@ def write_bash(self): if export_re.match(line): if (k == BashOptions.SUPER_CLIENT and str(v) == 'True'): # Ensure super client is only applied on user terminals - bash[i] = f'[ -t 0 ] && export {k.value}={v} || export {k.value}=False\n' + bash[i] = f'[ -t 0 ] && export {k.value}={v} || export {k.value}=False\n' # noqa: 501 else: # Quotations required around v to handle multiple servers # in discovery server @@ -349,7 +349,7 @@ def write_bash(self): if not found: if (k == BashOptions.SUPER_CLIENT and str(v) == 'True'): # Ensure super client is only applied on user terminals - bash.insert(0, f'[ -t 0 ] && export {k.value}={v} || export {k.value}=False\n') + bash.insert(0, f'[ -t 0 ] && export {k.value}={v} || export {k.value}=False\n') # noqa: 501 else: # Quotations required around v to handle multiple servers # in discovery server From ee12e6f9a6b2181ec5b0bb6821330206533366e6 Mon Sep 17 00:00:00 2001 From: Chris Iverach-Brereton Date: Thu, 12 Sep 2024 14:30:44 -0400 Subject: [PATCH 32/40] Add exception handling for install & uninstall --- turtlebot4_setup/ros_setup.py | 83 +++++++++++++++++++---------------- 1 file changed, 45 insertions(+), 38 deletions(-) diff --git a/turtlebot4_setup/ros_setup.py b/turtlebot4_setup/ros_setup.py index 03c6c9c..1e95132 100644 --- a/turtlebot4_setup/ros_setup.py +++ b/turtlebot4_setup/ros_setup.py @@ -341,51 +341,58 @@ def daemon_reload(self): subprocess.run(shlex.split('sudo systemctl daemon-reload')) def install(self): - self.uninstall() + try: + self.uninstall() - rmw = os.environ['RMW_IMPLEMENTATION'] - if rmw == 'rmw_fastrtps_cpp': - rmw_config = os.environ['FASTRTPS_DEFAULT_PROFILES_FILE'] - else: - rmw_config = os.environ['CYCLONEDDS_URI'] + rmw = os.environ['RMW_IMPLEMENTATION'] + if rmw == 'rmw_fastrtps_cpp': + rmw_config = os.environ['FASTRTPS_DEFAULT_PROFILES_FILE'] + else: + rmw_config = os.environ['CYCLONEDDS_URI'] - turtlebot4_job = robot_upstart.Job( - name='turtlebot4', - workspace_setup=os.environ['ROBOT_SETUP'], - rmw=rmw, - rmw_config=rmw_config, - systemd_after='network-online.target') + turtlebot4_job = robot_upstart.Job( + name='turtlebot4', + workspace_setup=os.environ['ROBOT_SETUP'], + rmw=rmw, + rmw_config=rmw_config, + systemd_after='network-online.target') - turtlebot4_job.symlink = True - turtlebot4_job.add(package='turtlebot4_bringup', - filename='launch/{0}.launch.py'.format( - self.conf.get(SystemOptions.MODEL))) - turtlebot4_job.install() + turtlebot4_job.symlink = True + turtlebot4_job.add(package='turtlebot4_bringup', + filename='launch/{0}.launch.py'.format( + self.conf.get(SystemOptions.MODEL))) + turtlebot4_job.install() - if self.conf.get(DiscoveryOptions.ENABLED): - discovery_job = robot_upstart.Job(workspace_setup=os.environ['ROBOT_SETUP']) - discovery_job.install(Provider=TurtleBot4Extras) - subprocess.run(shlex.split('sudo systemctl restart discovery.service')) + if self.conf.get(DiscoveryOptions.ENABLED): + discovery_job = robot_upstart.Job(workspace_setup=os.environ['ROBOT_SETUP']) + discovery_job.install(Provider=TurtleBot4Extras) + subprocess.run(shlex.split('sudo systemctl restart discovery.service')) - self.daemon_reload() + self.daemon_reload() + + except Exception as err: + print(f'Failed to install systemd job: {err}') def uninstall(self): - self.stop() - - # Uninstall Turtlebot4 Service - turtlebot4_job = robot_upstart.Job( - name='turtlebot4', - workspace_setup=os.environ['ROBOT_SETUP']) - turtlebot4_job.uninstall() - - # Uninstall Discovery Server Service - if os.path.exists('/lib/systemd/system/discovery.service'): - subprocess.run(shlex.split( - 'sudo systemctl stop discovery.service'), capture_output=True) - discovery_job = robot_upstart.Job(workspace_setup=os.environ['ROBOT_SETUP']) - discovery_job.uninstall(Provider=TurtleBot4Extras) - - self.daemon_reload() + try: + self.stop() + + # Uninstall Turtlebot4 Service + turtlebot4_job = robot_upstart.Job( + name='turtlebot4', + workspace_setup=os.environ['ROBOT_SETUP']) + turtlebot4_job.uninstall() + + # Uninstall Discovery Server Service + if os.path.exists('/lib/systemd/system/discovery.service'): + subprocess.run(shlex.split( + 'sudo systemctl stop discovery.service'), capture_output=True) + discovery_job = robot_upstart.Job(workspace_setup=os.environ['ROBOT_SETUP']) + discovery_job.uninstall(Provider=TurtleBot4Extras) + + self.daemon_reload() + except Exception as err: + print(f'Failed to uninstall existing systemd job: {err}') class TurtleBot4Extras(robot_upstart.providers.Generic): From 72f04a3722715face4e29d305c9981971fcaaeb3 Mon Sep 17 00:00:00 2001 From: Chris Iverach-Brereton Date: Thu, 12 Sep 2024 14:37:49 -0400 Subject: [PATCH 33/40] Add an error prompt to show errors during installation --- turtlebot4_setup/menu.py | 17 +++++++++++++++++ turtlebot4_setup/ros_setup.py | 6 +++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/turtlebot4_setup/menu.py b/turtlebot4_setup/menu.py index a33e4c8..7321fac 100644 --- a/turtlebot4_setup/menu.py +++ b/turtlebot4_setup/menu.py @@ -237,3 +237,20 @@ def highlight_file(self, filepath): highlighted_file_content = highlight(file_content, lexer, formatter) return highlighted_file_content + + +class ErrorPrompt(Menu): + # Error -- https://patorjk.com/software/taag/#p=display&v=0&f=Small + title = """ + ___ + | __|_ _ _ _ ___ _ _ + | _|| '_| '_/ _ \\ '_| + |___|_| |_| \\___/_| + +""" + + def __init__(self, text: str, display_help_title=True) -> None: + if display_help_title: + super().__init__(self.title + text, []) + else: + super().__init__(text, []) \ No newline at end of file diff --git a/turtlebot4_setup/ros_setup.py b/turtlebot4_setup/ros_setup.py index 1e95132..cb70d4c 100644 --- a/turtlebot4_setup/ros_setup.py +++ b/turtlebot4_setup/ros_setup.py @@ -20,7 +20,7 @@ import robot_upstart from turtlebot4_setup.conf import BashOptions, Conf, DiscoveryOptions, SystemOptions -from turtlebot4_setup.menu import Menu, MenuEntry, OptionsMenu, Prompt +from turtlebot4_setup.menu import ErrorPrompt, Menu, MenuEntry, OptionsMenu, Prompt __author__ = 'Roni Kreinin' @@ -371,7 +371,7 @@ def install(self): self.daemon_reload() except Exception as err: - print(f'Failed to install systemd job: {err}') + ErrorPrompt(f'Failed to install systemd job:\n{err}').show() def uninstall(self): try: @@ -392,7 +392,7 @@ def uninstall(self): self.daemon_reload() except Exception as err: - print(f'Failed to uninstall existing systemd job: {err}') + ErrorPrompt(f'Failed to uninstall existing systemd job:\n{err}').show() class TurtleBot4Extras(robot_upstart.providers.Generic): From 6519d9b2029debf1dbea8e2148cf0ab69589aa91 Mon Sep 17 00:00:00 2001 From: Chris Iverach-Brereton Date: Thu, 12 Sep 2024 14:40:33 -0400 Subject: [PATCH 34/40] Handle KeyErrors separately --- turtlebot4_setup/ros_setup.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/turtlebot4_setup/ros_setup.py b/turtlebot4_setup/ros_setup.py index cb70d4c..6d4f688 100644 --- a/turtlebot4_setup/ros_setup.py +++ b/turtlebot4_setup/ros_setup.py @@ -370,6 +370,8 @@ def install(self): self.daemon_reload() + except KeyError as err: + ErrorPrompt(f'Failed to install systemd job:\n{err} is not defined').show() except Exception as err: ErrorPrompt(f'Failed to install systemd job:\n{err}').show() @@ -391,6 +393,8 @@ def uninstall(self): discovery_job.uninstall(Provider=TurtleBot4Extras) self.daemon_reload() + except KeyError as err: + ErrorPrompt(f'Failed to uninstall existing systemd job:\n{err} is not defined').show() except Exception as err: ErrorPrompt(f'Failed to uninstall existing systemd job:\n{err}').show() From 15d0ead0cfd5995e22f41f1d77eeae5bcf0d6726 Mon Sep 17 00:00:00 2001 From: Chris Iverach-Brereton Date: Thu, 12 Sep 2024 14:52:54 -0400 Subject: [PATCH 35/40] Add newline to end of file --- turtlebot4_setup/menu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/turtlebot4_setup/menu.py b/turtlebot4_setup/menu.py index 7321fac..25c6efb 100644 --- a/turtlebot4_setup/menu.py +++ b/turtlebot4_setup/menu.py @@ -253,4 +253,4 @@ def __init__(self, text: str, display_help_title=True) -> None: if display_help_title: super().__init__(self.title + text, []) else: - super().__init__(text, []) \ No newline at end of file + super().__init__(text, []) From 5eb194c1fdc8dfa91887db706cf74371333187f3 Mon Sep 17 00:00:00 2001 From: Chris Iverach-Brereton Date: Thu, 12 Sep 2024 15:11:24 -0400 Subject: [PATCH 36/40] Fix indentation --- turtlebot4_setup/ros_setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/turtlebot4_setup/ros_setup.py b/turtlebot4_setup/ros_setup.py index 6d4f688..4e4735c 100644 --- a/turtlebot4_setup/ros_setup.py +++ b/turtlebot4_setup/ros_setup.py @@ -359,8 +359,8 @@ def install(self): turtlebot4_job.symlink = True turtlebot4_job.add(package='turtlebot4_bringup', - filename='launch/{0}.launch.py'.format( - self.conf.get(SystemOptions.MODEL))) + filename='launch/{0}.launch.py'.format( + self.conf.get(SystemOptions.MODEL))) turtlebot4_job.install() if self.conf.get(DiscoveryOptions.ENABLED): From a32809771fc2243010c70992a498936a043bf60b Mon Sep 17 00:00:00 2001 From: Chris Iverach-Brereton Date: Thu, 12 Sep 2024 15:21:28 -0400 Subject: [PATCH 37/40] ''.format -> f'' --- turtlebot4_setup/ros_setup.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/turtlebot4_setup/ros_setup.py b/turtlebot4_setup/ros_setup.py index 4e4735c..8fc31e8 100644 --- a/turtlebot4_setup/ros_setup.py +++ b/turtlebot4_setup/ros_setup.py @@ -358,9 +358,10 @@ def install(self): systemd_after='network-online.target') turtlebot4_job.symlink = True - turtlebot4_job.add(package='turtlebot4_bringup', - filename='launch/{0}.launch.py'.format( - self.conf.get(SystemOptions.MODEL))) + turtlebot4_job.add( + package='turtlebot4_bringup', + filename=f'launch/{self.conf.get(SystemOptions.MODEL)}.launch.py' + ) turtlebot4_job.install() if self.conf.get(DiscoveryOptions.ENABLED): From 77e8c522d3a9cd8dd7c17615832ce2372df81e64 Mon Sep 17 00:00:00 2001 From: Chris Iverach-Brereton Date: Thu, 12 Sep 2024 15:35:14 -0400 Subject: [PATCH 38/40] Typo in Ubuntu --- .github/ISSUE_TEMPLATE/1-bug.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/1-bug.yml b/.github/ISSUE_TEMPLATE/1-bug.yml index 801c9b4..1fa4291 100644 --- a/.github/ISSUE_TEMPLATE/1-bug.yml +++ b/.github/ISSUE_TEMPLATE/1-bug.yml @@ -53,7 +53,7 @@ body: - Select One - Ubuntu 20.04 - Ubuntu 22.04 - - Ubuntyu 24.04 + - Ubuntu 24.04 - Other Linux - Windows / MAC validations: From 03ea9be1c6d7091e5e471f1708f642605a11139b Mon Sep 17 00:00:00 2001 From: Chris Iverach-Brereton Date: Mon, 23 Sep 2024 12:32:49 -0400 Subject: [PATCH 39/40] Update the default system file, print the keys instead of the enums --- etc/turtlebot4/system | 5 +++-- turtlebot4_setup/conf.py | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/etc/turtlebot4/system b/etc/turtlebot4/system index efc36e3..9d9ae4a 100644 --- a/etc/turtlebot4/system +++ b/etc/turtlebot4/system @@ -1,3 +1,4 @@ -MODEL:standard -VERSION:1.0.0 +MODEL:lite +VERSION:2.0.0 ROS:Jazzy +HOSTNAME:ubuntu \ No newline at end of file diff --git a/turtlebot4_setup/conf.py b/turtlebot4_setup/conf.py index a0ac129..db6762b 100644 --- a/turtlebot4_setup/conf.py +++ b/turtlebot4_setup/conf.py @@ -76,7 +76,7 @@ class Conf(): default_system_conf = { SystemOptions.MODEL: 'lite', - SystemOptions.VERSION: '1.0.0', + SystemOptions.VERSION: '2.0.0', SystemOptions.ROS: 'Jazzy', SystemOptions.HOSTNAME: 'ubuntu', } @@ -203,7 +203,7 @@ def write_system(self): is_conf = False for k in [SystemOptions.MODEL, SystemOptions.VERSION, SystemOptions.ROS]: if k in line: - system[i] = '{0}:{1}\n'.format(k, self.system_conf[k]) + system[i] = f'{k.value}:{self.system_conf[k]}\n' is_conf = True break From 2880c69d968fa634d7568cd19045bf418b43d75d Mon Sep 17 00:00:00 2001 From: Chris Iverach-Brereton Date: Mon, 23 Sep 2024 16:14:13 -0400 Subject: [PATCH 40/40] Remove uses of .value when printing enums, just add a __str__ function to the relevant classes --- turtlebot4_setup/conf.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/turtlebot4_setup/conf.py b/turtlebot4_setup/conf.py index db6762b..2e538af 100644 --- a/turtlebot4_setup/conf.py +++ b/turtlebot4_setup/conf.py @@ -38,6 +38,9 @@ class SystemOptions(str, Enum): HOSTNAME = 'HOSTNAME' IP = 'IP' + def __str__(self): + return f'{self.value}' + class WifiOptions(str, Enum): SSID = 'SSID' @@ -48,6 +51,9 @@ class WifiOptions(str, Enum): IP = 'IP' DHCP = 'DHCP' + def __str__(self): + return f'{self.value}' + class BashOptions(str, Enum): CYCLONEDDS_URI = 'CYCLONEDDS_URI' @@ -60,6 +66,9 @@ class BashOptions(str, Enum): WORKSPACE = 'WORKSPACE_SETUP' SUPER_CLIENT = 'ROS_SUPER_CLIENT' + def __str__(self): + return f'{self.value}' + class DiscoveryOptions(str, Enum): ENABLED = 'ENABLED' @@ -69,6 +78,9 @@ class DiscoveryOptions(str, Enum): OFFBOARD_PORT = 'OFFBOARD_PORT' OFFBOARD_ID = 'OFFBOARD_ID' + def __str__(self): + return f'{self.value}' + class Conf(): setup_dir = '/etc/turtlebot4/' @@ -203,7 +215,7 @@ def write_system(self): is_conf = False for k in [SystemOptions.MODEL, SystemOptions.VERSION, SystemOptions.ROS]: if k in line: - system[i] = f'{k.value}:{self.system_conf[k]}\n' + system[i] = f'{k}:{self.system_conf[k]}\n' is_conf = True break @@ -334,26 +346,26 @@ def write_bash(self): if v is None: v = '' for i, line in enumerate(bash): - export_re = re.compile(rf'^\s*export\s+{k.value}=.*') + export_re = re.compile(rf'^\s*export\s+{k}=.*') if export_re.match(line): if (k == BashOptions.SUPER_CLIENT and str(v) == 'True'): # Ensure super client is only applied on user terminals - bash[i] = f'[ -t 0 ] && export {k.value}={v} || export {k.value}=False\n' # noqa: 501 + bash[i] = f'[ -t 0 ] && export {k}={v} || export {k}=False\n' # noqa: 501 else: # Quotations required around v to handle multiple servers # in discovery server - bash[i] = f'export {k.value}=\"{v}\"\n' + bash[i] = f'export {k}=\"{v}\"\n' found = True # If the setting is missing from the setup.bash, add it to the beginning if not found: if (k == BashOptions.SUPER_CLIENT and str(v) == 'True'): # Ensure super client is only applied on user terminals - bash.insert(0, f'[ -t 0 ] && export {k.value}={v} || export {k.value}=False\n') # noqa: 501 + bash.insert(0, f'[ -t 0 ] && export {k}={v} || export {k}=False\n') # noqa: 501 else: # Quotations required around v to handle multiple servers # in discovery server - bash.insert(0, f'export {k.value}=\"{v}\"\n') + bash.insert(0, f'export {k}=\"{v}\"\n') with open('/tmp' + self.setup_bash_file, 'w') as f: f.writelines(bash)