From aa37c58392936a77bc38953c9823a3eb100d415d Mon Sep 17 00:00:00 2001 From: Chris Iverach-Brereton Date: Tue, 14 Jan 2025 11:50:37 -0500 Subject: [PATCH] Imported upstream version '1.0.5' of 'upstream' --- .github/ISSUE_TEMPLATE/1-bug.yml | 2 - .github/workflows/ci.yml | 19 +- CHANGELOG.rst | 50 +--- CMakeLists.txt | 7 - CONTRIBUTING.md | 15 -- README.md | 12 +- etc/motd | 4 - etc/netplan/40-ethernets.yaml | 3 +- etc/rc.local | 6 - etc/turtlebot4/cyclonedds_rpi.xml | 6 +- etc/turtlebot4/discovery.sh | 2 +- etc/turtlebot4/fastdds_rpi.xml | 4 +- etc/turtlebot4/firmware/cmdline.txt | 1 - etc/turtlebot4/firmware/config.txt | 46 ---- etc/turtlebot4/setup.bash | 5 +- etc/turtlebot4/system | 7 +- package.xml | 3 +- scripts/create_update.sh | 2 +- scripts/{jazzy.sh => humble.sh} | 8 +- scripts/sd_flash.sh | 13 - scripts/turtlebot4_setup.sh | 19 +- turtlebot4_discovery/configure_discovery.sh | 4 +- turtlebot4_setup/conf.py | 169 +++++-------- turtlebot4_setup/menu.py | 76 ++---- turtlebot4_setup/ros_setup.py | 253 ++++++++------------ turtlebot4_setup/turtlebot4_setup | 189 ++++++--------- turtlebot4_setup/wifi.py | 76 +++--- 27 files changed, 308 insertions(+), 693 deletions(-) delete mode 100644 CONTRIBUTING.md delete mode 100644 etc/motd delete mode 100755 etc/rc.local delete mode 100644 etc/turtlebot4/firmware/cmdline.txt delete mode 100644 etc/turtlebot4/firmware/config.txt rename scripts/{jazzy.sh => humble.sh} (89%) diff --git a/.github/ISSUE_TEMPLATE/1-bug.yml b/.github/ISSUE_TEMPLATE/1-bug.yml index 1fa4291..46fcde9 100644 --- a/.github/ISSUE_TEMPLATE/1-bug.yml +++ b/.github/ISSUE_TEMPLATE/1-bug.yml @@ -30,7 +30,6 @@ body: - Select One - Galactic - Humble - - Jazzy validations: required: true - type: dropdown @@ -53,7 +52,6 @@ body: - Select One - Ubuntu 20.04 - Ubuntu 22.04 - - Ubuntu 24.04 - Other Linux - Windows / MAC validations: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cdb950c..15f3120 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,20 +3,11 @@ name: turtlebot4_setup_ci on: [push, pull_request] jobs: - turtlebot4_jazzy_ci: - name: Jazzy - runs-on: ubuntu-24.04 + turtlebot4_humble_ci: + name: Humble + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v2.3.4 - - uses: ros-tooling/setup-ros@v0.7 + - uses: ros-tooling/setup-ros@v0.3 with: - required-ros-distributions: jazzy - use-ros2-testing: true - - 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 + required-ros-distributions: humble diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 6c752fc..ab77316 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,53 +2,11 @@ Changelog for package turtlebot4_setup ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -2.0.2 (2024-10-23) +1.0.5 (2025-01-14) ------------------ -* Add e2fsck to SD-flasher script -* Bump default version numbers -* Fix default hostname to match released SD card images -* Append `p2` for `mmcblk` devices, but just `2` for `sd*` devices when expanding the last partition -* Contributors: Chris Iverach-Brereton - -2.0.1 (2024-10-04) ------------------- -* Add a copy of the boot/firmware files to /etc/turtlebot for reference in case users modify these and want a clean, offline copy for reference -* Add ROBOT_SETUP to setup.bash -* Add growpart & resize2fs commands to the SD card-flashing script to expand the partition to use up the whole SD card -* Add socat as a package dependency instead of an ad-hoc post-install package -* Add MOTD file with the Turtlebot4 logotype -* Contributors: Chris Iverach-Brereton - -2.0.0 (2024-09-28) ------------------- -* Initial Jazzy implementation - * Add a note about firmware compatibility to the readme - * Add exception handling to the file i/o so the node doesn't just crash if we're missing a file - * Add improved exception handling to the wifi settings parser - * Update CI - * Properly escape all `\` characters in stylized titles, add translation & link to generator page in comments - * Add copyright & contribution notices, fix up code formatting, import ordering. Disable linting for some specific lines where appropriate - * Add XML namespaces & version to cyclone DDS config - * Omit XML linting (for now); it's consistently timing out and failing - * Code formatting fixes - * Add exception handling to the file preview - * 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 - * Remove superfluous concatenation - * Enable testing packages for CI - * Disable checks on two lines with long format strings - * Add exception handling for install & uninstall - * Add an error prompt to show errors during installation - * Handle KeyErrors separately - * Add newline to end of file - * Fix indentation - * `''.format` -> `f''` - * Update the default system file, print the keys instead of the enums - * Add a `__str_\_` function to the relevant classes -* Disable DHCP4 on the built-in ethernet interface, make it non-optional -* Change the post-install chrony file command from mv to cp -* Only copy if the file exists -* Initial Jazzy implementation (`#15 `_) -* Contributors: Chris Iverach-Brereton +* Remove dhcp true because it was causing eth0 to lose the static IP (`#17 `_) +* Change the post-install chrony file command from mv to cp. Only copy if the file actually exists. +* Contributors: Chris Iverach-Brereton, Hilary Luo 1.0.4 (2024-07-02) ------------------ diff --git a/CMakeLists.txt b/CMakeLists.txt index 35a3707..0b5239a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,13 +16,6 @@ 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() diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 35ba863..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,15 +0,0 @@ -# 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/README.md b/README.md index 2fd221c..00c2fb1 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,6 @@ 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. @@ -14,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 24.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 22.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 @@ -64,7 +62,7 @@ ssh ubuntu@xxx.xxx.xxx.xxx ## Download and run the setup script ``` -wget -qO - https://raw.githubusercontent.com/turtlebot/turtlebot4_setup/jazzy/scripts/turtlebot4_setup.sh | bash +wget -qO - https://raw.githubusercontent.com/turtlebot/turtlebot4_setup/humble/scripts/turtlebot4_setup.sh | bash ``` -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. +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. diff --git a/etc/motd b/etc/motd deleted file mode 100644 index ed1fa7d..0000000 --- a/etc/motd +++ /dev/null @@ -1,4 +0,0 @@ - _____ _ _ _ _ _ _ - |_ _| _ _ _| |_| |___| |__ ___| |_| | | - | || || | '_| _| / -_) '_ \/ _ \ _|_ _| - |_| \_,_|_| \__|_\___|_.__/\___/\__| |_| diff --git a/etc/netplan/40-ethernets.yaml b/etc/netplan/40-ethernets.yaml index 913d254..689b64a 100644 --- a/etc/netplan/40-ethernets.yaml +++ b/etc/netplan/40-ethernets.yaml @@ -5,8 +5,7 @@ network: eth0: addresses: - 192.168.185.3/24 - dhcp4: false - optional: false + optional: true usb0: addresses: - 192.168.186.3/24 diff --git a/etc/rc.local b/etc/rc.local deleted file mode 100755 index 1634c50..0000000 --- a/etc/rc.local +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash - -# Give ourselves some swap to deal with RAM issues -if [ -f /swapfile ]; then - swapon /swapfile -fi diff --git a/etc/turtlebot4/cyclonedds_rpi.xml b/etc/turtlebot4/cyclonedds_rpi.xml index 7f0e73d..da8d8c0 100644 --- a/etc/turtlebot4/cyclonedds_rpi.xml +++ b/etc/turtlebot4/cyclonedds_rpi.xml @@ -1,8 +1,4 @@ - - + diff --git a/etc/turtlebot4/discovery.sh b/etc/turtlebot4/discovery.sh index 54ab52e..2fdf915 100755 --- a/etc/turtlebot4/discovery.sh +++ b/etc/turtlebot4/discovery.sh @@ -1,3 +1,3 @@ #!/bin/bash -source /opt/ros/jazzy/setup.bash +source /opt/ros/humble/setup.bash fastdds discovery -i 0 -p 11811 diff --git a/etc/turtlebot4/fastdds_rpi.xml b/etc/turtlebot4/fastdds_rpi.xml index 7df14f3..f5ffbac 100644 --- a/etc/turtlebot4/fastdds_rpi.xml +++ b/etc/turtlebot4/fastdds_rpi.xml @@ -1,5 +1,5 @@ - - + + diff --git a/etc/turtlebot4/firmware/cmdline.txt b/etc/turtlebot4/firmware/cmdline.txt deleted file mode 100644 index 5a6b0ff..0000000 --- a/etc/turtlebot4/firmware/cmdline.txt +++ /dev/null @@ -1 +0,0 @@ -console=serial0,115200 dwc_otg.lpm_enable=0 console=tty1 root=LABEL=writable rootfstype=ext4 rootwait fixrtc quiet splash modules-load=dwc2,g_ether diff --git a/etc/turtlebot4/firmware/config.txt b/etc/turtlebot4/firmware/config.txt deleted file mode 100644 index 91a4d4d..0000000 --- a/etc/turtlebot4/firmware/config.txt +++ /dev/null @@ -1,46 +0,0 @@ -[all] -kernel=vmlinuz -cmdline=cmdline.txt -initramfs initrd.img followkernel - -[pi4] -max_framebuffers=2 -arm_boost=1 - -[all] -# Enable the audio output, I2C and SPI interfaces on the GPIO header. As these -# parameters related to the base device-tree they must appear *before* any -# other dtoverlay= specification -dtparam=audio=on -dtparam=i2c_arm=on -dtparam=spi=on - -# Comment out the following line if the edges of the desktop appear outside -# the edges of your display -disable_overscan=1 - -# If you have issues with audio, you may try uncommenting the following line -# which forces the HDMI output into HDMI mode instead of DVI (which doesn't -# support audio output) -#hdmi_drive=2 - -# Enable the serial pins -enable_uart=1 - -# Autoload overlays for any recognized cameras or displays that are attached -# to the CSI/DSI ports. Please note this is for libcamera support, *not* for -# the legacy camera stack -camera_auto_detect=1 -display_auto_detect=1 - -# Config settings specific to arm64 -arm_64bit=1 -dtoverlay=dwc2,dr_mode=peripheral -dtoverlay=i2c-gpio,bus=3,i2c_gpio_delay_us=1,i2c_gpio_sda=4,i2c_gpio_scl=5 - -[cm4] -# Enable the USB2 outputs on the IO board (assuming your CM4 is plugged into -# such a board) -dtoverlay=dwc2,dr_mode=host - -[all] \ No newline at end of file diff --git a/etc/turtlebot4/setup.bash b/etc/turtlebot4/setup.bash index 0e66ccf..1098367 100644 --- a/etc/turtlebot4/setup.bash +++ b/etc/turtlebot4/setup.bash @@ -5,8 +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/jazzy/setup.bash +export WORKSPACE_SETUP=/opt/ros/humble/setup.bash export ROS_SUPER_CLIENT=False -export ROBOT_SETUP=/etc/turtlebot4/setup.bash -source $WORKSPACE_SETUP +source $WORKSPACE_SETUP \ No newline at end of file diff --git a/etc/turtlebot4/system b/etc/turtlebot4/system index e7c3d2a..0d27143 100644 --- a/etc/turtlebot4/system +++ b/etc/turtlebot4/system @@ -1,4 +1,3 @@ -MODEL:lite -VERSION:2.0.2 -ROS:Jazzy -HOSTNAME:turtlebot4 \ No newline at end of file +MODEL:standard +VERSION:1.0.0 +ROS:Humble \ No newline at end of file diff --git a/package.xml b/package.xml index 1784330..f6e6488 100644 --- a/package.xml +++ b/package.xml @@ -2,7 +2,7 @@ turtlebot4_setup - 2.0.2 + 1.0.5 Turtlebot4 setup scripts rkreinin Apache 2.0 @@ -16,7 +16,6 @@ rmw_fastrtps_cpp robot_upstart simple_term_menu_vendor - socat ament_lint_auto ament_lint_common diff --git a/scripts/create_update.sh b/scripts/create_update.sh index 86f30c1..5847051 100755 --- a/scripts/create_update.sh +++ b/scripts/create_update.sh @@ -5,7 +5,7 @@ Help() { - echo "Create 3 update script for robots running I.*.*" + echo "Create 3 update script for robots running H.1.0 or higher" echo echo "usage: bash create_update.sh /path/to/image.swu [-h]" echo "options:" diff --git a/scripts/jazzy.sh b/scripts/humble.sh similarity index 89% rename from scripts/jazzy.sh rename to scripts/humble.sh index 86cdeba..cd1003a 100755 --- a/scripts/jazzy.sh +++ b/scripts/humble.sh @@ -1,14 +1,10 @@ #!/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 - -# Install the packages sudo apt update sudo apt install -y \ -ros-jazzy-ros-base \ +ros-humble-ros-base \ build-essential \ cmake \ git \ @@ -16,4 +12,4 @@ wget \ ros-dev-tools \ socat \ network-manager \ -chrony +chrony \ No newline at end of file diff --git a/scripts/sd_flash.sh b/scripts/sd_flash.sh index 05046da..2bc81cd 100755 --- a/scripts/sd_flash.sh +++ b/scripts/sd_flash.sh @@ -38,16 +38,3 @@ do done sudo dcfldd if=$1 sizeprobe=if bs=1M$of - -for device in $device_names -do - if [[ $device == mmcblk* ]]; then - last_partition="${device}p2" - else - last_partition="${device}2" - fi - - sudo e2fsck -f /dev/${last_partition} - sudo growpart /dev/$device 2 - sudo resize2fs /dev/${last_partition} -done \ No newline at end of file diff --git a/scripts/turtlebot4_setup.sh b/scripts/turtlebot4_setup.sh index 0039f85..c70787e 100755 --- a/scripts/turtlebot4_setup.sh +++ b/scripts/turtlebot4_setup.sh @@ -31,24 +31,23 @@ echo "Setting up Turtlebot4"; sudo apt update && sudo apt upgrade -wget -qO - https://raw.githubusercontent.com/turtlebot/turtlebot4_setup/jazzy/scripts/jazzy.sh | bash +wget -qO - https://raw.githubusercontent.com/turtlebot/turtlebot4_setup/humble/scripts/humble.sh | bash sudo apt update && sudo apt upgrade -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 \ +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 \ ros-dev-tools \ +socat \ network-manager \ chrony -if [ -f /etc/netplan/50-cloud-init.yaml ]; then - sudo rm /etc/netplan/50-cloud-init.yaml -fi +sudo rm /etc/netplan/50-cloud-init.yaml -git clone https://github.com/turtlebot/turtlebot4_setup.git -b jazzy && \ +git clone https://github.com/turtlebot/turtlebot4_setup.git -b humble && \ 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 2e6c270..13f8023 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/jazzy/setup.bash" > $setup_file_temp +echo "source /opt/ros/humble/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 509beb2..b45ad35 100644 --- a/turtlebot4_setup/conf.py +++ b/turtlebot4_setup/conf.py @@ -1,34 +1,10 @@ -#!/usr/bin/env python3 - -# 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. -# 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 re -import shlex -import subprocess -import sys - import yaml +import subprocess +import shlex - -__author__ = 'Roni Kreinin' -__email__ = 'rkreinin@clearpathrobotics.com' -__copyright__ = 'Copyright © 2023 Clearpath Robotics. All rights reserved.' -__license__ = 'Apache 2.0' +from enum import Enum class SystemOptions(str, Enum): @@ -38,9 +14,6 @@ class SystemOptions(str, Enum): HOSTNAME = 'HOSTNAME' IP = 'IP' - def __str__(self): - return f'{self.value}' - class WifiOptions(str, Enum): SSID = 'SSID' @@ -51,9 +24,6 @@ class WifiOptions(str, Enum): IP = 'IP' DHCP = 'DHCP' - def __str__(self): - return f'{self.value}' - class BashOptions(str, Enum): CYCLONEDDS_URI = 'CYCLONEDDS_URI' @@ -66,9 +36,6 @@ 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' @@ -78,9 +45,6 @@ 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/' @@ -88,9 +52,9 @@ class Conf(): default_system_conf = { SystemOptions.MODEL: 'lite', - SystemOptions.VERSION: '2.0.2', - SystemOptions.ROS: 'Jazzy', - SystemOptions.HOSTNAME: 'turtlebot4', + SystemOptions.VERSION: '1.0.0', + SystemOptions.ROS: 'Humble', + SystemOptions.HOSTNAME: 'ubuntu', } default_wifi_conf = { @@ -111,7 +75,7 @@ class Conf(): BashOptions.DISCOVERY_SERVER: None, BashOptions.RMW: 'rmw_fastrtps_cpp', BashOptions.DIAGNOSTICS: '1', - BashOptions.WORKSPACE: '/opt/ros/jazzy/setup.bash', + BashOptions.WORKSPACE: '/opt/ros/humble/setup.bash', BashOptions.SUPER_CLIENT: False } @@ -152,7 +116,7 @@ def get(self, conf): return self.discovery_conf.get(conf) return None - def set(self, conf, value): # noqa: A003 + def set(self, conf, value): if isinstance(conf, SystemOptions): self.system_conf[conf] = value elif isinstance(conf, WifiOptions): @@ -173,25 +137,16 @@ def apply_default(self, conf): self.discovery_conf = copy.deepcopy(self.default_discovery_conf) def read(self): - try: - self.read_system() - self.read_wifi() - self.read_bash() - # 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) + 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 def write(self): - 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) + self.write_system() + self.write_wifi() + self.write_discovery() + self.write_bash() def read_system(self): with open(self.system_file, 'r') as f: @@ -215,7 +170,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}:{self.system_conf[k]}\n' + system[i] = '{0}:{1}\n'.format(k, self.system_conf[k]) is_conf = True break @@ -231,42 +186,36 @@ def write_system(self): subprocess.run(shlex.split('sudo mv /tmp' + self.hostname_file + ' ' + self.hostname_file)) def read_wifi(self): - 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) - - # wlan0 Config - wlan0 = netplan['network']['wifis']['wlan0'] + netplan = yaml.load(open(self.netplan_wifis_file, 'r'), yaml.SafeLoader) + # wlan0 Config + wlan0 = netplan['network']['wifis']['wlan0'] - # Get SSID - self.set(WifiOptions.SSID, list(wlan0['access-points'])[0]) - # SSID settings - ssid_settings = wlan0['access-points'][self.get(WifiOptions.SSID)] + # Get SSID + self.set(WifiOptions.SSID, list(wlan0['access-points'])[0]) + # SSID settings + ssid_settings = wlan0['access-points'][self.get(WifiOptions.SSID)] - self.set(WifiOptions.PASSWORD, ssid_settings.get('password')) + self.set(WifiOptions.PASSWORD, ssid_settings.get('password')) - if wlan0.get('addresses'): - self.set(WifiOptions.IP, wlan0['addresses'][0]) - else: - self.set(WifiOptions.IP, None) + if wlan0.get('addresses'): + self.set(WifiOptions.IP, wlan0['addresses'][0]) + else: + self.set(WifiOptions.IP, None) - if wlan0.get('dhcp4') is True: - self.set(WifiOptions.DHCP, True) - else: - self.set(WifiOptions.DHCP, False) + if wlan0.get('dhcp4') is True: + self.set(WifiOptions.DHCP, True) + else: + self.set(WifiOptions.DHCP, False) - if ssid_settings.get('mode') == 'ap': - self.set(WifiOptions.WIFI_MODE, 'Access Point') - else: - self.set(WifiOptions.WIFI_MODE, 'Client') + 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 Exception: - # If the wifi configuration doesn't have a wlan0 configuration, just skip this - pass + if ssid_settings.get('band'): + self.set(WifiOptions.BAND, ssid_settings.get('band')) + else: + self.set(WifiOptions.BAND, 'Any') def write_wifi(self): ssid = self.get(WifiOptions.SSID) @@ -306,7 +255,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') # noqa: E501 + f.write('# This file was automatically created by the turtlebot4-setup tool and should not be manually modified\n\n') yaml.dump(netplan, stream=open('/tmp' + self.netplan_wifis_file, 'a'), @@ -315,8 +264,7 @@ 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: @@ -346,14 +294,12 @@ def write_bash(self): if v is None: v = '' for i, line in enumerate(bash): - export_re = re.compile(rf'^\s*export\s+{k}=.*') - if export_re.match(line): + if f'export {k}' in 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' # noqa: 501 + 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 @@ -361,15 +307,14 @@ 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') # noqa: 501 + 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(f'sudo mv /tmp{self.setup_bash_file} {self.setup_bash_file}')) + subprocess.run(shlex.split('sudo mv /tmp' + self.setup_bash_file + ' ' + self.setup_bash_file)) for k, v in self.bash_conf.items(): if v is None: @@ -399,11 +344,10 @@ 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 Exception: + except: self.discovery_conf = self.default_discovery_conf def write_discovery(self): @@ -414,11 +358,10 @@ 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') # noqa: E501 + f.write('# This file was automatically created by the turtlebot4-setup tool and should not be manually modified\n\n') 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)}') # noqa: E501 - 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)}') + 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) @@ -450,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/menu.py b/turtlebot4_setup/menu.py index 25c6efb..5a1c945 100644 --- a/turtlebot4_setup/menu.py +++ b/turtlebot4_setup/menu.py @@ -1,33 +1,13 @@ -#!/usr/bin/env python3 - -# 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. -# 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 simple_term_menu_vendor.simple_term_menu import TerminalMenu -from typing import Callable, List, Union +from typing import List, Callable, Union from pygments import formatters, highlight, lexers from pygments.util import ClassNotFound -from simple_term_menu_vendor.simple_term_menu import TerminalMenu - +import os -__author__ = 'Roni Kreinin' -__email__ = 'rkreinin@clearpathrobotics.com' -__copyright__ = 'Copyright © 2023 Clearpath Robotics. All rights reserved.' -__license__ = 'Apache 2.0' +import readline class MenuEntry(): @@ -100,7 +80,7 @@ def reset_term_menu(self): self.menu = self.create_term_menu() self.menu_sel = 0 - def exit(self): # noqa: A003 + def exit(self): self.menu_exit = True def show(self, reset=True): @@ -118,7 +98,7 @@ def show(self, reset=True): class OptionsMenu(Menu): - def __init__(self, title: Union[str, Callable], menu_entries: List[str], default_option=None) -> None: # noqa: E501 + def __init__(self, title: Union[str, Callable], menu_entries: List[str], default_option=None) -> None: self.option = default_option self.menu_entries = [] @@ -143,13 +123,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: @@ -219,38 +199,12 @@ 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 - - try: - with open(filepath, 'r') as f: - file_content = f.read() - except PermissionError: - file_content = 'Permission denied.\nPlease check file permissions' - except FileNotFoundError: - file_content = f'{filepath} was deleted' - except Exception as err: - file_content = f'Error reading {filepath}:\n{err}' - + 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 - - -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, []) diff --git a/turtlebot4_setup/ros_setup.py b/turtlebot4_setup/ros_setup.py index 8fc31e8..93a3edb 100644 --- a/turtlebot4_setup/ros_setup.py +++ b/turtlebot4_setup/ros_setup.py @@ -1,42 +1,21 @@ -#!/usr/bin/env python3 - -# 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. -# 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.menu import Menu, OptionsMenu, MenuEntry, Prompt +from turtlebot4_setup.conf import Conf, SystemOptions, BashOptions, DiscoveryOptions import os -import shlex -import subprocess - -import robot_upstart -from turtlebot4_setup.conf import BashOptions, Conf, DiscoveryOptions, SystemOptions -from turtlebot4_setup.menu import ErrorPrompt, Menu, MenuEntry, OptionsMenu, Prompt +import subprocess, shlex -__author__ = 'Roni Kreinin' -__email__ = 'rkreinin@clearpathrobotics.com' -__copyright__ = 'Copyright © 2023 Clearpath Robotics. All rights reserved.' -__license__ = 'Apache 2.0' +import robot_upstart class RosSetup(): - # ROS Setup -- https://patorjk.com/software/taag/#p=display&v=0&f=Small + title = """ - ___ ___ ___ ___ _ - | _ \\/ _ \\/ __| / __| ___| |_ _ _ _ __ - | / (_) \\__ \\ \\__ \\/ -_) _| || | '_ \\ - |_|_\\\\___/|___/ |___/\\___|\\__|\\_,_| .__/ - |_| + ___ ___ ___ ___ _ + | _ \/ _ \/ __| / __| ___| |_ _ _ _ __ + | / (_) \__ \ \__ \/ -_) _| || | '_ \\ + |_|_\\\___/|___/ |___/\\___|\\__|\\_,_| .__/ + |_| """ setup_dir = '/etc/turtlebot4/' @@ -59,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: @@ -120,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 @@ -159,15 +138,14 @@ def set_robot_namespace(self): note='ROS2 namespace') # Add '/' if needed ns = p.show() - if ns is not None and ns[0] != '/': + if ns != 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') # noqa: E501 + options = OptionsMenu(title=BashOptions.DIAGNOSTICS, + menu_entries=['Enabled', 'Disabled'], + default_option='Enabled' if self.conf.get(BashOptions.DIAGNOSTICS) == '1' else 'Disabled') self.conf.set(BashOptions.DIAGNOSTICS, '1' if options.show() == 'Enabled' else '0') def save_settings(self): @@ -179,40 +157,32 @@ def apply_defaults(self): class DiscoveryServer(): - # Discovery Server -- https://patorjk.com/software/taag/#p=display&v=0&f=Small title = """ - ___ _ ___ - | \\(_)___ __ _____ _____ _ _ _ _ / __| ___ _ ___ _____ _ _ - | |) | (_- 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), # 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.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.menu = Menu(title=self.title, menu_entries=self.entries) @@ -247,7 +217,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)))): # noqa: 501 + if (self.conf.get(DiscoveryOptions.OFFBOARD_IP) and (server_id == int(self.conf.get(DiscoveryOptions.OFFBOARD_ID)))): return self.conf.set(DiscoveryOptions.SERVER_ID, server_id) @@ -275,7 +245,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') # noqa: 501 + note='Offboard Discovery Server ID (0-255) - Cannot be the same as the onboard server') server_id = p.show() server_id = max(0, min(int(server_id), 255)) if (server_id == int(self.conf.get(DiscoveryOptions.SERVER_ID))): @@ -291,13 +261,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: @@ -312,8 +282,7 @@ 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)] @@ -341,67 +310,53 @@ def daemon_reload(self): subprocess.run(shlex.split('sudo systemctl daemon-reload')) def install(self): - 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'] - - 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=f'launch/{self.conf.get(SystemOptions.MODEL)}.launch.py' - ) - 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')) - - 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() + 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'] + + 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() + + 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() def uninstall(self): - try: - self.stop() + self.stop() - # Uninstall Turtlebot4 Service - turtlebot4_job = robot_upstart.Job( - name='turtlebot4', - workspace_setup=os.environ['ROBOT_SETUP']) - turtlebot4_job.uninstall() + # 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) + # 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 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() + self.daemon_reload() class TurtleBot4Extras(robot_upstart.providers.Generic): - def post_install(self): pass @@ -411,26 +366,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 - }} + "/etc/systemd/system/multi-user.target.wants/discovery.service": { + "remove": True + }} \ No newline at end of file diff --git a/turtlebot4_setup/turtlebot4_setup b/turtlebot4_setup/turtlebot4_setup index 238182a..60beb39 100755 --- a/turtlebot4_setup/turtlebot4_setup +++ b/turtlebot4_setup/turtlebot4_setup @@ -1,43 +1,22 @@ #!/usr/bin/env python3 -# 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. -# 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.conf import Conf, SystemOptions, BashOptions, WifiOptions, DiscoveryOptions +from turtlebot4_setup.wifi import WifiSetup from turtlebot4_setup.menu import Menu, MenuEntry, OptionsMenu, Prompt, HelpMenu, PreviewMenu from turtlebot4_setup.ros_setup import RosSetup -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' +from turtlebot4_setup.conf import Conf, SystemOptions, BashOptions, WifiOptions, DiscoveryOptions class Turtlebot4Setup(): - # TurtleBot4 Setup -- https://patorjk.com/software/taag/#p=display&v=0&f=Small title = """ - _____ _ _ ___ _ _ _ ___ _ + _____ _ _ ___ _ _ _ ___ _ |_ _| _ _ _| |_| |___| _ ) ___| |_| | | / __| ___| |_ _ _ _ __ - | || || | '_| _| / -_) _ \\/ _ \\ _|_ _| \\__ \\/ -_) _| || | '_ \\ - |_| \\_,_|_| \\__|_\\___|___/\\___/\\__| |_| |___/\\___|\\__|\\_,_| .__/ + | || || | '_| _| / -_) _ \/ _ \ _|_ _| \__ \/ -_) _| || | '_ \\ + |_| \_,_|_| \__|_\___|___/\___/\__| |_| |___/\___|\__|\_,_| .__/ |_| """ @@ -52,7 +31,6 @@ 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), @@ -68,7 +46,7 @@ class Turtlebot4Setup(): if o.show() == 'Yes': subprocess.run(shlex.split('sudo apt update')) - subprocess.run(shlex.split('sudo apt install ros-jazzy-turtlebot4-setup')) + subprocess.run(shlex.split('sudo apt install ros-humble-turtlebot4-setup')) input() def view_settings(self): @@ -124,12 +102,11 @@ class Turtlebot4Setup(): if text == '': text = 'No changes made.\n' - # Apply Settings -- https://patorjk.com/software/taag/#p=display&v=0&f=Small text = """ - _ _ ___ _ _ _ - /_\\ _ __ _ __| |_ _ / __| ___| |_| |_(_)_ _ __ _ ___ - / _ \\| '_ \\ '_ \\ | || | \\__ \\/ -_) _| _| | ' \\/ _` (_-< - /_/ \\_\\ .__/ .__/_|\\_, | |___/\\___|\\__|\\__|_|_||_\\__, /__/ + _ _ ___ _ _ _ + /_\ _ __ _ __| |_ _ / __| ___| |_| |_(_)_ _ __ _ ___ + / _ \| '_ \ '_ \ | || | \__ \/ -_) _| _| | ' \/ _` (_-< + /_/ \_\ .__/ .__/_|\_, | |___/\___|\__|\__|_|_||_\__, /__/ |_| |_| |__/ |___/ \n\n""" + text text += '\nApply these settings?\n' @@ -173,9 +150,63 @@ class Turtlebot4Setup(): reinstall_job = True if update_create3: - (error, result) = self.update_create3() - if error: - return (error, result) + 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] + + discovery_server = f'&fast_discovery_server_value={self.conf.get_create3_server_str()}' + + create3_rmw_profile = 'config=' + if self.conf.get(DiscoveryOptions.ENABLED): + discovery_server_enabled = '&fast_discovery_server_enabled' + create3_rmw_profile_file = os.path.join(self.conf.setup_dir, 'fastdds_discovery_create3.xml') + with open(create3_rmw_profile_file) as f: + create3_rmw_profile += f.read() + else: + discovery_server_enabled = '' + + command = shlex.split( + 'curl -d "{0}{1}{2}{3}{4}"'.format(ros_domain_id, + ros_namespace, + rmw_implementation, + discovery_server, + discovery_server_enabled)) + \ + shlex.split('-X POST http://192.168.186.2/ros-config-save-main') + + result = subprocess.run(command, capture_output=True) + + # If the curl command fails then return and do not set any more settings. + if (result.returncode != 0): + return (result.returncode, "Error writing ROS settings to Create3\n\n" + result.stderr.decode("utf-8")) + + # 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. + if (result.returncode != 0): + return (result.returncode, "Error writing RMW XML Profile to Create3\n\n" + result.stderr.decode("utf-8")) + + # 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. + if (result.returncode != 0): + return (result.returncode, "Error writing NTP settings to Create3\n\n" + result.stderr.decode("utf-8")) + + # Reboot the Create3 + result = subprocess.run(shlex.split('curl -X POST http://192.168.186.2/api/reboot'), capture_output=True) + + # 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")) if reinstall_job: self.ros.robot_upstart_menu.install() @@ -189,92 +220,6 @@ class Turtlebot4Setup(): 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 = """ - ___ _ ___ _ ____ - | _ \\___ ___ ___| |_ / __|_ _ ___ __ _| |_ ___|__ / - | / -_|_- None: @@ -39,26 +19,24 @@ 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) @@ -88,7 +66,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)), # noqa: 501 + p = Prompt(prompt='Regulatory Domain ({0}): '.format(self.conf.get(WifiOptions.REG_DOMAIN)), default_response=self.conf.get(WifiOptions.REG_DOMAIN), note='Wireless regulatory domain. \n' + 'Common options:\n' +