From 4b0a568f44f73eaabf85d3fb9ff9c623cff18a5c Mon Sep 17 00:00:00 2001 From: Hilary Luo Date: Mon, 25 Mar 2024 23:25:18 -0400 Subject: [PATCH] Add create3 usb0 subnet setting --- etc/systemd/system/webserver.service | 2 +- etc/turtlebot4/aliases.bash | 2 +- etc/turtlebot4/webserver.sh | 3 ++ scripts/create_update.sh | 3 +- turtlebot4_setup/conf.py | 65 +++++++++++++++++++++++++--- turtlebot4_setup/ros_setup.py | 14 ++++++ turtlebot4_setup/turtlebot4_setup | 32 ++++++++++---- 7 files changed, 104 insertions(+), 17 deletions(-) create mode 100644 etc/turtlebot4/webserver.sh diff --git a/etc/systemd/system/webserver.service b/etc/systemd/system/webserver.service index 9b0568b..d409cfa 100644 --- a/etc/systemd/system/webserver.service +++ b/etc/systemd/system/webserver.service @@ -6,7 +6,7 @@ StartLimitIntervalSec=0 Type=simple Restart=always RestartSec=1 -ExecStart=/usr/bin/socat TCP-LISTEN:8080,fork,reuseaddr tcp:192.168.186.2:80 +ExecStart=/etc/turtlebot4/webserver.sh [Install] WantedBy=multi-user.target diff --git a/etc/turtlebot4/aliases.bash b/etc/turtlebot4/aliases.bash index d4a4270..a8b4c32 100644 --- a/etc/turtlebot4/aliases.bash +++ b/etc/turtlebot4/aliases.bash @@ -7,7 +7,7 @@ TurtleBot 4 User Manual: https://turtlebot.github.io/turtlebot4-user-manual \n\ TurtleBot 4 Github: https://github.com/turtlebot/turtlebot4"' # Restart ntpd on Create 3 -alias turtlebot4-ntpd-sync='curl -X POST http://192.168.186.2/api/restart-ntpd' +alias turtlebot4-ntpd-sync='curl -X POST http://${CREATE3_IP:-192.1168.186.2}/api/restart-ntpd' # Restart turtlebot4 service alias turtlebot4-service-restart='sudo systemctl restart turtlebot4.service' diff --git a/etc/turtlebot4/webserver.sh b/etc/turtlebot4/webserver.sh new file mode 100644 index 0000000..0378f5a --- /dev/null +++ b/etc/turtlebot4/webserver.sh @@ -0,0 +1,3 @@ +#!/bin/bash +source /etc/turtlebot4/setup.bash +/usr/bin/socat TCP-LISTEN:8080,fork,reuseaddr tcp:${CREATE3_IP:-192.1168.186.2}:80 \ No newline at end of file diff --git a/scripts/create_update.sh b/scripts/create_update.sh index 5847051..f4b97e6 100755 --- a/scripts/create_update.sh +++ b/scripts/create_update.sh @@ -27,4 +27,5 @@ done echo "Image path: $1"; -curl -X POST --data-binary @$1 http://192.168.186.2/api/firmware-update +source /etc/turtlebot4/setup.bash +curl -X POST --data-binary @$1 http://${CREATE3_IP:-192.1168.186.2}/api/firmware-update \ No newline at end of file diff --git a/turtlebot4_setup/conf.py b/turtlebot4_setup/conf.py index 7258e81..900f0c8 100644 --- a/turtlebot4_setup/conf.py +++ b/turtlebot4_setup/conf.py @@ -35,6 +35,7 @@ class BashOptions(str, Enum): DIAGNOSTICS = 'TURTLEBOT4_DIAGNOSTICS' WORKSPACE = 'WORKSPACE_SETUP' SUPER_CLIENT = 'ROS_SUPER_CLIENT' + CREATE3_IP = 'CREATE3_IP' class DiscoveryOptions(str, Enum): @@ -42,6 +43,7 @@ class DiscoveryOptions(str, Enum): IP = 'IP' PORT = 'PORT' SERVER_ID = 'SERVER_ID' + SUBNET = 'CREATE3_USB0_SUBNET' class Conf(): @@ -74,7 +76,8 @@ class Conf(): BashOptions.RMW: 'rmw_fastrtps_cpp', BashOptions.DIAGNOSTICS: '1', BashOptions.WORKSPACE: '/opt/ros/humble/setup.bash', - BashOptions.SUPER_CLIENT: False + BashOptions.SUPER_CLIENT: False, + BashOptions.CREATE3_IP: '192.168.186.2', } default_discovery_conf = { @@ -82,11 +85,13 @@ class Conf(): DiscoveryOptions.IP: '127.0.0.1', DiscoveryOptions.PORT: '11811', DiscoveryOptions.SERVER_ID: '0', + DiscoveryOptions.SUBNET: '186', } def __init__(self) -> None: self.system_file = os.path.join(self.setup_dir, 'system') self.setup_bash_file = os.path.join(self.setup_dir, 'setup.bash') + self.netplan_eth_file = os.path.join(self.netplan_dir, '40-ethernets.yaml') self.netplan_wifis_file = os.path.join(self.netplan_dir, '50-wifis.yaml') self.discovery_sh_file = os.path.join(self.setup_dir, 'discovery.sh') self.hostname_file = '/etc/hostname' @@ -133,15 +138,17 @@ 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 + self.read_subnet() + self.read_system() # Must come after read_subnet in order to mask the USB0 IP address def write(self): self.write_system() self.write_wifi() self.write_discovery() + self.write_subnet() self.write_bash() def read_system(self): @@ -151,9 +158,9 @@ def read_system(self): if k in line: self.system_conf[k] = line.split(':')[1].strip() - self.system_conf[SystemOptions.IP] = subprocess.run( - shlex.split('hostname -I'), - capture_output=True).stdout.decode('ascii').replace('192.168.186.3', '').strip() + subnet = self.get(DiscoveryOptions.SUBNET) + output = subprocess.run(shlex.split('hostname -I'), capture_output=True).stdout + self.system_conf[SystemOptions.IP] = output.decode('ascii').replace(f'192.168.{subnet}.3', '').strip() with open(self.hostname_file, 'r') as f: self.set(SystemOptions.HOSTNAME, f.readline().strip()) @@ -307,6 +314,52 @@ def write_bash(self): else: os.environ[k] = str(v) + def read_subnet(self): + netplan = yaml.load(open(self.netplan_eth_file, 'r'), yaml.SafeLoader) + # wlan0 Config + usb0 = netplan['network']['ethernets']['usb0'] + address = list(usb0['addresses'])[0] + subnet = address.split('.')[2] + + # Get Subnet + self.set(DiscoveryOptions.SUBNET, subnet) + self.set(BashOptions.CREATE3_IP, f'192.168.{subnet}.2') + + def write_subnet(self): + subnet = self.get(DiscoveryOptions.SUBNET) + + # Update the usb0 subnet using netplan + netplan = { + 'network': { + 'ethernets': { + 'eth0': { + 'addresses': ['192.168.185.3/24'], + 'dhcp4': True, + 'optional': True, + }, + 'usb0': { + 'addresses': [f'192.168.{subnet}.3/24'], + 'dhcp4': False, + 'optional': True, + }, + }, + 'version': 2, + } + } + + with open('/tmp' + self.netplan_eth_file, 'w') as f: + 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_eth_file, 'a'), + Dumper=yaml.SafeDumper, + indent=4, + default_flow_style=False, + default_style=None) + + subprocess.run(shlex.split(f'sudo mv /tmp{self.netplan_eth_file} {self.netplan_eth_file}')) + + def read_discovery(self): discovery_server = self.get(BashOptions.DISCOVERY_SERVER) if discovery_server is None or discovery_server == '': @@ -348,7 +401,9 @@ def write_discovery(self): else: self.set(BashOptions.DISCOVERY_SERVER, None) self.set(BashOptions.SUPER_CLIENT, False) + self.set(DiscoveryOptions.SUBNET, self.default_discovery_conf[DiscoveryOptions.SUBNET]) + self.write_subnet() self.write_bash() @staticmethod diff --git a/turtlebot4_setup/ros_setup.py b/turtlebot4_setup/ros_setup.py index d4538fa..7fb44cc 100644 --- a/turtlebot4_setup/ros_setup.py +++ b/turtlebot4_setup/ros_setup.py @@ -176,6 +176,8 @@ def __init__(self, configs: Conf) -> None: function=self.set_port), MenuEntry(entry=self.format_entry('Server ID', DiscoveryOptions.SERVER_ID), function=self.set_server_id), + MenuEntry(entry=self.format_entry('Create3 USB0 Subnet', DiscoveryOptions.SUBNET), + function=self.set_subnet), MenuEntry('', None), MenuEntry(entry='Apply Defaults', function=self.apply_defaults), MenuEntry(entry='Save', function=self.save_settings)] @@ -221,6 +223,18 @@ def set_server_id(self): server_id = max(0, min(int(server_id), 255)) self.conf.set(DiscoveryOptions.SERVER_ID, server_id) + def set_subnet(self): + p = Prompt(prompt='Create3 Subnet [{0}]: '.format(self.conf.get(DiscoveryOptions.SUBNET)), + default_response=self.conf.get(DiscoveryOptions.SUBNET), + response_type=int, + note='Create3 Subnet (0-184, 186-255)') + subnet = p.show() + subnet = max(0, min(int(subnet), 255)) + if (subnet == 185): # Avoid Pi Ethernet subnet (prevent potential routing issues) + subnet = 184 + self.conf.set(DiscoveryOptions.SUBNET, subnet) + self.conf.set(BashOptions.CREATE3_IP, f'192.168.{subnet}.2') + def apply_defaults(self): self.conf.apply_default(self.conf.discovery_conf) diff --git a/turtlebot4_setup/turtlebot4_setup b/turtlebot4_setup/turtlebot4_setup index 22c89cd..6ab83f0 100755 --- a/turtlebot4_setup/turtlebot4_setup +++ b/turtlebot4_setup/turtlebot4_setup @@ -64,7 +64,7 @@ class Turtlebot4Setup(): if self.conf.get(option) == '' and self.initial_conf.get(option) == None or \ self.conf.get(option) == None and self.initial_conf.get(option) == '': pass - elif (self.conf.get(option) != self.initial_conf.get(option)): + elif (str(self.conf.get(option)) != str(self.initial_conf.get(option))): diff.append(option) return diff @@ -128,6 +128,7 @@ class Turtlebot4Setup(): menu_entries=['Okay']) options.show() return + self.apply_pi_network_settings() self.initial_conf = copy.deepcopy(self.conf) def apply_ros_settings(self): @@ -149,6 +150,9 @@ class Turtlebot4Setup(): reinstall_job = True if update_create3: + initial_subnet = self.initial_conf.get(DiscoveryOptions.SUBNET) + new_subnet = self.conf.get(DiscoveryOptions.SUBNET) + ros_domain_id = 'ros_domain_id=' + os.environ[BashOptions.DOMAIN_ID] ros_namespace = '&ros_namespace=' + os.environ[BashOptions.NAMESPACE] rmw_implementation = '&rmw_implementation=' + os.environ[BashOptions.RMW] @@ -157,7 +161,7 @@ class Turtlebot4Setup(): port = self.conf.get(DiscoveryOptions.PORT) ip = self.conf.get(DiscoveryOptions.IP) if ip == '127.0.0.1': - ip = f'192.168.186.3' + ip = f'192.168.{new_subnet}.3' discovery_server = f'&fast_discovery_server_value={self.conf.get_discovery_str(id, ip, port)}' if self.conf.get(DiscoveryOptions.ENABLED): @@ -171,7 +175,7 @@ class Turtlebot4Setup(): rmw_implementation, discovery_server, discovery_server_enabled)) + \ - shlex.split('-X POST http://192.168.186.2/ros-config-save-main') + shlex.split(f'-X POST http://192.168.{initial_subnet}.2/ros-config-save-main') result = subprocess.run(command, capture_output=True) @@ -179,9 +183,19 @@ class Turtlebot4Setup(): if (result.returncode != 0): return (result.returncode, "Error writing ROS settings to Create3\n\n" + result.stderr.decode("utf-8")) + # Set subnet on Create3 + new_wired_subnet = f'new_wired_subnet={self.conf.get(DiscoveryOptions.SUBNET)}' + command = shlex.split(f'curl -d "{new_wired_subnet}" -X POST http://192.168.{initial_subnet}.2/beta-wired-subnet-save') + + 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 subnet settings 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') + config = f'config=server 192.168.{new_subnet}.3 prefer iburst minpoll 4 maxpoll 6 # Use RPi4 server' + command = shlex.split(f'curl -d "{config}" -X POST http://192.168.{initial_subnet}.2/beta-ntp-conf-save') result = subprocess.run(command, capture_output=True) @@ -190,7 +204,7 @@ class Turtlebot4Setup(): 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) + result = subprocess.run(shlex.split(f'curl -X POST http://192.168.{initial_subnet}.2/api/reboot'), capture_output=True) # If the curl command fails then return and indicate the error. if (result.returncode != 0): @@ -202,9 +216,9 @@ class Turtlebot4Setup(): return (0, "Success") - def apply_wifi_settings(self): - # Run netplan apply if WiFi options have changed - if len(self.get_settings_diff(WifiOptions)) > 0: + def apply_pi_network_settings(self): + if len(self.get_settings_diff(WifiOptions)) > 0 or len(self.get_settings_diff(DiscoveryOptions)) > 0: + # Run netplan apply if WiFi options or Discovery options (for subnet) have changed subprocess.run(shlex.split('sudo netplan apply')) os.system('sudo reboot')