diff --git a/.github/ISSUE_TEMPLATE/1-bug.yml b/.github/ISSUE_TEMPLATE/1-bug.yml index 46fcde9..1fa4291 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 + - Ubuntu 24.04 - Other Linux - Windows / MAC validations: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 15f3120..cdb950c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,11 +3,20 @@ 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 + - uses: ros-tooling/setup-ros@v0.7 with: - required-ros-distributions: humble + 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 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() 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/README.md b/README.md index 00c2fb1..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. @@ -12,14 +14,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 +64,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/rc.local b/etc/rc.local new file mode 100755 index 0000000..1634c50 --- /dev/null +++ b/etc/rc.local @@ -0,0 +1,6 @@ +#!/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 da8d8c0..7f0e73d 100644 --- a/etc/turtlebot4/cyclonedds_rpi.xml +++ b/etc/turtlebot4/cyclonedds_rpi.xml @@ -1,4 +1,8 @@ - + + 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/fastdds_rpi.xml b/etc/turtlebot4/fastdds_rpi.xml index f5ffbac..7df14f3 100644 --- a/etc/turtlebot4/fastdds_rpi.xml +++ b/etc/turtlebot4/fastdds_rpi.xml @@ -1,5 +1,5 @@ - - + + 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..9d9ae4a 100644 --- a/etc/turtlebot4/system +++ b/etc/turtlebot4/system @@ -1,3 +1,4 @@ -MODEL:standard -VERSION:1.0.0 -ROS:Humble \ No newline at end of file +MODEL:lite +VERSION:2.0.0 +ROS:Jazzy +HOSTNAME:ubuntu \ No newline at end of file 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/humble.sh b/scripts/jazzy.sh similarity index 89% rename from scripts/humble.sh rename to scripts/jazzy.sh index cd1003a..86cdeba 100755 --- a/scripts/humble.sh +++ b/scripts/jazzy.sh @@ -1,10 +1,14 @@ #!/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-humble-ros-base \ +ros-jazzy-ros-base \ build-essential \ cmake \ git \ @@ -12,4 +16,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..2e538af 100644 --- a/turtlebot4_setup/conf.py +++ b/turtlebot4_setup/conf.py @@ -1,10 +1,34 @@ +#!/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 yaml -import subprocess +import re 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): @@ -14,6 +38,9 @@ class SystemOptions(str, Enum): HOSTNAME = 'HOSTNAME' IP = 'IP' + def __str__(self): + return f'{self.value}' + class WifiOptions(str, Enum): SSID = 'SSID' @@ -24,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' @@ -36,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' @@ -45,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/' @@ -52,8 +88,8 @@ class Conf(): default_system_conf = { SystemOptions.MODEL: 'lite', - SystemOptions.VERSION: '1.0.0', - SystemOptions.ROS: 'Humble', + SystemOptions.VERSION: '2.0.0', + SystemOptions.ROS: 'Jazzy', SystemOptions.HOSTNAME: 'ubuntu', } @@ -75,7 +111,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 } @@ -116,7 +152,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): @@ -137,16 +173,25 @@ 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() + # 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) 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: @@ -170,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] = '{0}:{1}\n'.format(k, self.system_conf[k]) + system[i] = f'{k}:{self.system_conf[k]}\n' is_conf = True break @@ -186,36 +231,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 Exception: + # If the wifi configuration doesn't have a wlan0 configuration, just skip this + pass def write_wifi(self): ssid = self.get(WifiOptions.SSID) @@ -255,7 +306,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'), @@ -264,7 +315,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: @@ -294,12 +346,14 @@ 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}=.*') + 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}={v} || export {k}=False\n' # noqa: 501 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 @@ -307,14 +361,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') # noqa: 501 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: @@ -344,10 +399,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): @@ -358,10 +414,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) @@ -393,7 +450,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 5a1c945..25c6efb 100644 --- a/turtlebot4_setup/menu.py +++ b/turtlebot4_setup/menu.py @@ -1,13 +1,33 @@ -from simple_term_menu_vendor.simple_term_menu import TerminalMenu +#!/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 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 + -import readline +__author__ = 'Roni Kreinin' +__email__ = 'rkreinin@clearpathrobotics.com' +__copyright__ = 'Copyright © 2023 Clearpath Robotics. All rights reserved.' +__license__ = 'Apache 2.0' class MenuEntry(): @@ -80,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): @@ -98,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 = [] @@ -123,13 +143,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: @@ -199,12 +219,38 @@ 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 + 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}' + 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 93a3edb..8fc31e8 100644 --- a/turtlebot4_setup/ros_setup.py +++ b/turtlebot4_setup/ros_setup.py @@ -1,21 +1,42 @@ -from turtlebot4_setup.menu import Menu, OptionsMenu, MenuEntry, Prompt -from turtlebot4_setup.conf import Conf, SystemOptions, BashOptions, DiscoveryOptions +#!/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 - -import subprocess, shlex +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 -class RosSetup(): +__author__ = 'Roni Kreinin' +__email__ = 'rkreinin@clearpathrobotics.com' +__copyright__ = 'Copyright © 2023 Clearpath Robotics. All rights reserved.' +__license__ = 'Apache 2.0' + +class RosSetup(): + # ROS Setup -- https://patorjk.com/software/taag/#p=display&v=0&f=Small title = """ - ___ ___ ___ ___ _ - | _ \/ _ \/ __| / __| ___| |_ _ _ _ __ - | / (_) \__ \ \__ \/ -_) _| || | '_ \\ - |_|_\\\___/|___/ |___/\\___|\\__|\\_,_| .__/ - |_| + ___ ___ ___ ___ _ + | _ \\/ _ \\/ __| / __| ___| |_ _ _ _ __ + | / (_) \\__ \\ \\__ \\/ -_) _| || | '_ \\ + |_|_\\\\___/|___/ |___/\\___|\\__|\\_,_| .__/ + |_| """ setup_dir = '/etc/turtlebot4/' @@ -38,13 +59,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 +120,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 @@ -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): @@ -157,32 +179,40 @@ 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), - 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) @@ -217,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) @@ -245,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))): @@ -261,13 +291,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: @@ -282,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)] @@ -310,53 +341,67 @@ def daemon_reload(self): subprocess.run(shlex.split('sudo systemctl daemon-reload')) def install(self): - 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() + 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() def uninstall(self): - self.stop() + try: + 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() + 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() class TurtleBot4Extras(robot_upstart.providers.Generic): + def post_install(self): pass @@ -366,26 +411,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 60beb39..238182a 100755 --- a/turtlebot4_setup/turtlebot4_setup +++ b/turtlebot4_setup/turtlebot4_setup @@ -1,22 +1,43 @@ #!/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.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(): + # TurtleBot4 Setup -- https://patorjk.com/software/taag/#p=display&v=0&f=Small title = """ - _____ _ _ ___ _ _ _ ___ _ + _____ _ _ ___ _ _ _ ___ _ |_ _| _ _ _| |_| |___| _ ) ___| |_| | | / __| ___| |_ _ _ _ __ - | || || | '_| _| / -_) _ \/ _ \ _|_ _| \__ \/ -_) _| || | '_ \\ - |_| \_,_|_| \__|_\___|___/\___/\__| |_| |___/\___|\__|\_,_| .__/ + | || || | '_| _| / -_) _ \\/ _ \\ _|_ _| \\__ \\/ -_) _| || | '_ \\ + |_| \\_,_|_| \\__|_\\___|___/\\___/\\__| |_| |___/\\___|\\__|\\_,_| .__/ |_| """ @@ -31,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), @@ -46,7 +68,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): @@ -102,11 +124,12 @@ 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' @@ -150,63 +173,9 @@ 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] - - 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")) + (error, result) = self.update_create3() + if error: + return (error, result) if reinstall_job: self.ros.robot_upstart_menu.install() @@ -220,6 +189,92 @@ 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: @@ -19,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) @@ -66,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' +