From 5c789124342916a309209c7be9674e5e30cf38dc Mon Sep 17 00:00:00 2001 From: mickey li Date: Thu, 28 Oct 2021 14:54:03 +0100 Subject: [PATCH 01/36] Moved docker-compose example files into Murmuration submodule repository --- .gitmodules | 3 + Murmuration | 1 + docker-compose-2-drones.yml | 129 ------------------------ docker-compose-3-drones.yml | 179 ---------------------------------- docker-compose-simple.tcp.yml | 33 ------- docker-compose-simple.yml | 31 ------ docker-compose.ap-gazebo.yml | 26 ----- docker-compose.arducopter.yml | 17 ---- docker-compose.tcp.yml | 52 ---------- docker-compose.yml | 48 --------- 10 files changed, 4 insertions(+), 515 deletions(-) create mode 160000 Murmuration delete mode 100644 docker-compose-2-drones.yml delete mode 100644 docker-compose-3-drones.yml delete mode 100644 docker-compose-simple.tcp.yml delete mode 100644 docker-compose-simple.yml delete mode 100644 docker-compose.ap-gazebo.yml delete mode 100644 docker-compose.arducopter.yml delete mode 100644 docker-compose.tcp.yml delete mode 100644 docker-compose.yml diff --git a/.gitmodules b/.gitmodules index a0d366e..c246d2b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "system/vicon/vicon_udp"] path = system/vicon/vicon_udp url = https://github.com/UoBFlightLab/vicon_udp.git +[submodule "Murmuration"] + path = Murmuration + url = https://github.com/StarlingUAS/Murmuration.git diff --git a/Murmuration b/Murmuration new file mode 160000 index 0000000..17f1d15 --- /dev/null +++ b/Murmuration @@ -0,0 +1 @@ +Subproject commit 17f1d15adbfe9562a79bdc2ed36ec8341a175c11 diff --git a/docker-compose-2-drones.yml b/docker-compose-2-drones.yml deleted file mode 100644 index 8eee0f1..0000000 --- a/docker-compose-2-drones.yml +++ /dev/null @@ -1,129 +0,0 @@ -version: '3' - -# This docker-compose file starts 2 drones -# This is not network host, therefore - -services: - - simhost: # Must be called simhost, hosted 8080 - image: uobflightlabstarling/starling-sim-iris:latest - command: [ "ros2", "launch", "launch/iris.launch.xml" , sim_only:=true] - network_mode: host - pid: "host" # Share Process ID Namespace - - # ---------- - # Spawn Vehicle 1 - gazebo_spawn_1: - image: uobflightlabstarling/starling-sim-iris:latest - command: [ "./spawn_iris.sh" ] - environment: - - "PX4_SIM_HOST=localhost" - - "PX4_INSTANCE=0" - - "IGNORE_FAILURE=true" - depends_on: - - simhost - pid: "host" # Share Process ID Namespace - network_mode: host - - sitl_1: - image: uobflightlabstarling/starling-sim-px4-sitl:latest - environment: - - "PX4_SIM_HOST=localhost" #simhost - - "PX4_OFFBOARD_HOST=localhost" #mavros_1 - - "PX4_INSTANCE=0" - # ports: - # - "18570:18570/udp" - depends_on: - - gazebo_spawn_1 - pid: "host" # Share Process ID Namespace - network_mode: host - - mavros_1: - image: uobflightlabstarling/starling-mavros:latest - command: ros2 launch launch/mavros_bridge.launch.xml - environment: - - "MAVROS_TGT_SYSTEM=1" - - "MAVROS_FCU_IP=0.0.0.0" - depends_on: - - sitl_1 - pid: "host" # Share Process ID Namespace - network_mode: host - - simple_offboard_1: - image: uobflightlabstarling/starling-simple-offboard:latest - environment: - - "VEHICLE_MAVLINK_SYSID=1" - depends_on: - - mavros_1 - pid: "host" # Share Process ID Namespace - network_mode: host - # End Spawn Vehicle 1 - # ---------- - - # ---------- - # Spawn Vehicle 2 - gazebo_spawn_2: - image: uobflightlabstarling/starling-sim-iris:latest - command: [ "./spawn_iris.sh" ] - environment: - - "PX4_SIM_HOST=localhost" - - "PX4_INSTANCE=1" - - "IGNORE_FAILURE=true" - - "PX4_SIM_INIT_LOC_X=2" - depends_on: - - simhost - pid: "host" # Share Process ID Namespace - network_mode: host - - sitl_2: - image: uobflightlabstarling/starling-sim-px4-sitl:latest - environment: - - "PX4_SIM_HOST=localhost" #simhost - - "PX4_OFFBOARD_HOST=localhost" #mavros_2 - - "PX4_INSTANCE=1" - # ports: - # - "18571:18571/udp" - depends_on: - - gazebo_spawn_2 - pid: "host" # Share Process ID Namespace - network_mode: host - - mavros_2: - image: uobflightlabstarling/starling-mavros:latest - command: ros2 launch launch/mavros_bridge.launch.xml - environment: - - "MAVROS_TGT_SYSTEM=2" - - "MAVROS_FCU_IP=0.0.0.0" - depends_on: - - sitl_2 - pid: "host" # Share Process ID Namespace - network_mode: host - - simple_offboard_2: - image: uobflightlabstarling/starling-simple-offboard:latest - environment: - - "VEHICLE_MAVLINK_SYSID=2" - depends_on: - - mavros_2 - pid: "host" # Share Process ID Namespace - network_mode: host - # End Spawn Vehicle 2 - # ---------- - - ui: - image: mickeyli789/starling-ui-dashly:latest - # ports: - # - "3000:3000" - pid: "host" # Share Process ID Namespace - network_mode: host - - allocator: - image: uobflightlabstarling/starling-allocator:latest - pid: "host" # Share Process ID Namespace - network_mode: host - - ros-web-bridge: - image: uobflightlabstarling/ros-web-bridge:latest - network_mode: host - # ports: - # - "9090:9090" diff --git a/docker-compose-3-drones.yml b/docker-compose-3-drones.yml deleted file mode 100644 index 7cbd855..0000000 --- a/docker-compose-3-drones.yml +++ /dev/null @@ -1,179 +0,0 @@ -version: '3' - -services: - - simhost: # Must be called simhost - image: uobflightlabstarling/starling-sim-iris:latest - command: [ "ros2", "launch", "launch/iris.launch.xml" , sim_only:=true] - # ports: - # - "8080:8080" - pid: "host" # Share Process ID Namespace - network_mode: host - - # ---------- - # Spawn Vehicle 1 - gazebo_spawn_1: - image: uobflightlabstarling/starling-sim-iris:latest - command: [ "./spawn_iris.sh" ] - environment: - - "PX4_SIM_HOST=localhost" - - "PX4_INSTANCE=0" - - "IGNORE_FAILURE=true" - depends_on: - - simhost - pid: "host" # Share Process ID Namespace - network_mode: host - - sitl_1: - image: uobflightlabstarling/starling-sim-px4-sitl:latest - environment: - - "PX4_SIM_HOST=localhost" - - "PX4_OFFBOARD_HOST=localhost" - - "PX4_INSTANCE=0" - # ports: - # - "18570:18570/udp" - depends_on: - - gazebo_spawn_1 - pid: "host" # Share Process ID Namespace - network_mode: host - - mavros_1: - image: uobflightlabstarling/starling-mavros:latest - command: ros2 launch launch/mavros_bridge.launch.xml - environment: - - "MAVROS_TGT_SYSTEM=1" - - "MAVROS_FCU_IP=0.0.0.0" - depends_on: - - sitl_1 - pid: "host" # Share Process ID Namespace - network_mode: host - - simple_offboard_1: - image: uobflightlabstarling/starling-simple-offboard:latest - environment: - - "VEHICLE_MAVLINK_SYSID=1" - depends_on: - - mavros_1 - pid: "host" # Share Process ID Namespace - network_mode: host - # End Spawn Vehicle 1 - # ---------- - - # ---------- - # Spawn Vehicle 2 - gazebo_spawn_2: - image: uobflightlabstarling/starling-sim-iris:latest - command: [ "./spawn_iris.sh" ] - environment: - - "PX4_SIM_HOST=localhost" - - "PX4_INSTANCE=1" - - "IGNORE_FAILURE=true" - - "PX4_SIM_INIT_LOC_X=2" - depends_on: - - simhost - pid: "host" # Share Process ID Namespace - network_mode: host - - sitl_2: - image: uobflightlabstarling/starling-sim-px4-sitl:latest - environment: - - "PX4_SIM_HOST=localhost" - - "PX4_OFFBOARD_HOST=localhost" - - "PX4_INSTANCE=1" - # ports: - # - "18571:18571/udp" - depends_on: - - gazebo_spawn_2 - pid: "host" # Share Process ID Namespace - network_mode: host - - mavros_2: - image: uobflightlabstarling/starling-mavros:latest - command: ros2 launch launch/mavros_bridge.launch.xml - environment: - - "MAVROS_TGT_SYSTEM=2" - - "MAVROS_FCU_IP=0.0.0.0" - depends_on: - - sitl_2 - pid: "host" # Share Process ID Namespace - network_mode: host - - simple_offboard_2: - image: uobflightlabstarling/starling-simple-offboard:latest - environment: - - "VEHICLE_MAVLINK_SYSID=2" - depends_on: - - mavros_2 - pid: "host" # Share Process ID Namespace - network_mode: host - # End Spawn Vehicle 2 - # ---------- - - - # ---------- - # Spawn Vehicle 3 - gazebo_spawn_3: - image: uobflightlabstarling/starling-sim-iris:latest - command: [ "./spawn_iris.sh" ] - environment: - - "PX4_SIM_HOST=localhost" - - "PX4_INSTANCE=2" - - "IGNORE_FAILURE=true" - - "PX4_SIM_INIT_LOC_X=4" - depends_on: - - simhost - pid: "host" # Share Process ID Namespace - network_mode: host - - sitl_4: - image: uobflightlabstarling/starling-sim-px4-sitl:latest - environment: - - "PX4_SIM_HOST=localhost" - - "PX4_OFFBOARD_HOST=localhost" - - "PX4_INSTANCE=2" - network_mode: host - # ports: - # - "18572:18572/udp" - depends_on: - - gazebo_spawn_3 - pid: "host" # Share Process ID Namespace - - mavros_3: - image: uobflightlabstarling/starling-mavros:latest - command: ros2 launch launch/mavros_bridge.launch.xml - network_mode: host - environment: - - "MAVROS_TGT_SYSTEM=3" - - "MAVROS_FCU_IP=0.0.0.0" - depends_on: - - sitl_3 - pid: "host" # Share Process ID Namespace - - simple_offboard_2: - image: uobflightlabstarling/starling-simple-offboard:latest - network_mode: host - environment: - - "VEHICLE_MAVLINK_SYSID=3" - depends_on: - - mavros_3 - pid: "host" # Share Process ID Namespace - # End Spawn Vehicle 3 - # ---------- - - ui: - image: mickeyli789/starling-ui-dashly:latest - network_mode: host - # ports: - # - "3000:3000" - pid: "host" # Share Process ID Namespace - - allocator: - image: uobflightlabstarling/starling-allocator:latest - network_mode: host - pid: "host" # Share Process ID Namespace - - ros-web-bridge: - image: uobflightlabstarling/ros-web-bridge:latest - network_mode: host - # ports: - # - "9090:9090" diff --git a/docker-compose-simple.tcp.yml b/docker-compose-simple.tcp.yml deleted file mode 100644 index 0550b18..0000000 --- a/docker-compose-simple.tcp.yml +++ /dev/null @@ -1,33 +0,0 @@ -version: '3' - -services: - simhost: - image: uobflightlabstarling/starling-sim-iris:latest - environment: - - "PX4_SIM_HOST=localhost" # Required for spawning the vehicle on localhost - network_mode: host - - sitl: - image: uobflightlabstarling/starling-sim-px4-sitl:latest - environment: - - "PX4_SIM_HOST=localhost" - - "PX4_OFFBOARD_HOST=localhost" - network_mode: host - - mavros: - image: uobflightlabstarling/starling-mavros:latest - command: ros2 launch launch/mavros_bridge.launch.xml - network_mode: host - environment: - - "MAVROS_TGT_SYSTEM=1" - - "MAVROS_FCU_IP=0.0.0.0" - - "MAVROS_GCS_URL=tcp-l://0.0.0.0:5760" - - - ros-web-bridge: - image: uobflightlabstarling/ros-web-bridge:latest - network_mode: host - - ui-example: - image: uobflightlabstarling/starling-ui-example - network_mode: host \ No newline at end of file diff --git a/docker-compose-simple.yml b/docker-compose-simple.yml deleted file mode 100644 index 56856ef..0000000 --- a/docker-compose-simple.yml +++ /dev/null @@ -1,31 +0,0 @@ -version: '3' - -services: - simhost: - image: uobflightlabstarling/starling-sim-iris:latest - environment: - - "PX4_SIM_HOST=localhost" # Required for spawning the vehicle on localhost - network_mode: host - - sitl: - image: uobflightlabstarling/starling-sim-px4-sitl:latest - environment: - - "PX4_SIM_HOST=localhost" - - "PX4_OFFBOARD_HOST=localhost" - network_mode: host - - mavros: - image: uobflightlabstarling/starling-mavros:latest - command: ros2 launch launch/mavros_bridge.launch.xml - network_mode: host - environment: - - "MAVROS_TGT_SYSTEM=1" - - "MAVROS_FCU_IP=0.0.0.0" - - ros-web-bridge: - image: uobflightlabstarling/ros-web-bridge:latest - network_mode: host - - ui-example: - image: uobflightlabstarling/starling-ui-example - network_mode: host \ No newline at end of file diff --git a/docker-compose.ap-gazebo.yml b/docker-compose.ap-gazebo.yml deleted file mode 100644 index 1930309..0000000 --- a/docker-compose.ap-gazebo.yml +++ /dev/null @@ -1,26 +0,0 @@ -version: '3' - -services: - simhost: - network_mode: host - image: uobflightlabstarling/starling-sim-iris-ap:latest - # ports: - # - "8080:8080" - - sitl: - network_mode: host - image: uobflightlabstarling/starling-sim-ardupilot-copter:latest - environment: - - AP_USE_GAZEBO=true - - mavros: - network_mode: host - image: uobflightlabstarling/starling-mavros:latest - command: ros2 launch launch/mavros_bridge.launch.xml - environment: - - "MAVROS_TGT_FIRMWARE=apm" - - "MAVROS_TGT_SYSTEM=1" - - "MAVROS_FCU_URL=tcp://127.0.0.1:5760" - - "MAVROS_CONFIG_PATH=/mavros_config_ap.yaml" - - "MAVROS_PLUGINLISTS_PATH=/mavros_pluginlists_ap.yaml" - diff --git a/docker-compose.arducopter.yml b/docker-compose.arducopter.yml deleted file mode 100644 index 51f084c..0000000 --- a/docker-compose.arducopter.yml +++ /dev/null @@ -1,17 +0,0 @@ -version: '3' - -services: - sitl: - image: uobflightlabstarling/starling-sim-ardupilot-copter:latest - network_mode: host - environment: - - AP_SYSID=ip - - AP_DISTRIBUTE=1 - - gateway: - image: uobflightlabstarling/mavp2p - command: get_sitls.sh udps:0.0.0.0:14551 - network_mode: host - # ports: - # - 14551:14551/udp - diff --git a/docker-compose.tcp.yml b/docker-compose.tcp.yml deleted file mode 100644 index 19940c5..0000000 --- a/docker-compose.tcp.yml +++ /dev/null @@ -1,52 +0,0 @@ -version: '3' - -services: - simhost: # Must be called simhost - image: uobflightlabstarling/starling-sim-iris:latest - environment: - - "PX4_SIM_HOST=localhost" - network_mode: host - - sitl: - image: uobflightlabstarling/starling-sim-px4-sitl:latest - environment: - - "PX4_SIM_HOST=localhost" - - "PX4_OFFBOARD_HOST=localhost" - network_mode: host - depends_on: - - simhost - - mavros: - image: uobflightlabstarling/starling-mavros:latest - command: ros2 launch launch/mavros_bridge.launch.xml - environment: - - "MAVROS_TGT_SYSTEM=1" - - "MAVROS_FCU_IP=0.0.0.0" - - "MAVROS_GCS_URL=tcp-l://0.0.0.0:5760" - network_mode: host - depends_on: - - sitl - - - simple_offboard: - image: uobflightlabstarling/starling-simple-offboard:latest - network_mode: host - environment: - - "VEHICLE_MAVLINK_SYSID=1" - depends_on: - - mavros - - ui: - image: mickeyli789/starling-ui-dashly:latest - network_mode: host - - allocator: - image: uobflightlabstarling/starling-allocator:latest - network_mode: host - - ros-web-bridge: - image: uobflightlabstarling/ros-web-bridge:latest - network_mode: host - - - diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 96edfcf..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,48 +0,0 @@ -version: '3' - -services: - simhost: # Must be called simhost - image: uobflightlabstarling/starling-sim-iris:latest - environment: - - "PX4_SIM_HOST=localhost" - network_mode: host - - sitl: - image: uobflightlabstarling/starling-sim-px4-sitl:latest - environment: - - "PX4_SIM_HOST=localhost" - - "PX4_OFFBOARD_HOST=localhost" - network_mode: host - depends_on: - - simhost - - mavros: - image: uobflightlabstarling/starling-mavros:latest - command: ros2 launch launch/mavros_bridge.launch.xml - environment: - - "MAVROS_TGT_SYSTEM=1" - - "MAVROS_FCU_IP=0.0.0.0" - network_mode: host - depends_on: - - sitl - - - simple_offboard: - image: uobflightlabstarling/starling-simple-offboard:latest - network_mode: host - environment: - - "VEHICLE_MAVLINK_SYSID=1" - depends_on: - - mavros - - ui: - image: mickeyli789/starling-ui-dashly:latest - network_mode: host - - allocator: - image: uobflightlabstarling/starling-allocator:latest - network_mode: host - - ros-web-bridge: - image: uobflightlabstarling/ros-web-bridge:latest - network_mode: host From 3416cdf9d24b0da55a021fe2595a7c4cfcdc0346 Mon Sep 17 00:00:00 2001 From: mickey li Date: Thu, 28 Oct 2021 15:29:43 +0100 Subject: [PATCH 02/36] Updated docs --- README.md | 7 +++ docs/README.md | 7 +++ docs/details/kubernetes-deployment.md | 4 +- docs/guide/development.md | 13 ++++- docs/guide/getting-started.md | 5 +- docs/guide/single-drone-local-machine.md | 66 +++++++++++++++++------- docs/guide/windows-support.md | 18 +++++-- docs/other/related_projects_and_links.md | 13 ++++- 8 files changed, 102 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 7d180ec..5e706aa 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,13 @@ This systems provides a number of key features. ![Simple Architecture](/docs/img/ArchSimple.jpg) +## Setup + +For local development and testing, clone the repository recursively so that all submodules are included + +``` +git clone --recurse-submodules https://github.com/StarlingUAS/ProjectStarling.git +``` ## Documentation Please refer to the documentation at [uobflightlab.github.io/ProjectStarling](https://uobflightlab.github.io/ProjectStarling) for detailed instructions and explanations of how to use this system. diff --git a/docs/README.md b/docs/README.md index b1d604e..28e0300 100644 --- a/docs/README.md +++ b/docs/README.md @@ -14,6 +14,13 @@ This systems provides a number of key features. ![Simple Architecture](/img/ArchSimple.jpg) +## Setup + +For local development and testing, clone the repository recursively so that all submodules are included + +``` +git clone --recurse-submodules https://github.com/StarlingUAS/ProjectStarling.git +``` ## Documentation Please refer to the documentation at [uobflightlab.github.io/ProjectStarling](https://uobflightlab.github.io/ProjectStarling) for detailed instructions and explanations of how to use this system. diff --git a/docs/details/kubernetes-deployment.md b/docs/details/kubernetes-deployment.md index 04c5715..af341bf 100644 --- a/docs/details/kubernetes-deployment.md +++ b/docs/details/kubernetes-deployment.md @@ -126,9 +126,9 @@ If you want to stop kubernetes completely, the internet install script comes wit [See the k3s docs for info on how to run](https://rancher.com/docs/k3s/latest/en/installation/kube-dashboard/) Are started automatically in the `./run_k3s.sh` script. -## Running the test cases + -Go to [testing directory for more info](testing/README.md) + diff --git a/docs/guide/development.md b/docs/guide/development.md index 75c8f93..0c15a73 100644 --- a/docs/guide/development.md +++ b/docs/guide/development.md @@ -1,6 +1,6 @@ # Developing your own containers -This guide shows recommended methods for developing your own controllers and using Starling to develop your exeperiments. +This guide shows recommended methods for developing your own controllers and using Starling to develop your experiments. ## Contents @@ -41,3 +41,14 @@ There are actually a number of methods of developing an application within a con * Iteration is very quick and your local environment need not be modified in any way. * Be aware that you may need to repeat some of these steps after restarting the container if the container environment (e.g. ports/networking) needs to be changed. * Recommended method if quick changes need to be tested, but not for full development in case the environment needs to be changed. + + +## VSCode / ROS2 Template Project + +There is a template repository within the StarlingUAS organisation called [`vscode_ros2_workspace`](https://github.com/StarlingUAS/vscode_ros2_workspace). This was developed by an active VSCode/ROS2 developer working in industry. + +This template most likely has many features that you are possibly unlikely to use, but will give you a flavour of what developing systems in ROS2 is like out there. + +Feel free to try and use this template, I would recommend thoroughly reading through the repository README for the full instructions. + +Also if you are on windows you may need to change [line 11 of `.devcontainer/devcontainer.json`](https://github.com/StarlingUAS/vscode_ros2_workspace/blob/4392bd0873a5d4c32a804fcaaa9f7fbe1b1057af/.devcontainer/devcontainer.json#L11) to the name of the current docker network (if using docker-compose - see the tutorial example single drone local). \ No newline at end of file diff --git a/docs/guide/getting-started.md b/docs/guide/getting-started.md index 919fa2d..a7e6688 100644 --- a/docs/guide/getting-started.md +++ b/docs/guide/getting-started.md @@ -54,6 +54,8 @@ That is Docker on Linux installed. See [the original page](https://docs.docker.c For Windows systems, please see [the windows installation instructions for full details](https://docs.docker.com/docker-for-windows/install/). You will need to install Docker Desktop for Windows. +Follow the instructions and tutorial below, but refer to our [Windows Support page](windows-support.md) if you have issues. + Starling has not been fully tested on Windows, but single-agent non-cluster (docker-only) applications should be compatible. ### Mac OS @@ -94,11 +96,10 @@ For *multiple drone* applications, there are also 3 steps to the development of 1. **Step 1:** *Single Drone testing on local machine using Docker only* - Follow the following [user guide](../single-drone-local-machine) - Simplest deployment method and the quickest way to get a single simulation (gazebo) and drone (px4, mavros) instance running. - - Quick and easy way to test the basic functionality of your controller without worrying about too may other factors. - Can be used to quickly protype controllers before testing with multiple vehicles. 2. **Step 2:** *Multiple Drone testing on local cluster* - - Follow the following [user guide](../multiple-drone-local-machine.md) + - Follow the following [user guide](multiple-drone-local-machine.md) - The cluster facilitates the multi-agent capability of starling. - The guide takes you through the basic concepts required for running multi-agent applications 3. **Step 3:** *Multiple Drones flying at the Robotics Laboratory* diff --git a/docs/guide/single-drone-local-machine.md b/docs/guide/single-drone-local-machine.md index cbbe781..e5b29dc 100644 --- a/docs/guide/single-drone-local-machine.md +++ b/docs/guide/single-drone-local-machine.md @@ -11,55 +11,79 @@ Follow these instructions for quick and easy testing of controllers on a single ## Contents [TOC] -## Starting the Drone and Simulator (Simple Version) +## Pre-Setup There are two basic example starting confgurations, this 'Simple' version starts the bare minimum with the expectation of it being used through mavlink. There is also a 'Full' version with instructions below this section which starts a system capable of flying higher level paths and trajectories submitted through a GUI or ROS. - First check that you have installed the single prerequisit of `docker`, see [Getting Started](../guide/getting-started). You may also need to install `docker-compose` via `sudo apt-get install docker-compose`. -In the root directory first download the most up-to-date versions of the system using `docker-compose pull`. (Periodically run this to ensure you are updated). Then in a terminal, execute: - +Secondly you will need to clone the examples repository. If you are only running examples, clone the *Murmuration* repository: +``` +git clone https://github.com/StarlingUAS/Murmuration.git +cd Murmuration ``` -docker-compose -f docker-compose-simple.yml up +If you are preparing to run multi-drone systems or require more complex setup, recursively clone the *ProjectStarling* repository: ``` +git clone --recurse-submodules https://github.com/StarlingUAS/ProjectStarling.git +cd ProjectStarling/Mumurations # Go into Murmuration directory +``` + +> **Note** if ProjectStarling is already cloned, the submodules can be accessed by running `git submodule init && git submodule update` +## Starting the Drone and Simulator (Core for use with GCS) + +In the Mumuration root directory first download the most up-to-date versions of the system, then start *up* the system: -> **Note**: If on Windows using with a native windows GCS such as Mission Planner, you may need to use `docker-compose -f docker-compose-simple.tcp.yml up` +``` +# First Pull the relevant docker containers +docker-compose -f docker-compose/px4/docker-compose.core.linux.yml pull +# or for windows +# docker-compose -f docker-compose/px4/docker-compose.core.windows.yml pull + +# Run the docker-containers +docker-compose -f docker-compose/px4/docker-compose.core.linux.yml up +# or for windows +# docker-compose -f docker-compose/px4/docker-compose.core.windows.yml up +``` This will start the following: 1. Gazebo simulation enviornment running 1 Iris quadcopter model 2. A SITL (Software In The Loop) instance running the PX4 autopilot. 3. A Mavros node connected to the SITL instance -4. A simple UI with a go and estop button. +4. A ros-web-bridge allowing for ros connections through the web (this is used later) -> **Note:** this might take a while (can be up to 30 min or an hour depending on internet) on first run as downloads are required. +> **Note:** the downloads might take a while (can be up to 30 min or an hour depending on internet) on first run as downloads are required. > **Note:** Use ctrl+c to stop the process when you have finished. The User Interfaces are available in the following locations: - Go to [`http://localhost:8080`](http://localhost:8080) in a browser to (hopefully) see the gazebo simulator. -- Go to [`http://localhost:3000`](http://localhost:3000) in a browser to see the starling user interface containing go/stop buttons. > **Note:** All specified sites can be accessed from other machines by replacing `localhost` with your computer's IP address. > **Note:** Sometimes it might take a bit of time for the UIs to become available, give it a minute and refresh the page. With Gazebo you may accidentally be too zoomed in, or the grid may not show up. Use the mouse wheel to zoom in and out. The grid can be toggled on the left hand pane. -## Starting the Drone and Simulator (Full Version) +Separately, a Ground Control Station such as [*QGroundControl*](http://qgroundcontrol.com/) for Linux or [*Mission Planner*](https://ardupilot.org/planner/) can be downloaded and started. The simulated drone should automatically connect through the default ports. See [below](https://docs.starlinguas.dev/guide/single-drone-local-machine/#1-connecting-a-ground-control-station-via-mavlink) -This confugration starts a slightly more complex system which is capable of automatic higher level navigation and trajectory following. Ensure that the 'simple' version has been stopped before using this one. +## Starting the Drone and Simulator (Simple-Offboard through ROS2) -Again, first check that you have installed the single prerequisit of `docker`, see [Getting Started](../guide/getting-started). You may also need to install `docker-compose` via `sudo apt-get install docker-compose`. +This confugration starts a slightly more complex system which is capable of automatic higher level navigation and trajectory following. Ensure that the 'simple' version has been stopped (Ctrl+C) before using this one. -In the root directory first download the most up-to-date versions of the system using `docker-compose pull`. (Periodically run this to ensure you are updated). Then in a terminal, execute: +In the Mumuration root directory first download the most up-to-date versions of the system, then start *up* the system: ``` -docker-compose up +# First Pull the relevant docker containers +docker-compose -f docker-compose/px4/docker-compose.simple-offboard.linux.yml pull +# or for windows +# docker-compose -f docker-compose/px4/docker-compose.simple-offboard.windows.yml pull + +# Run the docker-containers +docker-compose -f docker-compose/px4/docker-compose.simple-offboard.linux.yml up +# or for windows +# docker-compose -f docker-compose/px4/docker-compose.simple-offboard.windows.yml up ``` -> **Note**: If on Windows using with a native windows GCS such as Mission Planner, you may need to use `docker-compose -f docker-compose.tcp.yml up` - This will start the following: 1. Gazebo simulation enviornment running 1 Iris quadcopter model @@ -106,9 +130,9 @@ An example offboard ROS2 controller can then be conncted to SITL by running the ```bash docker pull uobflightlabstarling/example_controller_python # Download the latest container -docker run -it --rm --network projectstarling_default uobflightlabstarling/example_controller_python +docker run -it --rm --network px4_default uobflightlabstarling/example_controller_python ``` -This will download and run the `example_controller_python` image from `uobflightlabstarling` on docker hub. `-it` opens an interactive terminal. `--rm` removes the container when completed. `--network` attaches the container to the default network created by `docker-compose`. The default network name is `_default`. +This will download and run the `example_controller_python` image from `uobflightlabstarling` on docker hub. `-it` opens an interactive terminal. `--rm` removes the container when completed. `--network` attaches the container to the default network created by `docker-compose`. The default network name is "`_default`". > **Note:** The controller may complain that it cannot find the drone. Double check that the name of the root folder matches the one passed to `--network`. @@ -134,7 +158,7 @@ In this instance there is only an abstract difference between onboard and offboa ## Implementing a Controller ### Modifying the example controller -In the [controllers](https://github.com/UoBFlightLab/ProjectStarling/tree/master/controllers) folder there is an example_controller_python which you should have seen in action in the example above. The ROS2 package is in [example_controller_python](https://github.com/UoBFlightLab/ProjectStarling/tree/master/controllers/example_controller_python/example_controller_python). Any edits made to the ROS2 package must first be built: +In the [ProjectStarling controllers](https://github.com/UoBFlightLab/ProjectStarling/tree/master/controllers) folder there is an example_controller_python which you should have seen in action in the example above. The ROS2 package is in [example_controller_python](https://github.com/UoBFlightLab/ProjectStarling/tree/master/controllers/example_controller_python/example_controller_python). Any edits made to the ROS2 package must first be built: ```bash cd controllers make example_controller_python @@ -159,6 +183,8 @@ Inside you can `source install/setup.bash` and run ROS2 commands like normal. ### Creating your own from scratch +> See the [development docs](development.md) for more detailed information on container development. + Of course you can create your own controller from scratch. Inside your controller repository, the following is required 1. Your ROS2 package folder (what would usually go inside the `dev_ws/src` directory) 2. A Dockerfile (named `Dockerfile`) which is dervied `FROM uobflightlabstarling/starling-controller-base`, use the [example Dockerfile](https://github.com/UoBFlightLab/ProjectStarling/blob/master/controllers/example_controller_python/Dockerfile) as a template. @@ -169,6 +195,6 @@ docker build -t # e.g. docker build -t my_new_controller . ``` -Your container can then be run as above using `docker run. +Your container can then be run as above using `docker run. ## Troubleshooting/ FAQs \ No newline at end of file diff --git a/docs/guide/windows-support.md b/docs/guide/windows-support.md index 3954be3..19ce695 100644 --- a/docs/guide/windows-support.md +++ b/docs/guide/windows-support.md @@ -20,14 +20,17 @@ versioning and is used for distributing the core Starling files. It can be downl ## Getting Starling -Clone the `ProjectStarling` repo to a working folder on your computer. You probably want to avoid +For simple usage of the system, clone the `Mumuration` repo to a working folder on your computer. If you are doing slightly more complex work, clone the `ProjectStarling` repo to a working folder on your computer. You probably want to avoid any cloud-backed folders as these tend to interfere with Git. With Git installed as above, you can open PowerShell, navigate to the folder where you want to clone the project (using `cd`) and run: ``` -git clone https://github.com/UoBFlightLab/ProjectStarling +# Mumurations +git clone https://github.com/StarlingUAS/Mumuration +# ProjectStarling +git clone --recurse-subdmoules https://github.com/StarlingUAS/ProjectStarling ``` ## Running @@ -37,10 +40,10 @@ Once there you should see a file called `docker-compose.tcp.yml`. This contains tool called Docker Compose to setup a set of containers for you. Note that this version has been modified slightly to use TCP ports which makes things easier on Windows/WSL. -To launch the containers, open PowerShell, make sure you're in the root of the ProjectStarling repo -and run: +To launch the containers, open PowerShell, make sure you're in the root of the Mumuration repo (if using ProjectStarling it will be 'ProjectStarling/Mumuration') +and run the windows version of any launch: ```ps -docker compose -f docker-compose.tcp.yml up +docker-compose -f docker-compose/px4/docker-compose.simple-offboard.windows.yml up ``` You should see the tool begin to "pull" (download) the files needed to run the project. These have @@ -57,3 +60,8 @@ With everything running, and docker allowed to access the network, open a web br You can also launch Mission Planner (other GCS software is available) and connect to TCP port 5760. Using the IP address of `127.0.0.1` should work as docker will ensure that the connection gets routed to the right place. + +## Things to be aware of + +Docker works slightly differently in Windows compared to Linux which can cause problems, especially with regards to networking. If you're webpages for gazebo or others are not connecting on local ports, it may be because you have run the linux docker-compose file by accident! + diff --git a/docs/other/related_projects_and_links.md b/docs/other/related_projects_and_links.md index 7eda602..bb14771 100644 --- a/docs/other/related_projects_and_links.md +++ b/docs/other/related_projects_and_links.md @@ -21,14 +21,25 @@ This repository contains the core elements of the system. It contains the source In addition it also contains the source for the following containers for simulation * [uobflightlabstarling/starling-sim-base-core](https://hub.docker.com/repository/docker/uobflightlabstarling/starling-sim-base-core) - Base Gazebo simulator -* [uobflightlabstarling/starling-sim-base-px4](https://hub.docker.com/repository/docker/uobflightlabstarling/starling-sim-base-px4) - Base continaer with px4 + +* [uobflightlabstarling/starling-sim-base-px4](https://hub.docker.com/repository/docker/uobflightlabstarling/starling-sim-base-px4) - Base container with px4 * [uobflightlabstarling/starling-sim-px4-sitl](https://hub.docker.com/repository/docker/uobflightlabstarling/starling-sim-px4-sitl) - Gazebo container with px4 sitl installed * [uobflightlabstarling/starling-sim-iris](https://hub.docker.com/repository/docker/uobflightlabstarling/starling-sim-iris) - Base gazebo container with px4 sitl installed and spwawns the iris quadcopter model +* [uobflightlabstarling/starling-sim-ardupilot-copter](https://hub.docker.com/repository/docker/uobflightlabstarling/starling-sim-ardupilot-copter) - Base Container with ArduCopter +* [uobflightlabstarling/starling-sim-ardupilot-gazebo](https://hub.docker.com/repository/docker/uobflightlabstarling/starling-sim-ardupilot-gazebo) - Gazebo Simulator for use with Ardupilot +* [uobflightlabstarling/starling-sim-iris-ap](https://hub.docker.com/repository/docker/uobflightlabstarling/starling-sim-iris-ap) - Base gazebo container with ardupulot sitl installed and spawns the iris quadcopter model with camera. + It also contains some example usage: * [uobflightlabstarling/example_controller_python](https://hub.docker.com/repository/docker/uobflightlabstarling/example_controller_python) +### Murmuration + +link - [https://github.com/StarlingUAS/Murmuration](https://github.com/StarlingUAS/Murmuration) + +This repository contains all of the docker-compose and kubernetes deployment files. These deployment files rely on a number of the containers in this file. + ### Starling Simple Offboard link - [https://github.com/mhl787156/starling_simple_offboard](https://github.com/mhl787156/starling_simple_offboard) From e69e50d2f3c85ad00bc329a182e12c3ee5dc96d8 Mon Sep 17 00:00:00 2001 From: Mickey Li Date: Tue, 18 Jan 2022 12:25:33 +0000 Subject: [PATCH 03/36] Modified dockerfile sed to match mavlink start --- simulator/base/px4/sitl.Dockerfile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/simulator/base/px4/sitl.Dockerfile b/simulator/base/px4/sitl.Dockerfile index db9df48..fa905b1 100644 --- a/simulator/base/px4/sitl.Dockerfile +++ b/simulator/base/px4/sitl.Dockerfile @@ -28,7 +28,9 @@ ENV SIM_WD /sim_wd RUN sed -i 's/simulator start -c $simulator_tcp_port/simulator start -t $PX4_SIM_IP $simulator_tcp_port/' /src/PX4-Autopilot/ROMFS/px4fmu_common/init.d-posix/px4-rc.simulator # Modify startup script to add partner IP for offboard script -RUN sed -i 's/\(mavlink start -x -u $udp_offboard_port_local -r 4000000 -m onboard -o $udp_offboard_port_remote\)/\1 -t $PX4_OFFBOARD_IP -p/' /src/PX4-Autopilot/ROMFS/px4fmu_common/init.d-posix/px4-rc.mavlink +RUN sed -i 's/\(mavlink start -x -u $udp_offboard_port_local -r 4000000 -f -m onboard -o $udp_offboard_port_remote\)/\1 -t $PX4_OFFBOARD_IP -p/' /src/PX4-Autopilot/ROMFS/px4fmu_common/init.d-posix/px4-rc.mavlink +RUN sed -i '/udp_onboard_payload_port_local/d' /src/PX4-Autopilot/ROMFS/px4fmu_common/init.d-posix/px4-rc.mavlink +RUN sed -i '/udp_onboard_gimbal_port_local/d' /src/PX4-Autopilot/ROMFS/px4fmu_common/init.d-posix/px4-rc.mavlink # Modify startup script to enable mavlink broadcasting RUN sed -i '/param set IMU_INTEG_RATE 250/a param set MAV_${px4_instance}_BROADCAST 1' /src/PX4-Autopilot/ROMFS/px4fmu_common/init.d-posix/rcS From a0d1be376cf904d4aa113ba227e8b313a39ebfe7 Mon Sep 17 00:00:00 2001 From: Mickey Li Date: Thu, 20 Jan 2022 11:26:24 +0000 Subject: [PATCH 04/36] Created custom launch file to avoid multiple rosapi warnings --- system/rosbridge-suite/Dockerfile | 6 +- .../rosbridge_websocket_launch.xml | 66 +++++++++++++++++++ 2 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 system/rosbridge-suite/rosbridge_websocket_launch.xml diff --git a/system/rosbridge-suite/Dockerfile b/system/rosbridge-suite/Dockerfile index 05c866b..33529b5 100644 --- a/system/rosbridge-suite/Dockerfile +++ b/system/rosbridge-suite/Dockerfile @@ -6,7 +6,7 @@ RUN apt-get update \ curl \ ros-foxy-rosbridge-suite \ ros-foxy-mavros-msgs \ - ros-foxy-gazebo-msgs \ + ros-foxy-gazebo-msgs \ && rm -rf /var/lib/apt/lists/* @@ -14,10 +14,12 @@ RUN apt-get update \ COPY fastrtps_profiles.xml /ros_ws/ ENV FASTRTPS_DEFAULT_PROFILES_FILE /ros_ws/fastrtps_profiles.xml +COPY rosbridge_websocket_launch.xml /ros_ws/ + ENV ROSBRIDGE_PORT 9090 # Expose websocket server EXPOSE ${ROSBRIDGE_PORT} # Set initial command -CMD [ "sh", "-c", "ros2 launch rosbridge_server rosbridge_websocket_launch.xml port:=${ROSBRIDGE_PORT}" ] \ No newline at end of file +CMD ["sh", "-c", "ros2 launch /ros_ws/rosbridge_websocket_launch.xml port:=${ROSBRIDGE_PORT}"] \ No newline at end of file diff --git a/system/rosbridge-suite/rosbridge_websocket_launch.xml b/system/rosbridge-suite/rosbridge_websocket_launch.xml new file mode 100644 index 0000000..6c70038 --- /dev/null +++ b/system/rosbridge-suite/rosbridge_websocket_launch.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From b595a930eb4b28069ee1c67fe78c3dad494d7558 Mon Sep 17 00:00:00 2001 From: Mickey Li Date: Thu, 20 Jan 2022 18:49:21 +0000 Subject: [PATCH 05/36] Added remapping of /clock to /gazebo_clock and back in gazebo and mavros to allow for foxglove plotting --- simulator/base/core/Dockerfile | 4 ++-- simulator/base/core/gzweb.launch.xml | 5 ++++- system/mavros/mavros.launch | 4 ++++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/simulator/base/core/Dockerfile b/simulator/base/core/Dockerfile index 183ab2f..1a0d98e 100644 --- a/simulator/base/core/Dockerfile +++ b/simulator/base/core/Dockerfile @@ -42,7 +42,7 @@ RUN echo "source /usr/share/gazebo/setup.sh" >> /ros.env RUN apt-get update \ && apt-get install --no-install-recommends -y \ ros-foxy-gazebo-ros \ - ros-foxy-gazebo-ros-pkgs \ + ros-foxy-gazebo-ros-pkgs \ ros-foxy-xacro \ ros-foxy-image-transport-plugins \ && rm -rf /var/lib/apt/lists/* @@ -96,7 +96,7 @@ COPY gzweb_entrypoint.sh / # Add URDF conversion tools COPY tools /ros_ws/tools -# Add custom ROS DDS configuration (force UDP always) +# Add custom ROS DDS configuration (force UDP always) COPY fastrtps_profiles.xml /ros_ws/ ENV FASTRTPS_DEFAULT_PROFILES_FILE /ros_ws/fastrtps_profiles.xml diff --git a/simulator/base/core/gzweb.launch.xml b/simulator/base/core/gzweb.launch.xml index 48e3b90..fa048ea 100644 --- a/simulator/base/core/gzweb.launch.xml +++ b/simulator/base/core/gzweb.launch.xml @@ -1,8 +1,11 @@ - + + + + diff --git a/system/mavros/mavros.launch b/system/mavros/mavros.launch index cc38f41..c9a0033 100644 --- a/system/mavros/mavros.launch +++ b/system/mavros/mavros.launch @@ -27,6 +27,10 @@ + + + + From bd3e6bb223d26b38c15e4111021f2d1729eb62ca Mon Sep 17 00:00:00 2001 From: Mickey Li Date: Fri, 21 Jan 2022 10:12:53 +0000 Subject: [PATCH 06/36] Added support for building external messages at runtime for rosbridge --- system/rosbridge-suite/Dockerfile | 5 ++++- system/rosbridge-suite/ros_entrypoint.sh | 7 +++++++ system/rosbridge-suite/run.sh | 16 ++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100755 system/rosbridge-suite/ros_entrypoint.sh create mode 100755 system/rosbridge-suite/run.sh diff --git a/system/rosbridge-suite/Dockerfile b/system/rosbridge-suite/Dockerfile index 33529b5..e4e82b0 100644 --- a/system/rosbridge-suite/Dockerfile +++ b/system/rosbridge-suite/Dockerfile @@ -15,11 +15,14 @@ COPY fastrtps_profiles.xml /ros_ws/ ENV FASTRTPS_DEFAULT_PROFILES_FILE /ros_ws/fastrtps_profiles.xml COPY rosbridge_websocket_launch.xml /ros_ws/ +COPY ros_entrypoint.sh / +COPY run.sh / ENV ROSBRIDGE_PORT 9090 +ENV MSGS_WS "/msgs_ws" # Expose websocket server EXPOSE ${ROSBRIDGE_PORT} # Set initial command -CMD ["sh", "-c", "ros2 launch /ros_ws/rosbridge_websocket_launch.xml port:=${ROSBRIDGE_PORT}"] \ No newline at end of file +CMD ["./run.sh"] \ No newline at end of file diff --git a/system/rosbridge-suite/ros_entrypoint.sh b/system/rosbridge-suite/ros_entrypoint.sh new file mode 100755 index 0000000..09a718a --- /dev/null +++ b/system/rosbridge-suite/ros_entrypoint.sh @@ -0,0 +1,7 @@ +#!/bin/bash +set -e + +# setup ros2 environment +source "/opt/ros/$ROS_DISTRO/setup.bash" + +exec "$@" \ No newline at end of file diff --git a/system/rosbridge-suite/run.sh b/system/rosbridge-suite/run.sh new file mode 100755 index 0000000..75fd5fb --- /dev/null +++ b/system/rosbridge-suite/run.sh @@ -0,0 +1,16 @@ +#!/bin/bash +set -e + +# Check for mounted msgs +if [ ! -d "${MSGS_WS}" ]; then + echo "No external messages mounted at ${MSGS_WS}" +elif [ -d "${MSGS_WS}/install" ]; then + echo "Messages already built at ${MSGS_WS}/install, not building" +else + echo "External messages detected at ${MSGS_WS}, building..." + colcon build --base-paths "${MSGS_WS}" --build-base "${MSGS_WS}/build" --install-base "${MSGS_WS}/install" + rm -r "${MSGS_WS}/build" + source "${MSGS_WS}/install/setup.bash" +fi + +ros2 launch /ros_ws/rosbridge_websocket_launch.xml port:=${ROSBRIDGE_PORT} \ No newline at end of file From 59cc4ef2f554ad988780a957f7d4ce0f331531c0 Mon Sep 17 00:00:00 2001 From: Mickey Li Date: Fri, 21 Jan 2022 10:17:57 +0000 Subject: [PATCH 07/36] Updated README.md with external messages instructions --- system/rosbridge-suite/README.md | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/system/rosbridge-suite/README.md b/system/rosbridge-suite/README.md index a5753f3..87a9ae1 100644 --- a/system/rosbridge-suite/README.md +++ b/system/rosbridge-suite/README.md @@ -2,7 +2,7 @@ Builds a container that serves a ros2 web-bridge using the [rosbridge-suite](https://github.com/RobotWebTools/rosbridge_suite) -The rosbridge is known as a 'json' bridge as it converts topics into json to be sent via websocket. +The rosbridge is known as a 'json' bridge as it converts topics into json to be sent via websocket. ## Installation @@ -31,12 +31,38 @@ The port can be specified by passing the environment variable `ROSBRIDGE_PORT`, docker run -it --rm --net=host -e ROSBRIDGE_PORT= -p : uobflightlabstarling/rosbridge-suite ``` +## External ROS messages + +Core rosbridge suite currently only supports `mavros_msgs` and `gazebo_msgs` ontop of the standard ROS2 messages. + +However, external messages can be built at runtime by bind mounting your messages into the source folder of the messages workspace defined by environment variable `MSGS_WS`. + +For example if you had local msgs `controller_msgs`: +```console +docker run -it --rm --network px4_default -p 9090:9090 -v "$(pwd)"/controller_msgs:/msgs_ws/src/controller_msgs -e MSGS_WS=/msgs_ws uobflightlabstarling/rosbridge-suite:latest +``` + +or equivalently in a docker-compose file: +```yaml +services: + rosbridge-suite: + image: uobflightlabstarling/rosbridge-suite:latest + environment: + - "MSGS_WS=/msgs_ws" + volumes: + - ./controller_msgs:/msgs_ws/src/controller_msgs + ports: + - "9090:9090" +``` + +Note that this would work best for small numbers of extra message topics. If you have a large number consider writing your own dockerfile which uses this one. + ## Application -This can be used with any application which supports the ros2 web bridge suite. +This can be used with any application which supports the ros2 web bridge suite. An example ui application is given in [StarlingUAS/starling_ui_example](https://github.com/StarlingUAS/starling_ui_example) A more complex system would be the dashboard created by [foxglove studios](https://foxglove.dev/): https://studio.foxglove.dev/? -> Note: Starling doesn't quite support foxglove just yet, there is a known bug here: https://github.com/foxglove/studio/issues/2035. If this is fixed it will be compatible. \ No newline at end of file +> Note: Starling doesn't quite support foxglove just yet, there is a known bug here: https://github.com/foxglove/studio/issues/2035. If this is fixed it will be compatible. \ No newline at end of file From 7617f12b1ee7c2a84f7cc12599f1aded2a1af2f3 Mon Sep 17 00:00:00 2001 From: Mickey Li Date: Sun, 23 Jan 2022 23:47:24 +0000 Subject: [PATCH 08/36] Created temporary 1.0.7 dockerfile for foxglove compatibility --- system/Makefile | 2 +- system/rosbridge-suite/Dockerfile | 2 +- system/rosbridge-suite/Dockerfile_old | 17 +++++++ .../rosbridge-suite/Dockerfile_rosbridge1.0.7 | 36 +++++++++++++ .../rosbridge_websocket_launch.xml | 51 ++++++------------- system/rosbridge-suite/run.sh | 8 +++ 6 files changed, 78 insertions(+), 38 deletions(-) create mode 100644 system/rosbridge-suite/Dockerfile_old create mode 100644 system/rosbridge-suite/Dockerfile_rosbridge1.0.7 diff --git a/system/Makefile b/system/Makefile index 0ec0622..844e1ab 100644 --- a/system/Makefile +++ b/system/Makefile @@ -11,7 +11,7 @@ controller-base: mavros: $(BAKE) starling-mavros -mavros_all_arch: +mavros_all_arch: cd $(MAKEFILE_DIR)/.. && docker buildx bake --builder default --load -f $(BAKE_SCRIPT) rosbridge-suite: diff --git a/system/rosbridge-suite/Dockerfile b/system/rosbridge-suite/Dockerfile index e4e82b0..77d854d 100644 --- a/system/rosbridge-suite/Dockerfile +++ b/system/rosbridge-suite/Dockerfile @@ -4,7 +4,7 @@ FROM ros:foxy-ros-base-focal RUN apt-get update \ && apt-get install -y --no-install-recommends \ curl \ - ros-foxy-rosbridge-suite \ + ros-foxy-rosbridge-suit \ ros-foxy-mavros-msgs \ ros-foxy-gazebo-msgs \ && rm -rf /var/lib/apt/lists/* diff --git a/system/rosbridge-suite/Dockerfile_old b/system/rosbridge-suite/Dockerfile_old new file mode 100644 index 0000000..570f1d2 --- /dev/null +++ b/system/rosbridge-suite/Dockerfile_old @@ -0,0 +1,17 @@ +FROM ros:foxy-ros-base-focal + +# Install rosbridge suite and associated messages +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + curl \ + ros-foxy-rosbridge-suite \ + ros-foxy-mavros-msgs \ + ros-foxy-gazebo-msgs \ + && rm -rf /var/lib/apt/lists/* +ENV ROSBRIDGE_PORT 9090 + +# Expose websocket server +EXPOSE ${ROSBRIDGE_PORT} + +# Set initial command +CMD [ "sh", "-c", "ros2 launch rosbridge_server rosbridge_websocket_launch.xml port:=${ROSBRIDGE_PORT}" ] \ No newline at end of file diff --git a/system/rosbridge-suite/Dockerfile_rosbridge1.0.7 b/system/rosbridge-suite/Dockerfile_rosbridge1.0.7 new file mode 100644 index 0000000..b384f41 --- /dev/null +++ b/system/rosbridge-suite/Dockerfile_rosbridge1.0.7 @@ -0,0 +1,36 @@ +FROM ros:foxy-ros-base-focal + +# Build v 1.0.7 rosbridge +RUN git clone -b 1.0.7 https://github.com/RobotWebTools/rosbridge_suite.git /ros_ws/src/rosbridge_suite + +# Install rosbridge suite and associated messages +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + curl \ + ros-foxy-mavros-msgs \ + ros-foxy-gazebo-msgs \ + && rosdep install --from-paths /ros_ws/src -y\ + && rm -rf /var/lib/apt/lists/* + +RUN . /opt/ros/${ROS_DISTRO}/setup.sh \ + && cd /ros_ws \ + && export CMAKE_PREFIX_PATH=$AMENT_PREFIX_PATH:$CMAKE_PREFIX_PATH \ + && colcon build --cmake-force-configure \ + && rm -r build + +# Add custom ROS DDS configuration (force UDP always) +COPY fastrtps_profiles.xml /ros_ws/ +ENV FASTRTPS_DEFAULT_PROFILES_FILE /ros_ws/fastrtps_profiles.xml + +COPY rosbridge_websocket_launch.xml /ros_ws/ +COPY ros_entrypoint.sh / +COPY run.sh / + +ENV ROSBRIDGE_PORT 9090 +ENV MSGS_WS "/msgs_ws" + +# Expose websocket server +EXPOSE ${ROSBRIDGE_PORT} + +# Set initial command +CMD ["./run.sh"] \ No newline at end of file diff --git a/system/rosbridge-suite/rosbridge_websocket_launch.xml b/system/rosbridge-suite/rosbridge_websocket_launch.xml index 6c70038..0275471 100644 --- a/system/rosbridge-suite/rosbridge_websocket_launch.xml +++ b/system/rosbridge-suite/rosbridge_websocket_launch.xml @@ -1,9 +1,6 @@ - - - @@ -21,42 +18,24 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + - - - + + + + + + - - - diff --git a/system/rosbridge-suite/run.sh b/system/rosbridge-suite/run.sh index 75fd5fb..179b4fe 100755 --- a/system/rosbridge-suite/run.sh +++ b/system/rosbridge-suite/run.sh @@ -6,6 +6,7 @@ if [ ! -d "${MSGS_WS}" ]; then echo "No external messages mounted at ${MSGS_WS}" elif [ -d "${MSGS_WS}/install" ]; then echo "Messages already built at ${MSGS_WS}/install, not building" + source "${MSGS_WS}/install/setup.bash" else echo "External messages detected at ${MSGS_WS}, building..." colcon build --base-paths "${MSGS_WS}" --build-base "${MSGS_WS}/build" --install-base "${MSGS_WS}/install" @@ -13,4 +14,11 @@ else source "${MSGS_WS}/install/setup.bash" fi +# Check if extra /install in ros_ws, if so source it. +if [ -d "/ros_ws/install" ]; then + echo "Install folder exists in ros_ws, sourcing setup" + source "/ros_ws/install/setup.bash" +fi + +ros2 pkg list ros2 launch /ros_ws/rosbridge_websocket_launch.xml port:=${ROSBRIDGE_PORT} \ No newline at end of file From 6aed9b4308b1fad42f1af40100458a4d9246c250 Mon Sep 17 00:00:00 2001 From: Mickey Li Date: Sun, 23 Jan 2022 23:51:01 +0000 Subject: [PATCH 09/36] Fixed typo in dockerfile --- system/rosbridge-suite/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/rosbridge-suite/Dockerfile b/system/rosbridge-suite/Dockerfile index 77d854d..e4e82b0 100644 --- a/system/rosbridge-suite/Dockerfile +++ b/system/rosbridge-suite/Dockerfile @@ -4,7 +4,7 @@ FROM ros:foxy-ros-base-focal RUN apt-get update \ && apt-get install -y --no-install-recommends \ curl \ - ros-foxy-rosbridge-suit \ + ros-foxy-rosbridge-suite \ ros-foxy-mavros-msgs \ ros-foxy-gazebo-msgs \ && rm -rf /var/lib/apt/lists/* From d7261534fcf3affc7b0684dbcaef230d6ce9d989 Mon Sep 17 00:00:00 2001 From: Mickey Li Date: Wed, 26 Jan 2022 10:36:35 +0000 Subject: [PATCH 10/36] Main dockerfile now builds rosbridge_suite 1.1.2 --- system/rosbridge-suite/Dockerfile | 10 +++++++++- system/rosbridge-suite/run.sh | 4 ++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/system/rosbridge-suite/Dockerfile b/system/rosbridge-suite/Dockerfile index e4e82b0..541eec0 100644 --- a/system/rosbridge-suite/Dockerfile +++ b/system/rosbridge-suite/Dockerfile @@ -1,14 +1,22 @@ FROM ros:foxy-ros-base-focal +# Build v 1.1.2 rosbridge +RUN git clone -b 1.1.2 https://github.com/RobotWebTools/rosbridge_suite.git /ros_ws/src/rosbridge_suite + # Install rosbridge suite and associated messages RUN apt-get update \ && apt-get install -y --no-install-recommends \ curl \ - ros-foxy-rosbridge-suite \ ros-foxy-mavros-msgs \ ros-foxy-gazebo-msgs \ + && rosdep install --from-paths /ros_ws/src -y\ && rm -rf /var/lib/apt/lists/* +RUN . /opt/ros/${ROS_DISTRO}/setup.sh \ + && cd /ros_ws \ + && export CMAKE_PREFIX_PATH=$AMENT_PREFIX_PATH:$CMAKE_PREFIX_PATH \ + && colcon build --cmake-force-configure \ + && rm -r build # Add custom ROS DDS configuration (force UDP always) COPY fastrtps_profiles.xml /ros_ws/ diff --git a/system/rosbridge-suite/run.sh b/system/rosbridge-suite/run.sh index 179b4fe..1ba31c5 100755 --- a/system/rosbridge-suite/run.sh +++ b/system/rosbridge-suite/run.sh @@ -5,12 +5,13 @@ set -e if [ ! -d "${MSGS_WS}" ]; then echo "No external messages mounted at ${MSGS_WS}" elif [ -d "${MSGS_WS}/install" ]; then - echo "Messages already built at ${MSGS_WS}/install, not building" + echo "Messages already built at ${MSGS_WS}/install, not building. Sourcing ${MSGS_WS}/install/setup.bash" source "${MSGS_WS}/install/setup.bash" else echo "External messages detected at ${MSGS_WS}, building..." colcon build --base-paths "${MSGS_WS}" --build-base "${MSGS_WS}/build" --install-base "${MSGS_WS}/install" rm -r "${MSGS_WS}/build" + echi "Sourcing ${MSGS_WS}/install/setup.bash" source "${MSGS_WS}/install/setup.bash" fi @@ -20,5 +21,4 @@ if [ -d "/ros_ws/install" ]; then source "/ros_ws/install/setup.bash" fi -ros2 pkg list ros2 launch /ros_ws/rosbridge_websocket_launch.xml port:=${ROSBRIDGE_PORT} \ No newline at end of file From c8a69c951d84b3450718a6f7c08fb37e41607e5b Mon Sep 17 00:00:00 2001 From: Mickey Li Date: Thu, 10 Feb 2022 11:28:44 +0000 Subject: [PATCH 11/36] Fixed breaking typo in run script --- system/rosbridge-suite/run.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/rosbridge-suite/run.sh b/system/rosbridge-suite/run.sh index 1ba31c5..ffd4296 100755 --- a/system/rosbridge-suite/run.sh +++ b/system/rosbridge-suite/run.sh @@ -11,7 +11,7 @@ else echo "External messages detected at ${MSGS_WS}, building..." colcon build --base-paths "${MSGS_WS}" --build-base "${MSGS_WS}/build" --install-base "${MSGS_WS}/install" rm -r "${MSGS_WS}/build" - echi "Sourcing ${MSGS_WS}/install/setup.bash" + echo "Sourcing ${MSGS_WS}/install/setup.bash" source "${MSGS_WS}/install/setup.bash" fi From 0cb204c3bb13774b1a7e98fad0a93835f65a5d4c Mon Sep 17 00:00:00 2001 From: mickey li Date: Fri, 11 Feb 2022 15:21:26 +0000 Subject: [PATCH 12/36] Removed /clock remapping in sitl and mavros and enabled USE_SIM_TIME parameter in controller base --- simulator/base/core/gzweb.launch.xml | 3 --- system/controller-base/Dockerfile | 2 ++ system/mavros/mavros.launch | 4 ---- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/simulator/base/core/gzweb.launch.xml b/simulator/base/core/gzweb.launch.xml index fa048ea..ead88a9 100644 --- a/simulator/base/core/gzweb.launch.xml +++ b/simulator/base/core/gzweb.launch.xml @@ -3,9 +3,6 @@ - - - diff --git a/system/controller-base/Dockerfile b/system/controller-base/Dockerfile index 3264cc9..01f85a4 100644 --- a/system/controller-base/Dockerfile +++ b/system/controller-base/Dockerfile @@ -24,4 +24,6 @@ COPY base_env_setup.sh /ros.env.d/controller_base/setup.bash COPY fastrtps_profiles.xml /ros_ws/ ENV FASTRTPS_DEFAULT_PROFILES_FILE /ros_ws/fastrtps_profiles.xml +ENV USE_SIMULATED_TIME false + WORKDIR /ros_ws diff --git a/system/mavros/mavros.launch b/system/mavros/mavros.launch index c9a0033..cc38f41 100644 --- a/system/mavros/mavros.launch +++ b/system/mavros/mavros.launch @@ -27,10 +27,6 @@ - - - - From b1159c987c92895dc0cb6a130cd430d646d0c4a3 Mon Sep 17 00:00:00 2001 From: mickey li Date: Fri, 11 Feb 2022 16:50:47 +0000 Subject: [PATCH 13/36] Added docs for writing a controller including launch file use-sim-time --- docs/guide/writing-a-controller.md | 198 +++++++++++++++++++++++++++++ mkdocs.yml | 1 + 2 files changed, 199 insertions(+) create mode 100644 docs/guide/writing-a-controller.md diff --git a/docs/guide/writing-a-controller.md b/docs/guide/writing-a-controller.md new file mode 100644 index 0000000..d6f57ce --- /dev/null +++ b/docs/guide/writing-a-controller.md @@ -0,0 +1,198 @@ +# Creating your own controller + +This documentation describes creating your own controller which is compatible with the Starling ecosystem. + +TODO: Complete this docs page + +[TOC] + +## Overview + +## Implementation + + +### Launch Files + +The controller itself can be run using `ros2 run`, but if you might want to run multiple nodes together, or provide some more complex functionality, it is recommended that ros2 nodes be run using launch files. ROS2 launch files can be written as xml or python programmes. Here we will explain using xml which imo is clearer, but the python is much more flexible. + +#### Basic launch file: + +Here is an example of a launch file which should be saved as `controller.launch.xml` in a `launch` folder in the root of the repository: +```xml + + + + + + + + + + + + + + + + + +``` + +This launch file takes two arguments which can be set by the user. Importantly the `vehicle_namespace` is set by the environment varible `VEHICLE_NAMESPACE` which is populated by an initialsation script in the controller base. + +Then to ensure that the controller is an `onboard` controller, so we have one controller running for one vehicle, we create a group and set the namespace to vehicle namespace. As long as the topic names used inside of the controller are not absolute (i.e. no leading `/` - `mytopic` vs `/mytopic`), the nodes topics will get mapped to `vehicle_1/mytopic`. + +Two nodes are started inside this node group. The first one requires a number of parameters which can need to use the vehicle name. The second one doesnt need any. + +#### Enabling running In simulation +The base controller has a USE_SIMULATED_TIME environment variable. Any node in the controller launchfile should include the following line if the controller requires use of time: + +```xml + + + ... + +``` + +So when you want to run the controller in simulation, you can simply set the `USE_SIMULATED_TIME` to true. For example `docker run --it --rm -e USE_SIMULATED_TIME mycontroller`. + +### Build and Bake Files + +A bake file is a configuration file used to specify how to build a particular dockerfile. We use it in particular because it allows us to build cross-platfrom executables in the case we want to run our containers on a drone. A drone running a raspberry pi runs the `arm64` architecture, vs your own machine which most likely runs the `amd64` architecture. + +### Basic Bake file: +TODO: Explain what this means + +``` +variable "BAKE_VERSION" { + default = "latest" +} + +variable "BAKE_REGISTRY" { + default = "" +} + +variable "BAKE_RELEASENAME" { + default = "" +} + +variable "BAKE_CACHEFROM_REGISTRY" { + default = "" +} + +variable "BAKE_CACHETO_REGISTRY" { + default = "" +} + +variable "BAKE_CACHEFROM_NAME" { + default = "" +} + +variable "BAKE_CACHETO_NAME" { + default = "" +} + +/* + * Groups for target ordering + */ +group "stage1" { + targets = ["4pl"] +} + +// This target depends on starling-controller-base +target "4pl-controller" { + context = "." + args = { + "VERSION": "${BAKE_VERSION}", + "REGISTRY": "${BAKE_REGISTRY}" + } + tags = [ + "${BAKE_REGISTRY}uobflightlabstarling/4pl-controller:${BAKE_VERSION}", + notequal("",BAKE_RELEASENAME) ? "${BAKE_REGISTRY}uobflightlabstarling/4pl-controller:${BAKE_RELEASENAME}": "", + ] + platforms = ["linux/amd64", "linux/arm64"] + cache-to = [ notequal("",BAKE_CACHETO_NAME) ? "${BAKE_CACHETO_REGISTRY}uobflightlabstarling/4pl-controller:${BAKE_CACHETO_NAME}" : "" ] + cache-from = [ notequal("",BAKE_CACHEFROM_NAME) ? "${BAKE_CACHEFROM_REGISTRY}uobflightlabstarling/4pl-controller:${BAKE_CACHEFROM_NAME}" : "" ] +} +``` + +### Make Files + +It is recommended that make (or nmake for windows) be used as a tool for building your controller. Make is a useful tool as it allows the running of any number of specific commands (similar to a bash file but with arguments dealt with for you). It is recommended that the makefile look something like the following, and be put in the root of the project folder: + +```make +MAKEFILE_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) +BAKE_SCRIPT:=$(MAKEFILE_DIR)/docker-bake.hcl +BUILDX_HOST_PLATFORM:=$(shell docker buildx inspect default | sed -nE 's/^Platforms: ([^,]*),.*$$/\1/p') +BAKE:=docker buildx bake --builder default --load --set *.platform=$(BUILDX_HOST_PLATFORM) -f $(BAKE_SCRIPT) + +CONTROLLER_NAME=controller +NETWORK?=4pl-ros2-controller_default +ENV?= +BUILD_ARGS?= + +all: build + +build: + $(BAKE) $(BUILD_ARGS) $(CONTROLLER) + +# This mybuilder needs the following lines to be run: +# docker run --rm --privileged multiarch/qemu-user-static --reset -p yes +# docker buildx create --name mybuilder +# docker buildx use mybuilder +# docker buildx inspect --bootstrap +local-build-push: + docker buildx bake --builder mybuilder -f $(BAKE_SCRIPT) --push $(CONTROLLER_NAME) + +run: build + docker run -it --rm --net=$(NETWORK) $(ENV) -e USE_SIMULATED_TIME=true $(CONTROLLER_NAME):latest + +run_bash: build + docker run -it --rm --net=$(NETWORK) -e USE_SIMULATED_TIME=true $(CONTROLLER_NAME):latest bash + +.PHONY: all build local-build-push run run_bash +``` +Breaking down the make file, we have a number of commands. + +```make +MAKEFILE_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) +BAKE_SCRIPT:=$(MAKEFILE_DIR)/docker-bake.hcl +BUILDX_HOST_PLATFORM:=$(shell docker buildx inspect default | sed -nE 's/^Platforms: ([^,]*),.*$$/\1/p') +BAKE:=docker buildx bake --builder default --load --set *.platform=$(BUILDX_HOST_PLATFORM) -f $(BAKE_SCRIPT) +``` +The first 4 lines define a number of useful variables including where the current directory is, the location of your bake script, your system's curent platform to build for and the final buildx bake command for building your controller + +```make +CONTROLLER_NAME=controller +NETWORK?=controller_default +ENV?= +BUILD_ARGS?= +``` +Then we define some useful variables to us, including the name of the controller and the local network (set to the default of foldername_default). Note that ENV and BUILD_ARGS have been set to a default of empty string on purporse. Then when running the file these can be specified e.g. `make ENV=-e HELLO=mynewvariable`. + +```make +all: build + +build: + $(BAKE) $(BUILD_ARGS) $(CONTROLLER) + +# This mybuilder needs the following lines to be run: +# docker run --rm --privileged multiarch/qemu-user-static --reset -p yes +# docker buildx create --name mybuilder +# docker buildx use mybuilder +# docker buildx inspect --bootstrap +local-build-push: + docker buildx bake --builder mybuilder -f $(BAKE_SCRIPT) --push $(CONTROLLER_NAME) + +run: build + docker run -it --rm --net=$(NETWORK) $(ENV) -e USE_SIMULATED_TIME=true $(CONTROLLER_NAME):latest + +run_bash: build + docker run -it --rm --net=$(NETWORK) -e USE_SIMULATED_TIME=true $(CONTROLLER_NAME):latest bash +``` +Then we specify the commands. Here we have specified 5 commands: `all`, `build`, `local-build-push`,`run` and `run_bash`. The important one is `build` which can be run using `make build` which builds the container. A useful is `run` which will both build and run the newly built docker container. `run_bash` is the same as `run` except it puts you into the bash shell of the container instead of directly running. `local-build-push` is a helper which will help you push your container to dockerhub if you so need to. + +```make +.PHONY: all build local-build-push run run_bash +``` +This final line is makefile syntax just in case you have any local files which are accidentally named exactly the same as one of the named commands. \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 76c2d66..3632ad2 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -37,6 +37,7 @@ nav: - "Multiple Drones Simulation on Local Cluster": "guide/multiple-drone-local-machine.md" - "Multiple Drones on Hardware": "guide/multiple-drone-drones.md" - "Additional Guides": + - "Developing a controller": 'guide/writing-a-controller.md' - "Development Guide": 'guide/development.md' - "Understanding Docker": 'details/docker.md' - "Understanding Kubernetes": "details/kubernetes.md" From 917581fd798b0fbb43c66006a1adc293498c5973 Mon Sep 17 00:00:00 2001 From: mickey li Date: Thu, 3 Mar 2022 12:51:03 +0000 Subject: [PATCH 14/36] Added rangefinder into iris-ap model --- .../iris-ap/models/iris_ap/model.sdf.xacro | 61 +++++++++++++++++++ .../iris-ap/models/iris_ap/setup.bash | 1 + 2 files changed, 62 insertions(+) diff --git a/simulator/vehicles/iris-ap/models/iris_ap/model.sdf.xacro b/simulator/vehicles/iris-ap/models/iris_ap/model.sdf.xacro index 9e7b086..9630954 100644 --- a/simulator/vehicles/iris-ap/models/iris_ap/model.sdf.xacro +++ b/simulator/vehicles/iris-ap/models/iris_ap/model.sdf.xacro @@ -2,6 +2,7 @@ + @@ -27,6 +28,66 @@ + + + + + + + 10 + 1 + -0.087 + 0.087 + + + 10 + 1 + -0.087 + 0.087 + + + + 0.10 + 10.0 + 0.01 + + + + gaussian + 0.0 + 0.01 + + + + 30 + + + + + ${ros_namespace} + + + sensor_msgs/Range + + infrared + + + + + + iris::base_link + hokuyo_link + + + 0 + 0 + + -1 0 1 + 0 0.0 0 + true + + + 0.3 diff --git a/simulator/vehicles/iris-ap/models/iris_ap/setup.bash b/simulator/vehicles/iris-ap/models/iris_ap/setup.bash index 1a26cfe..0daf139 100755 --- a/simulator/vehicles/iris-ap/models/iris_ap/setup.bash +++ b/simulator/vehicles/iris-ap/models/iris_ap/setup.bash @@ -5,6 +5,7 @@ XACRO_PATH=${SCRIPT_DIR}/model.sdf.xacro MODEL_PATH=/ardupilot_gazebo/models/iris_with_ardupilot/model.sdf xacro ${XACRO_PATH} \ + ros_namespace:=${VEHICLE_NAMESPACE} \ fdm_addr:=${AP_SITL_ADDRESS} \ listen_addr:=0.0.0.0 \ -o ${MODEL_PATH} From 7ec4e691ed00190bc7d3ed056fbd11a8c294c632 Mon Sep 17 00:00:00 2001 From: mickey li Date: Fri, 4 Mar 2022 17:18:49 +0000 Subject: [PATCH 15/36] Continued trying to adjust the rangefinder so pointing in the correct direction --- .../iris-ap/models/iris_ap/model.sdf.xacro | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/simulator/vehicles/iris-ap/models/iris_ap/model.sdf.xacro b/simulator/vehicles/iris-ap/models/iris_ap/model.sdf.xacro index 9630954..f499a25 100644 --- a/simulator/vehicles/iris-ap/models/iris_ap/model.sdf.xacro +++ b/simulator/vehicles/iris-ap/models/iris_ap/model.sdf.xacro @@ -29,6 +29,15 @@ + -0.5 0 -1 0 -1.57075 0 + @@ -74,10 +83,10 @@ - + iris::base_link hokuyo_link - + From 9c7dfe7cf898c60e888fbf9d15997620517e2219 Mon Sep 17 00:00:00 2001 From: Mickey Li Date: Sat, 5 Mar 2022 12:25:00 +0000 Subject: [PATCH 16/36] Removed inertia and mass from sensor and set min/max resolution --- .../iris-ap/models/iris_ap/model.sdf.xacro | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/simulator/vehicles/iris-ap/models/iris_ap/model.sdf.xacro b/simulator/vehicles/iris-ap/models/iris_ap/model.sdf.xacro index f499a25..01ab93c 100644 --- a/simulator/vehicles/iris-ap/models/iris_ap/model.sdf.xacro +++ b/simulator/vehicles/iris-ap/models/iris_ap/model.sdf.xacro @@ -29,15 +29,19 @@ - -0.5 0 -1 0 -1.57075 0 - + 0 0 0 0 1.57075 0 + + 0.016 + + 0.0001 + 0 + 0 + 0.0001 + 0 + 0.0001 + + + @@ -56,8 +60,8 @@ - 0.10 - 10.0 + 0.01 + 20.0 0.01 From 56862d3110ddbfc1307a4d4db8eb50b8950dae3c Mon Sep 17 00:00:00 2001 From: mickey li Date: Mon, 7 Mar 2022 12:29:47 +0000 Subject: [PATCH 17/36] Added variable delay to mavros bridge launch --- system/mavros/mavros_bridge.launch.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/system/mavros/mavros_bridge.launch.xml b/system/mavros/mavros_bridge.launch.xml index 0c6fee2..526a416 100644 --- a/system/mavros/mavros_bridge.launch.xml +++ b/system/mavros/mavros_bridge.launch.xml @@ -9,6 +9,7 @@ + + launch-prefix="bash -c 'sleep $(var bridge_launch_delay); $0 $@' "> \ No newline at end of file From 6f61529f33091efe4f9a0430a223a611f66680c5 Mon Sep 17 00:00:00 2001 From: Mickey Li Date: Wed, 9 Mar 2022 09:23:36 +0000 Subject: [PATCH 18/36] Updated mavros docs to include new environment variable --- docs/docker-images/mavros.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/docker-images/mavros.md b/docs/docker-images/mavros.md index bf6f25b..406e9d8 100644 --- a/docs/docker-images/mavros.md +++ b/docs/docker-images/mavros.md @@ -43,6 +43,7 @@ Name | Default Value | Description `MAVROS_MOD_CONFIG_PATH` | "/mavros_config_mod.yaml" | Path for modified MAVROS config to be written to `BRIDGE_CONFIG_PATH` | "/bridge_config.yaml" | Path for initial bridge config file `BRIDGE_MOD_CONFIG_PATH` | "/bridge_config_mod.yaml" | Path for modified bridge config to be written to +`BRIDGE_LAUNCH_DELAY` | 2.0 | Time delay in seconds between starting ROS1 mavros and the ROS1-2 bridge. Increasing this value reduces the chance of a race condition causing mavros failure due to the bridge being unable to read ros1 parameters. See [this issue](https://github.com/StarlingUAS/ProjectStarling/issues/124) for more details. Recommendation is to increase the value to 10 seconds if this occurs. ### Configuring the Connection From 403f43c74833f35189b46bd7603ce97535159a9b Mon Sep 17 00:00:00 2001 From: Mickey Li Date: Wed, 9 Mar 2022 09:29:06 +0000 Subject: [PATCH 19/36] Changed gpu_ray to ray to avoid possible gpu issues on windows docker --- simulator/vehicles/iris-ap/models/iris_ap/model.sdf.xacro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simulator/vehicles/iris-ap/models/iris_ap/model.sdf.xacro b/simulator/vehicles/iris-ap/models/iris_ap/model.sdf.xacro index 01ab93c..56b5b79 100644 --- a/simulator/vehicles/iris-ap/models/iris_ap/model.sdf.xacro +++ b/simulator/vehicles/iris-ap/models/iris_ap/model.sdf.xacro @@ -43,7 +43,7 @@ - + From 26e9be86632a6ff187013f66b31d4940a527f917 Mon Sep 17 00:00:00 2001 From: Mickey Li Date: Mon, 21 Mar 2022 19:35:33 +0000 Subject: [PATCH 20/36] Removed deployments and old scripts --- deployment/README.md | 58 --------- deployment/k8.ap-sitl.amd64.yaml | 51 -------- deployment/k8.gazebo-iris.amd64.yaml | 64 ---------- deployment/k8.mavros-vicon.arm64.yaml | 74 ------------ deployment/k8.mavros.arm64.yaml | 56 --------- deployment/k8.mavros.daemonset.yaml | 60 ---------- .../k8.px4-sitl-simple-offboard.amd64.yaml | 84 ------------- deployment/k8.px4-sitl.amd64.yaml | 75 ------------ deployment/k8.ros_monitor.amd64.yaml | 19 --- deployment/k8.starling-ui-dashly.yaml | 22 ---- deployment/k8.vicon.daemonset.yaml | 47 -------- deployment/resources/dashboard.admin.yaml | 45 ------- run_k3s.sh | 47 -------- scripts/get_dashboard_token.sh | 3 - scripts/start_dashboard.sh | 57 --------- scripts/start_example_controller.sh | 54 --------- scripts/start_k3s.sh | 112 ------------------ scripts/start_single_px4sitl_gazebo.sh | 97 --------------- ...t_single_px4sitl_gazebo_simple_offboard.sh | 97 --------------- scripts/start_starling_base.sh | 62 ---------- 20 files changed, 1184 deletions(-) delete mode 100644 deployment/README.md delete mode 100644 deployment/k8.ap-sitl.amd64.yaml delete mode 100644 deployment/k8.gazebo-iris.amd64.yaml delete mode 100644 deployment/k8.mavros-vicon.arm64.yaml delete mode 100644 deployment/k8.mavros.arm64.yaml delete mode 100644 deployment/k8.mavros.daemonset.yaml delete mode 100644 deployment/k8.px4-sitl-simple-offboard.amd64.yaml delete mode 100644 deployment/k8.px4-sitl.amd64.yaml delete mode 100644 deployment/k8.ros_monitor.amd64.yaml delete mode 100644 deployment/k8.starling-ui-dashly.yaml delete mode 100644 deployment/k8.vicon.daemonset.yaml delete mode 100644 deployment/resources/dashboard.admin.yaml delete mode 100755 run_k3s.sh delete mode 100755 scripts/get_dashboard_token.sh delete mode 100755 scripts/start_dashboard.sh delete mode 100755 scripts/start_example_controller.sh delete mode 100755 scripts/start_k3s.sh delete mode 100755 scripts/start_single_px4sitl_gazebo.sh delete mode 100755 scripts/start_single_px4sitl_gazebo_simple_offboard.sh delete mode 100755 scripts/start_starling_base.sh diff --git a/deployment/README.md b/deployment/README.md deleted file mode 100644 index bdf3f8e..0000000 --- a/deployment/README.md +++ /dev/null @@ -1,58 +0,0 @@ -# Kubernetes Dashboard - -[TOC] - -## Quick GIF - -![Dashboard GIF](../img/starling-dashboard.gif) - -## Logging In - -Connecting to [`https://localhost:31771`](https://localhost:31771) may show a security problem. This is caused by certificate issues. Accept the risk and continue if you are comfortable (this is serving locally anyhow :) ) - -![page1](../img/starling-dashboard-1.png) - -This will give the following log on page - -![page2](../img/starling-dashboard-2.png) - -The token can be found from the initial start of the dashboard, or can be accessed using the following command: -```bash -sudo k3s kubectl -n kubernetes-dashboard describe secret admin-user-token -``` -![token](../img/starling-dashboard-token.png) - -## Navigating the Dashboard - -Once logged in, the dashboard frontpage looks like the following. This can be accessed via the 'Workloads' button. It shows the current status of the cluster. On the left hand side there is a navigation bar showing the various 'resources' or computational loads which are running on the cluster. - -![page3](../img/starling-dashboard-3.png) - -The key types of resources for us is the *Deployment*, *Pod*, *Stateful Set* and *Services*. See [kubernetes docs page](/details/kubernetes.md) for more details. For example, selecting the *Pods* on the left hand panel opens up the currently running pods on the cluster. - -Here you can see *starling-px4-0*, *gazebo-v1-* pods which run the simulated drone and the gazebo simulator, as well as a numver of other pods running networking and other functions. - -Any particular Pod can be inspected by clicking on the Pods name. -![page](../img/starling-dashboard-4.png) - -For example, inspecting the *starling-px4-0* gives the following page. It specifies several useful details including: - -- pod name, -- pod creation time, -- pod labels, -- ip address -- and a number of other things - -You can access both the container *logs* and also access the container shell to directly execute commands for testing. The buttons for both are in the top right corner of the UI. - -![page](../img/starling-dashboard-5.png) - -Once you have clicked the logs icon, the logs are streamed from the container inside the pod. For example here we see the *starling-px4-sitl* container within the *starling-px4-sitl-0* pod. - -The terminal can be scrolled up and down, but has maximum size. If the terminal output has reached maximum size, the arrows in the bottom right can be used to navigate terminal 'pages'. Also note that the terminal does not update in real time. To force update the terminal, click on one of the arrows in the bottom right, or select the auto-update option in the options menu in the top right next to the downloads symbol. - -![page](../img/starling-dashboard-6.png) - -Finally, if a pod has multiple containers running within it, the logs of each container can be viewed using the drop down menu accessed by clicking the container name. - -![page](../img/starling-dashboard-7.png) \ No newline at end of file diff --git a/deployment/k8.ap-sitl.amd64.yaml b/deployment/k8.ap-sitl.amd64.yaml deleted file mode 100644 index c323527..0000000 --- a/deployment/k8.ap-sitl.amd64.yaml +++ /dev/null @@ -1,51 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: starling-ap-sitl - labels: - app: starling - platform: sitl -spec: - selector: - matchLabels: - app: starling-ap-sitl - template: - metadata: - labels: - app: starling - firmware: ardupilot - platform: sitl - spec: - containers: - - name: starling-ardupilot-sitl - image: uobflightlabstarling/starling-ardupilot-sitl - imagePullPolicy: Always - env: - - name: SYSID - valueFrom: - fieldRef: - fieldPath: status.podIP - ports: - - containerPort: 14553 - protocol: "UDP" - - containerPort: 5760 - - containerPort: 5762 - - containerPort: 5763 - - name: starling-mavros - image: uobflightlabstarling/starling-mavros:latest - imagePullPolicy: Always - env: - - name: MAVROS_FCU_URL - value: tcp://127.0.0.1:5762 - - name: MAVROS_TGT_FIRMWARE - value: "apm" - - name: MAVROS_TGT_SYSTEM - valueFrom: - fieldRef: - fieldPath: status.podIP - ports: - - containerPort: 5762 - # - name: network-tools - # image: praqma/network-multitool - nodeSelector: - kubernetes.io/arch: "amd64" diff --git a/deployment/k8.gazebo-iris.amd64.yaml b/deployment/k8.gazebo-iris.amd64.yaml deleted file mode 100644 index 584c6c3..0000000 --- a/deployment/k8.gazebo-iris.amd64.yaml +++ /dev/null @@ -1,64 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: gazebo-v1 - labels: - version: v1 -spec: - replicas: 1 - selector: - matchLabels: - app: gazebo - template: - metadata: - labels: - app: gazebo - version: v1 - spec: - # hostname: sim-gazebo - # subdomain: gazebo-srv - hostNetwork: true - shareProcessNamespace: true - nodeSelector: - beta.kubernetes.io/arch: "amd64" - containers: - - name: gazebo - image: uobflightlabstarling/starling-sim-iris:latest - resources: - requests: - cpu: "100m" - imagePullPolicy: Always - args: [ "ros2", "launch", "launch/iris.launch.xml" , sim_only:=true] - ports: - - containerPort: 8080 - - containerPort: 7681 - - containerPort: 11345 - - containerPort: 4560 - - containerPort: 7400 - protocol: UDP - - containerPort: 7401 - protocol: UDP - - containerPort: 7410 - protocol: UDP - - containerPort: 7411 - protocol: UDP - - containerPort: 7412 - protocol: UDP - - containerPort: 7413 - protocol: UDP - - containerPort: 7414 - protocol: UDP - - containerPort: 7415 - protocol: UDP - - containerPort: 7418 - protocol: UDP - - containerPort: 7419 - protocol: UDP - - containerPort: 7422 - protocol: UDP - - containerPort: 7423 - protocol: UDP - - containerPort: 10318 - protocol: UDP - - containerPort: 10317 - protocol: UDP diff --git a/deployment/k8.mavros-vicon.arm64.yaml b/deployment/k8.mavros-vicon.arm64.yaml deleted file mode 100644 index 1da04cf..0000000 --- a/deployment/k8.mavros-vicon.arm64.yaml +++ /dev/null @@ -1,74 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: starling-drones-vicon - labels: - app: starling - platform: pixhawk - -spec: - selector: - matchLabels: - app: starling - template: - metadata: - name: starling-drone-vicon - labels: - app: starling - platform: pixhawk - - spec: - hostNetwork: true - shareProcessNamespace: true - tolerations: - - key: "starling.dev/type" - operator: "Equal" - value: "vehicle" - effect: "NoSchedule" - - containers: - - - name: starling-mavros - image: uobflightlabstarling/starling-mavros:latest - imagePullPolicy: Always - securityContext: - privileged: true - env: - - name: MAVROS_TGT_FIRMWARE - value: "px4" - - name: MAVROS_TGT_SYSTEM - value: "auto" # Look up on system /etc/drone.config - - name: ROS_HOSTNAME # Required for ROS1 bridge in hostNetwork mode - value: "localhost" # ROS1 will ping HOSTNAME, defaults to system hostname - - name: ROS_MASTER_URI - value: "http://localhost:11311" - volumeMounts: - - mountPath: /dev/px4fmu - name: ttypx4 - - mountPath: /etc/starling/vehicle.config - name: vehicleconfig - readOnly: true - - - name: starling-vicon - image: uobflightlabstarling/starling-vicon:latest - imagePullPolicy: Always - ports: - - containerPort: 51001 - protocol: UDP - volumeMounts: - - mountPath: /etc/starling/vehicle.config - name: vehicleconfig - readOnly: true - - volumes: - - name: ttypx4 - hostPath: - path: /dev/px4fmu - - name: vehicleconfig - hostPath: - path: /etc/starling/vehicle.config - type: File - - nodeSelector: - kubernetes.io/arch: arm64 - starling.dev/type: vehicle diff --git a/deployment/k8.mavros.arm64.yaml b/deployment/k8.mavros.arm64.yaml deleted file mode 100644 index db06bb2..0000000 --- a/deployment/k8.mavros.arm64.yaml +++ /dev/null @@ -1,56 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: starling-drones - labels: - app: starling - platform: pixhawk -spec: - selector: - matchLabels: - app: starling - template: - metadata: - name: starling-drone - labels: - app: starling - platform: pixhawk - spec: - hostNetwork: true - shareProcessNamespace: true - containers: - - name: starling-mavros - image: uobflightlabstarling/starling-mavros:latest - imagePullPolicy: Always - securityContext: - privileged: true - env: - - name: VEHICLE_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: MAVROS_TGT_FIRMWARE - value: "px4" - - name: MAVROS_TGT_SYSTEM - value: "auto" # Look up on system /etc/drone.config - - name: ROS_HOSTNAME # Required for ROS1 bridge in hostNetwork mode - value: "localhost" # ROS1 will ping HOSTNAME, defaults to system hostname - - name: ROS_MASTER_URI - value: "http://localhost:11311" - volumeMounts: - - mountPath: /dev/px4fmu - name: ttypx4 - - mountPath: /etc/starling/vehicle.config - name: vehicleconfig - readOnly: true - volumes: - - name: ttypx4 - hostPath: - path: /dev/px4fmu - - name: vehicleconfig - hostPath: - path: /etc/starling/vehicle.config - type: File - nodeSelector: - kubernetes.io/arch: arm64 - # k3s.io/hostname: clover1 \ No newline at end of file diff --git a/deployment/k8.mavros.daemonset.yaml b/deployment/k8.mavros.daemonset.yaml deleted file mode 100644 index f49b6b1..0000000 --- a/deployment/k8.mavros.daemonset.yaml +++ /dev/null @@ -1,60 +0,0 @@ -apiVersion: apps/v1 -kind: DaemonSet -metadata: - name: starling-mavros-daemon - labels: - app: starling - -spec: - selector: - matchLabels: - name: starling-mavros-daemon - - template: - metadata: - labels: - name: starling-mavros-daemon - - spec: - nodeSelector: - starling.dev/type: vehicle - - tolerations: - - key: starling.dev/type - operator: Equal - value: vehicle - effect: NoSchedule - - hostNetwork: true - shareProcessNamespace: true - - containers: - - name: starling-mavros - image: uobflightlabstarling/starling-mavros:latest - imagePullPolicy: Always - securityContext: - privileged: true - env: - - name: MAVROS_TGT_FIRMWARE - value: "px4" - - name: MAVROS_TGT_SYSTEM - value: "auto" # Look up on system /etc/drone.config - - name: ROS_HOSTNAME # Required for ROS1 bridge in hostNetwork mode - value: "localhost" # ROS1 will ping HOSTNAME, defaults to system hostname - - name: ROS_MASTER_URI - value: "http://localhost:11311" - volumeMounts: - - mountPath: /dev/px4fmu - name: ttypx4 - - mountPath: /etc/starling/vehicle.config - name: vehicleconfig - readOnly: true - - volumes: - - name: ttypx4 - hostPath: - path: /dev/px4fmu - - name: vehicleconfig - hostPath: - path: /etc/starling/vehicle.config - type: File \ No newline at end of file diff --git a/deployment/k8.px4-sitl-simple-offboard.amd64.yaml b/deployment/k8.px4-sitl-simple-offboard.amd64.yaml deleted file mode 100644 index 580326a..0000000 --- a/deployment/k8.px4-sitl-simple-offboard.amd64.yaml +++ /dev/null @@ -1,84 +0,0 @@ -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: starling-px4-sitl - labels: - app: starling - platform: sitl -spec: - serviceName: starling-px4-sitl-srv - selector: - matchLabels: - app: starling - firmware: px4 - platform: sitl - template: - metadata: - labels: - app: starling - firmware: px4 - platform: sitl - spec: - hostNetwork: true - terminationGracePeriodSeconds: 10 - shareProcessNamespace: true - initContainers: - - name: gazebo-spawn-iris - image: uobflightlabstarling/starling-sim-iris:latest - imagePullPolicy: IfNotPresent - args: [ "./spawn_iris.sh" ] - env: - - name: PX4_INSTANCE - value: "ordinal" - - name: PX4_INSTANCE_BASE - value: "200" - - name: IGNORE_FAILURE - value: "true" - - name: HOSTNAME - valueFrom: - fieldRef: - fieldPath: metadata.name - containers: - - name: starling-px4-sitl - image: uobflightlabstarling/starling-sim-px4-sitl:latest - imagePullPolicy: IfNotPresent - env: - - name: PX4_SIM_HOST - value: "localhost" - - name: PX4_INSTANCE - value: "ordinal" - - name: PX4_INSTANCE_BASE - value: "200" - - name: HOSTNAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: starling-mavros - image: uobflightlabstarling/starling-mavros:latest - imagePullPolicy: IfNotPresent - env: - - name: VEHICLE_NAMESPACE # Optional but unique - valueFrom: - fieldRef: - fieldPath: metadata.name # Pod name - - name: MAVROS_TGT_FIRMWARE - value: "px4" - - name: MAVROS_TGT_SYSTEM - value: "auto" - - name: PX4_INSTANCE_BASE - value: "200" - - name: HOSTNAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: starling-simple-offboard - image: uobflightlabstarling/starling-simple-offboard:latest - imagePullPolicy: IfNotPresent - env: - - name: VEHICLE_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.name # Pod name - - nodeSelector: - kubernetes.io/arch: "amd64" diff --git a/deployment/k8.px4-sitl.amd64.yaml b/deployment/k8.px4-sitl.amd64.yaml deleted file mode 100644 index c2b0725..0000000 --- a/deployment/k8.px4-sitl.amd64.yaml +++ /dev/null @@ -1,75 +0,0 @@ -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: starling-px4-sitl - labels: - app: starling - platform: sitl -spec: - serviceName: starling-px4-sitl-srv - selector: - matchLabels: - app: starling - firmware: px4 - platform: sitl - template: - metadata: - labels: - app: starling - firmware: px4 - platform: sitl - spec: - hostNetwork: true - terminationGracePeriodSeconds: 10 - shareProcessNamespace: true - initContainers: - - name: gazebo-spawn-iris - image: uobflightlabstarling/starling-sim-iris:latest - imagePullPolicy: Always - args: [ "./spawn_iris.sh" ] - env: - - name: PX4_INSTANCE - value: "ordinal" - - name: PX4_INSTANCE_BASE - value: "200" - - name: IGNORE_FAILURE - value: "true" - - name: HOSTNAME - valueFrom: - fieldRef: - fieldPath: metadata.name - containers: - - name: starling-px4-sitl - image: uobflightlabstarling/starling-sim-px4-sitl:latest - imagePullPolicy: Always - env: - - name: PX4_SIM_HOST - value: "localhost" - - name: PX4_INSTANCE - value: "ordinal" - - name: PX4_INSTANCE_BASE - value: "200" - - name: HOSTNAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: starling-mavros - image: uobflightlabstarling/starling-mavros:latest - imagePullPolicy: Always - env: - - name: VEHICLE_NAMESPACE # Optional but unique - valueFrom: - fieldRef: - fieldPath: metadata.name # Pod name - - name: MAVROS_TGT_FIRMWARE - value: "px4" - - name: MAVROS_TGT_SYSTEM - value: "auto" - - name: PX4_INSTANCE_BASE - value: "200" - - name: HOSTNAME - valueFrom: - fieldRef: - fieldPath: metadata.name - nodeSelector: - kubernetes.io/arch: "amd64" diff --git a/deployment/k8.ros_monitor.amd64.yaml b/deployment/k8.ros_monitor.amd64.yaml deleted file mode 100644 index 1c7aa61..0000000 --- a/deployment/k8.ros_monitor.amd64.yaml +++ /dev/null @@ -1,19 +0,0 @@ ---- -apiVersion: v1 -kind: Pod -metadata: - name: mavros-monitor-debug -spec: - hostNetwork: true - shareProcessNamespace: true - containers: - - name: mavros-monitor-debug - image: uobflightlabstarling/starling-mavros:latest - imagePullPolicy: Always - command: [ "/bin/bash", "-c", "--" ] - args: [ "while true; do sleep 300; done;" ] - - name: network-toolbox - image: praqma/network-multitool - nodeSelector: - kubernetes.io/arch: amd64 - # k3s.io/hostname: clover1 \ No newline at end of file diff --git a/deployment/k8.starling-ui-dashly.yaml b/deployment/k8.starling-ui-dashly.yaml deleted file mode 100644 index 14ecec6..0000000 --- a/deployment/k8.starling-ui-dashly.yaml +++ /dev/null @@ -1,22 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: starling-ui -spec: - selector: - matchLabels: - app: starling-ui - template: - metadata: - labels: - app: starling-ui - spec: - hostNetwork: true - shareProcessNamespace: true - containers: - - name: starling-ui - image: mickeyli789/starling-ui-dashly:latest - imagePullPolicy: IfNotPresent - ports: - - containerPort: 3000 - - containerPort: 9090 \ No newline at end of file diff --git a/deployment/k8.vicon.daemonset.yaml b/deployment/k8.vicon.daemonset.yaml deleted file mode 100644 index a903c96..0000000 --- a/deployment/k8.vicon.daemonset.yaml +++ /dev/null @@ -1,47 +0,0 @@ -apiVersion: apps/v1 -kind: DaemonSet -metadata: - name: starling-vicon-daemon - labels: - app: starling - -spec: - selector: - matchLabels: - name: starling-vicon-daemon - - template: - metadata: - labels: - name: starling-vicon-daemon - - spec: - nodeSelector: - starling.dev/type: vehicle - - tolerations: - - key: starling.dev/type - operator: Equal - value: vehicle - effect: NoSchedule - - hostNetwork: true - shareProcessNamespace: true - - containers: - - name: starling-vicon - image: uobflightlabstarling/starling-vicon:latest - imagePullPolicy: Always - ports: - - containerPort: 51001 - protocol: UDP - volumeMounts: - - mountPath: /etc/starling/vehicle.config - name: vehicleconfig - readOnly: true - - volumes: - - name: vehicleconfig - hostPath: - path: /etc/starling/vehicle.config - type: File diff --git a/deployment/resources/dashboard.admin.yaml b/deployment/resources/dashboard.admin.yaml deleted file mode 100644 index 6d931e1..0000000 --- a/deployment/resources/dashboard.admin.yaml +++ /dev/null @@ -1,45 +0,0 @@ -# This defines the admin-user service account -apiVersion: v1 -kind: ServiceAccount -metadata: - name: admin-user - namespace: kubernetes-dashboard - ---- -# In tutorials this is referred to as -# dashboard.admin-user-role.yml -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: admin-user -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: cluster-admin -subjects: -- kind: ServiceAccount - name: admin-user - namespace: kubernetes-dashboard - ---- -# Dashboard Service to forward service onto localhost 31771 -apiVersion: v1 -kind: Service -metadata: - labels: - k8s-app: kubernetes-dashboard - name: kubernetes-dashboard - namespace: kubernetes-dashboard -spec: - externalTrafficPolicy: Cluster - ports: - - nodePort: 31771 - port: 443 - protocol: TCP - targetPort: 8443 - selector: - k8s-app: kubernetes-dashboard - sessionAffinity: None - type: NodePort -status: - loadBalancer: {} \ No newline at end of file diff --git a/run_k3s.sh b/run_k3s.sh deleted file mode 100755 index 9ccd83e..0000000 --- a/run_k3s.sh +++ /dev/null @@ -1,47 +0,0 @@ -#!/bin/bash -set -e -ARGS="$@" -# Parse arguments -POSITIONAL=() -while [[ $# -gt 0 ]] -do - key="$1" - case $key in - -h|--help) - HELP=1 - shift # past argument - ;; - -sk|--skip-install) - SKIP=1 - shift - ;; - *) # unknown option - POSITIONAL+=("$1") # save it in an array for later - shift # past argument - ;; - esac -done -set -- "${POSITIONAL[@]}" # restore positional parameters - -if [[ $HELP ]] -then - echo "Script installs and runs k3s, then starts gazebo and a single px4 sitl drone" - echo "It runs start_k3s_agent.sh and start_single_px4sitl_gazebo.sh from scripts dir" - echo "Usage: './run_k3s.sh [-h|--help] [-d|--delete]" - echo "-h|--help: prints this dialogue" - echo "-d|--delete: removes everything k3s on the system (k3s-uninstall)" - echo "-sk|--skip-install: skips the installation (and requirement for sudo" - exit -1 -fi - -SCRIPTSDIR=scripts - -if [[ ! $SKIP ]]; then - # Start K3S - # Only accepts -u|--uninstall - ./$SCRIPTSDIR/start_k3s.sh $ARGS -fi - -# Start Gazebo and Single PX4 drone -# Accepts the following [-ow|--open], [-d|--delete], [-sk|--skip-base-check], [-r|--restart] -./$SCRIPTSDIR/start_single_px4sitl_gazebo.sh $ARGS diff --git a/scripts/get_dashboard_token.sh b/scripts/get_dashboard_token.sh deleted file mode 100755 index 5aaef3e..0000000 --- a/scripts/get_dashboard_token.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -k3s kubectl -n kubernetes-dashboard describe secret admin-user-token | grep ^token | cut -c 13- diff --git a/scripts/start_dashboard.sh b/scripts/start_dashboard.sh deleted file mode 100755 index e4cac70..0000000 --- a/scripts/start_dashboard.sh +++ /dev/null @@ -1,57 +0,0 @@ -#!/bin/bash -set -e - -SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" - -# Parse arguments -POSITIONAL=() -while [[ $# -gt 0 ]] -do - key="$1" - case $key in - -h|--help) - HELP=1 - shift # past argument - ;; - *) # unknown option - POSITIONAL+=("$1") # save it in an array for later - shift # past argument - ;; - esac -done -set -- "${POSITIONAL[@]}" # restore positional parameters - -if [[ $HELP ]] -then - echo "Script starts the kubernetes dashboard and reports the connection token" - echo "Assumes that kubernetes is already running on the system" - exit -1 -fi - -echo "Deploying Kubernetes Dashboard" -echo "See: https://rancher.com/docs/k3s/latest/en/installation/kube-dashboard/ for more details" -echo "===================" -GITHUB_URL=https://github.com/kubernetes/dashboard/releases -VERSION_KUBE_DASHBOARD=$(curl -w '%{url_effective}' -I -L -s -S ${GITHUB_URL}/latest -o /dev/null | sed -e 's|.*/||') -kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/${VERSION_KUBE_DASHBOARD}/aio/deploy/recommended.yaml -kubectl apply -f ${SCRIPT_DIR}/../deployment/resources/dashboard.admin.yaml #dashboard.admin-user.yml -f deployment/resources/dashboard.admin-user-role.yml -echo "===================" - -DASHBOARD_TOKEN=`k3s kubectl -n kubernetes-dashboard describe secret admin-user-token | grep ^token | cut -c 13-` - -echo "The Dashboard is available at https://localhost:31771" -echo "You will need the dashboard token, to access it." -if command -v xclip; then - set +e # Will fail if there is no xserver running, e.g. via ssh. Fail silently - echo $DASHBOARD_TOKEN | xclip -selection clipboard -i - echo "The token has been copied onto your clipboard, it is also printed below" - set -e -else - echo "Copy and paste the token from below" -fi -echo "-----BEGIN DASHBOARD TOKEN-----" -echo $DASHBOARD_TOKEN -echo "-----END DASHBOARD TOKEN-----" -echo "Note: your browser may not like the self signed ssl certificate, ignore and continue for now" -echo "To get the token yourself run: k3s kubectl -n kubernetes-dashboard describe secret admin-user-token" -echo "===================" diff --git a/scripts/start_example_controller.sh b/scripts/start_example_controller.sh deleted file mode 100755 index 82a9808..0000000 --- a/scripts/start_example_controller.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/bin/bash -set -e - - -# Parse arguments -POSITIONAL=() -while [[ $# -gt 0 ]] -do - key="$1" - case $key in - -h|--help) - HELP=1 - shift # past argument - ;; - -d|--delete) - DELETE=1 - shift - ;; - -r|--restart) - RESTART=1 - DELETE=1 - shift - ;; - *) # unknown option - POSITIONAL+=("$1") # save it in an array for later - shift # past argument - ;; - esac -done -set -- "${POSITIONAL[@]}" # restore positional parameters - -if [[ $HELP ]] -then - echo "This script builds and runs the example controller" - echo "Usage: ./start_example_controller.sh [-h|--help] [-d|--delete] [-r|--restart]" - exit -1 -fi - - -SCRIPTSDIR="${BASH_SOURCE%/*}" -CONTROLLERSDIR="$SCRIPTSDIR/../controllers" - -if [[ $DELETE ]]; then - kubectl delete -f $CONTROLLERSDIR/example_controller_python/k8.example_controller_python.amd64.yaml -fi - -if [[ $RESTART ]]; then - echo "Restarting, waiting 20 seconds for clean shutdown" - sleep 20 -fi - -if [[ ! $DELETE || $RESTART ]]; then - kubectl apply -f $CONTROLLERSDIR/example_controller_python/k8.example_controller_python.amd64.yaml -fi \ No newline at end of file diff --git a/scripts/start_k3s.sh b/scripts/start_k3s.sh deleted file mode 100755 index eee5330..0000000 --- a/scripts/start_k3s.sh +++ /dev/null @@ -1,112 +0,0 @@ -#!/bin/bash -set -e - -KUBECONFIGDIR=~/.kube -CONFIGFILE=$KUBECONFIGDIR/config/k3s.yaml - -declare -a RCFILES=( - ~/.zshrc - ~/.bashrc -) - -# Parse arguments -POSITIONAL=() -while [[ $# -gt 0 ]] -do - key="$1" - case $key in - -h|--help) - HELP=1 - shift # past argument - ;; - -u|--uninstall) - UNINSTALL=1 - shift - ;; - --config-file) - CONFIGFILE=$2 - shift - shift - ;; - --do-not-start) - SKIPENABLE=1 - shift - ;; - --node-external-ip) - EXTERNAL_IP=$2 - shift - shift - ;; - *) # unknown option - POSITIONAL+=("$1") # save it in an array for later - shift # past argument - ;; - esac -done -set -- "${POSITIONAL[@]}" # restore positional parameters - -if [[ $HELP ]] -then - echo "Script downloads and runs k3s on the system" - echo "Default runs the system with the --docker parameter" - echo "k3s config file can be set with --config-file , defaults to $CONFIGFILE" - exit -1 -fi - -if [[ ! $UNINSTALL ]]; then - - echo "Created directory $(dirname "${CONFIGFILE}") for storing config file" - mkdir -p "$(dirname "${CONFIGFILE}")" - - K3S_ADDITIONAL_ARGS="--docker --write-kubeconfig $CONFIGFILE --write-kubeconfig-mode 644" - - if [[ $EXTERNAL_IP ]]; then - echo "External IP set to ${EXTERNAL_IP}" - K3S_ADDITIONAL_ARGS="${K3S_ADDITIONAL_ARGS} --node-external-ip ${EXTERNAL_IP}" - fi - - # Only needs to be run once per system - # Download and start kubernetes master node - echo "Downloading and Running K3s in systemd (Will not do anything if k3s already installed and running" - echo "The configuration file will be placed in $CONFIGFILE" - echo "root is required for initial installation (running of the kubernetes systemd)" - curl -sfL https://get.k3s.io | sudo bash -s - ${K3S_ADDITIONAL_ARGS} - - echo "Setting KUBECONFIG in rcfiles" - for rcfile in "${RCFILES[@]}"; do - if [[ -f $rcfile ]]; then - case `grep -F KUBECONFIG $rcfile >/dev/null; echo $?` in - 0) - echo "KUBECONFIG already set in $rcfile, KUBECONFIG NOT added to $rcfile" - ;; - 1) - echo "export KUBECONFIG=$CONFIGFILE" >> $rcfile - echo "$rcfile appended KUBECONFIG, set to $CONFIGFILE" - ;; - *) - echo "Error occured in setting KUBECONFIG in $rcfile" - RED='\033[0;31m' - NC='\033[0m' - echo "${RED}Please manually ensure that KUBECONFIG is set to $CONFIGFILE in .profile or .bashrc file${NC}" - ;; - esac - else - echo "$rcfile not found" - fi - done - - echo "All actions completed" - echo "k3s will run in the background systemd permanently" - echo "run this script with the --uninstall flag or 'k3s-uninstall.sh' to remove all of k3s" -else - echo "Removing k3s" - sudo k3s-uninstall.sh - echo "Removed k3s from system" - sudo rm -r $KUBECONFIGDIR - echo "Removed k3s config from $KUBECONFIGDIR" - exit 1 -fi - -if [[ $SKIPENABLE ]]; then - k3s-killall.sh -fi diff --git a/scripts/start_single_px4sitl_gazebo.sh b/scripts/start_single_px4sitl_gazebo.sh deleted file mode 100755 index 773b9c7..0000000 --- a/scripts/start_single_px4sitl_gazebo.sh +++ /dev/null @@ -1,97 +0,0 @@ -#!/bin/bash -set -e - -echo "----- Start: $0 -----" - -ARGS="$@" - -# Parse arguments -POSITIONAL=() -while [[ $# -gt 0 ]] -do - key="$1" - case $key in - -h|--help) - HELP=1 - shift # past argument - ;; - -ow|--open) - OPENWEBPAGE=1 - shift # past argument - ;; - -d|--delete) - DELETE=1 - shift - ;; - -sk|--skip-base-check) - SKIP_BASE=1 - shift - ;; - -r|--restart) - DELETE=1 - RESTART=1 - shift - ;; - *) # unknown option - POSITIONAL+=("$1") # save it in an array for later - shift # past argument - ;; - esac -done -set -- "${POSITIONAL[@]}" # restore positional parameters - -if [[ $HELP ]] -then - echo "Script starts both gazebo and a single px4 instance" - echo "If the base is not running, this script will start the base" - echo "If already started nothing will happen" - echo "Usage: ./start_single_px4sitl_gazebo.sh [-h | --help] [-ow | --open]" - echo "--open will automatically open the associated web pages" - exit -1 -fi - -SCRIPTSDIR="${BASH_SOURCE%/*}" -SYSTEMDIR="$SCRIPTSDIR/../system" -DEPLOYMENTDIR="$SCRIPTSDIR/../deployment" - - -if [[ $DELETE ]]; then - echo "Deleting Gazebo-iris" - kubectl delete -f $DEPLOYMENTDIR/k8.gazebo-iris.amd64.yaml - echo "Deleting px4-sitl with mavros" - kubectl delete -f $DEPLOYMENTDIR/k8.px4-sitl.amd64.yaml -fi - -if [[ $RESTART ]]; then - echo "Restarting, sleeping 30"; - sleep 30; -fi - -if [[ ! $DELETE || $RESTART ]]; then - - if [[ ! $SKIP_BASE ]]; then - # Start/ Check starling base - echo "Starting Starling Base, args: $ARGS" - ./$SCRIPTSDIR/start_starling_base.sh $ARGS - else - echo "Skipping Base Check" - fi - - echo "Deploying Starling Modules (this may take a while)" - echo "Deploying Gazebo-iris to localhost:8080 and wait for start" - kubectl apply -f $DEPLOYMENTDIR/k8.gazebo-iris.amd64.yaml - - echo "Waiting 5s for Gazebo to start to ensure proper connection" - sleep 5s - - echo "Deploying px4-sitl with mavros" - kubectl apply -f $DEPLOYMENTDIR/k8.px4-sitl.amd64.yaml - - if [[ $OPENWEBPAGE ]] - then - echo "Opening gazebo to http://localhost:8080" - xdg-open http://localhost:8080 - fi -fi - -echo "----- End: $0 -----" \ No newline at end of file diff --git a/scripts/start_single_px4sitl_gazebo_simple_offboard.sh b/scripts/start_single_px4sitl_gazebo_simple_offboard.sh deleted file mode 100755 index b2928c2..0000000 --- a/scripts/start_single_px4sitl_gazebo_simple_offboard.sh +++ /dev/null @@ -1,97 +0,0 @@ -#!/bin/bash -set -e - -echo "----- Start: $0 -----" - -ARGS="$@" - -# Parse arguments -POSITIONAL=() -while [[ $# -gt 0 ]] -do - key="$1" - case $key in - -h|--help) - HELP=1 - shift # past argument - ;; - -ow|--open) - OPENWEBPAGE=1 - shift # past argument - ;; - -d|--delete) - DELETE=1 - shift - ;; - -sk|--skip-base-check) - SKIP_BASE=1 - shift - ;; - -r|--restart) - DELETE=1 - RESTART=1 - shift - ;; - *) # unknown option - POSITIONAL+=("$1") # save it in an array for later - shift # past argument - ;; - esac -done -set -- "${POSITIONAL[@]}" # restore positional parameters - -if [[ $HELP ]] -then - echo "Script starts both gazebo and a single px4 instance" - echo "If the base is not running, this script will start the base" - echo "If already started nothing will happen" - echo "Usage: ./start_single_px4sitl_gazebo.sh [-h | --help] [-ow | --open]" - echo "--open will automatically open the associated web pages" - exit -1 -fi - -SCRIPTSDIR="${BASH_SOURCE%/*}" -SYSTEMDIR="$SCRIPTSDIR/../system" -DEPLOYMENTDIR="$SCRIPTSDIR/../deployment" - - -if [[ $DELETE ]]; then - echo "Deleting Gazebo-iris" - kubectl delete -f $DEPLOYMENTDIR/k8.gazebo-iris.amd64.yaml - echo "Deleting px4-sitl with mavros and simple offboard controller" - kubectl delete -f $DEPLOYMENTDIR/k8.px4-sitl-simple-offboard.amd64.yaml -fi - -if [[ $RESTART ]]; then - echo "Restarting, sleeping 30"; - sleep 30; -fi - -if [[ ! $DELETE || $RESTART ]]; then - - if [[ ! $SKIP_BASE ]]; then - # Start/ Check starling base - echo "Starting Starling Base, args: $ARGS" - ./$SCRIPTSDIR/start_starling_base.sh $ARGS - else - echo "Skipping Base Check" - fi - - echo "Deploying Starling Modules (this may take a while)" - echo "Deploying Gazebo-iris to localhost:8080 and wait for start" - kubectl apply -f $DEPLOYMENTDIR/k8.gazebo-iris.amd64.yaml - - echo "Waiting 5s for Gazebo to start to ensure proper connection" - sleep 5s - - echo "Deploying px4-sitl with mavros and simple offboard controller" - kubectl apply -f $DEPLOYMENTDIR/k8.px4-sitl-simple-offboard.amd64.yaml - - if [[ $OPENWEBPAGE ]] - then - echo "Opening gazebo to http://localhost:8080" - xdg-open http://localhost:8080 - fi -fi - -echo "----- End: $0 -----" \ No newline at end of file diff --git a/scripts/start_starling_base.sh b/scripts/start_starling_base.sh deleted file mode 100755 index 3595d14..0000000 --- a/scripts/start_starling_base.sh +++ /dev/null @@ -1,62 +0,0 @@ -#!/bin/bash -set -e - -echo "----- Start: $0 -----" -# Parse arguments -POSITIONAL=() -while [[ $# -gt 0 ]] -do - key="$1" - case $key in - -h|--help) - HELP=1 - shift # past argument - ;; - -ow|--open) - OPENWEBPAGE=1 - shift # past argument - ;; - *) # unknown option - POSITIONAL+=("$1") # save it in an array for later - shift # past argument - ;; - esac -done -set -- "${POSITIONAL[@]}" # restore positional parameters - -if [[ $HELP ]] -then - echo "Script applies the minimum required for starling" - echo "(1) Dashboard is started (2) Starling mission UI is started" - echo "If already started nothing will happen" - echo "Usage: ./start_starling_base.sh [-h | --help] [-ow | --open]" - echo "--open will automatically open the associated web pages" - exit -1 -fi - -if [[ ! "systemctl is-active --quiet k3s" ]] -then - echo "k3s is not active as a systemd" - echo "Please start and download k3s using start_k3s.sh" - exit -1 -fi - -SCRIPTSDIR="${BASH_SOURCE%/*}" -SYSTEMDIR="$SCRIPTSDIR/../system" -DEPLOYMENTDIR="$SCRIPTSDIR/../deployment" - -# Start dashboard -./$SCRIPTSDIR/start_dashboard.sh - -# Start starling UI -kubectl apply -f "$DEPLOYMENTDIR/k8.starling-ui-dashly.yaml" - -if [[ $OPENWEBPAGE ]] -then - echo "Opening Starling UI to http://localhost:3000" - xdg-open http://localhost:3000 - - echo "Opening Dashboard to https://localhost:31771" - xdg-open https://localhost:31771 -fi -echo "----- End: $0 -----" From a174b3377c9834d5d983edaa2fcaa2c6c63fb0e1 Mon Sep 17 00:00:00 2001 From: Mickey Li Date: Mon, 21 Mar 2022 20:06:05 +0000 Subject: [PATCH 21/36] Updated documentation further --- README.md | 6 +- docs/README.md | 21 +- docs/details/background.md | 32 +- docs/details/docker.md | 26 +- docs/details/flight_arena.md | 45 ++- docs/details/kubernetes-dashboard.md | 34 +- docs/details/kubernetes-deployment.md | 17 +- docs/details/kubernetes.md | 86 ++++- docs/details/px4.md | 1 - .../starling-vicon.md | 0 docs/guide/cli.md | 298 ++++++++++++++++++ docs/guide/example-controller.md | 18 +- docs/guide/getting-started.md | 29 +- docs/guide/using-kubectl.md | 15 - docs/guide/windows-support.md | 9 +- docs/img/ArchitectureNetworking2.png | Bin 0 -> 201660 bytes docs/other/related_projects_and_links.md | 6 +- mkdocs.yml | 7 +- 18 files changed, 547 insertions(+), 103 deletions(-) delete mode 100644 docs/details/px4.md rename docs/{details => docker-images}/starling-vicon.md (100%) create mode 100644 docs/guide/cli.md delete mode 100644 docs/guide/using-kubectl.md create mode 100644 docs/img/ArchitectureNetworking2.png diff --git a/README.md b/README.md index 87300e5..419019f 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,12 @@ This systems provides a number of key features. ## Setup -For local development and testing, clone the repository recursively so that all submodules are included +For the simplest usage, a simple Command Line Interface and examples are available in the [Murmuration repository](https://github.com/StarlingUAS/Murmuration) +``` +git clone https://github.com/StarlingUAS/Murmuration.git +``` +For inspecting and developing core Starling clone the repository recursively so that all submodules are included. ``` git clone --recurse-submodules https://github.com/StarlingUAS/ProjectStarling.git ``` diff --git a/docs/README.md b/docs/README.md index 21fb530..f923baf 100644 --- a/docs/README.md +++ b/docs/README.md @@ -12,15 +12,8 @@ This systems provides a number of key features. - Built in simulation stack based on Gazebo - Quick transition from simulation to flying controllers on real drones. -![Simple Architecture](img/ArchSimple.jpg) +![Simple Architecture](../img/ArchSimple.jpg) -## Setup - -For local development and testing, clone the repository recursively so that all submodules are included - -``` -git clone --recurse-submodules https://github.com/StarlingUAS/ProjectStarling.git -``` ## Documentation Please refer to the documentation at [https://docs.starlinguas.dev/](https://docs.starlinguas.dev/) for detailed instructions and explanations of how to use this system. @@ -30,6 +23,18 @@ The documentation is built using [MKDocs](https://www.mkdocs.org/) and can be se 1. Install requirements `pip install -r docs/requirements.txt` 2. In the project root, run `make docs` or `make serve-docs` if you want live viewing (serving to `localhost:8000`) +## Setup + +For the simplest usage, a simple Command Line Interface and examples are available in the [Murmuration repository](https://github.com/StarlingUAS/Murmuration). Refer to the documentation for furhter details. +``` +git clone https://github.com/StarlingUAS/Murmuration.git +``` + +For inspecting and developing core Starling clone the repository recursively so that all submodules are included. +``` +git clone --recurse-submodules https://github.com/StarlingUAS/ProjectStarling.git +``` + ## Features ### Drone Sandbox diff --git a/docs/details/background.md b/docs/details/background.md index a0f20f5..f2b69da 100644 --- a/docs/details/background.md +++ b/docs/details/background.md @@ -1,13 +1,33 @@ # Background -This system has been tested on **linux ubuntu 20.04**. It should be compatible with any linux distribution compatible with Docker. It may work on windows and MacOS, however it is untested and there may be networking issues [See Link](https://github.com/arthurrichards77/ardupilot_sitl_docker#mission-planner-on-windows). +## Motivation + +As robotic and autonomous systems proliferate into the wider world, there is a need to address the difficulties of system development and deployment at scale. There is evidence that industry is directly facing these challenges through the use of cloud computing, continuous integration and similar systems inspired from very successful and agile software development processes. This is made clear through offerings such as Amazon's [AWS Robomaker](https://aws.amazon.com/robomaker/), Google's cloud robotics [platforms](\url{https://googlecloudrobotics.github.io/core/) and so on. + +However, there is a great lack of such systems in most academic settings. The result's oriented attitude of many labs often leads to each researcher building a bespoke solution in order to evaluate, validate or prove their goals. These bespoke solutions are often inflexible, not extensible, difficult to understand and, importantly, reuse, with any level of confidence. This becomes especially difficult when coupled with hardware, such as UAVs, where many operational details have been implicitly assumed or ignored for favour of getting the experiment running as quick as possible. In addition these solutions are often poorly structured and maintained with little to no documentation meaning that it is difficult for researchers to build upon these systems. This is an exceptionally large hurdle to researchers who do not have strong software backgrounds, but wish to perform real world experiments which could improve the quality of research outputs. + +This is not to say that it is impossible for a research system to be developed into a reusable platform. There are many examples of research systems being ubiquitous within a group or being released outside the lab. For instance, the [Robotarium at Georgia Tech](https://www.robotarium.gatech.edu/), the Multi-Robot Systems Group at the Czech Technical University with their [experimental system](https://github.com/ctu-mrs/mrs_uav_system), and the PX4 autopilot which began it's life as a collaboration between a number of labs at ETH Zurich. But what we see is that it takes a concerted effort and many years of coincidental work which provide incremental improvements to the system each time. + +Inspired by the increasing adoption of paradigms from cloud and distributed computing, this work aims to provide a UAV experimentation system which: + +1. Supports single or multiple drones with low (control) or high level (path planning) experimentation. +2. Supports the transition between simulation to indoor flight to outdoor flight. +3. Provides a simple and easy to use interface for researchers to implement experimentation without knowledge of hardware specifics. -This system also assumes the use of ROS2 Foxy for communication between nodes, MavLink for autopilot communication with Mavros used as the bridge. PX4 is currently the main tested firmware, although ardupilot is also on the list. +## System Overview -This system primarily uses containerisation technology ([Docker](https://www.docker.com/)) to encapsulate the dependencies of core systems. This means that running containered applications do not require any installation of extra dependencies apart from the Docker runtime. Full Install instructions are here: [https://docs.docker.com/engine/install/ubuntu/](https://docs.docker.com/engine/install/ubuntu/). The core container images (i.e. container executables) of this system are all hosted in the uobflightlabstarling docker hub repository: [https://hub.docker.com/orgs/uobflightlabstarling/repositories](https://hub.docker.com/orgs/uobflightlabstarling/repositories) +Starling makes use of containerisation ([Docker](https://www.docker.com/)) and container orchestration technologies (Kubernetes) to enable consistent deployment environments regardless of underlying platform - whether simulated or real. Many previous systems also aim for a similar capability, but the use of containerisation in particular allows Starling to abstract away from hardware whilst minimising setup and configuration. This is key to increase the efficiency for the development and testing of UAV systems in our resource and time constrained research laboratory. Starling has been primarily been devised with the following two insights: + +Firstly, a key concept of Starling is to treat individual vehicles as if they are individual nodes in a compute cluster. A compute cluster is traditionally a physical set of servers within a data centre, where each node is one hardware server with its own resources. Starling therefore considers a group of UAVs as a mobile compute cluster where each UAV is a 'flying computer'. With this viewpoint, actions such as software deployment, scaling up and down, self-healing and others can start to have a physical meaning within a UAV application. + +Secondly, Starling embraces the modularity inherent in using both ROS and containerisation. ROS provides the framework for communication, and containers provide the portable foundation upon which it runs. In particular Starling provides a core set of containers which have been identified as being mandatory when running a vehicle. These include a pre-configured container which runs MAVROS, as well as defining environment variables and protocols to ensure that vehicles are unique and can communicate in a scalable manner with both its hardware and other vehicles without additional user configuration. With this modularity through containers, we can then exploit all of the recent advances in compute cluster deployment and control for use in a single and multi UAV testing environment. + +## Technology Stack + +This system has been tested on **linux ubuntu 20.04**. It should be compatible with any linux distribution compatible with Docker. It may work on windows and MacOS, however it is untested and there may be networking issues [See Link](https://github.com/arthurrichards77/ardupilot_sitl_docker#mission-planner-on-windows). -In our system each Docker Container can contain one or more ROS2 nodes. For example the `uobflightlabstarling / starling-mavros` container contains Mavros and its dependencies and require no modification by most users. Each docker container can be run by using `Docker run...`. Multiple containers can be run simultaneously using the `docker-compose` tool and specifying a yaml configuration file. +This system also assumes the use of ROS2 Foxy for communication between nodes, MavLink for autopilot communication with Mavros used as the bridge. PX4 is currently the main tested firmware, although ardupilot is also on the list. -When real drone hardware is added to the system a container deployment system is required, as docker (and docker-compose) only runs on one physical machine. [Kubernetes](https://kubernetes.io/) is what is known as a container orchestration platform controlling the deployment of groups of containers (known as pods) onto physical machines. The system can also be run within kubernetes locally on a single machine to test deployment before running on real hardware. +The core container images (i.e. container executables) of this system are all hosted in the uobflightlabstarling docker hub repository: [https://hub.docker.com/orgs/uobflightlabstarling/repositories](https://hub.docker.com/orgs/uobflightlabstarling/repositories) -Note that most of this is for the users information, we have wrapped up most of this functionality into Makefiles for ease of use. For more information about specifics, see the wiki page (TODO). \ No newline at end of file +In our system each Docker Container can contain one or more ROS2 nodes. For example the `uobflightlabstarling / starling-mavros` container contains Mavros and its dependencies and require no modification by most users. Each docker container can be run by using `Docker run...`. Multiple containers can be run simultaneously using the `docker-compose` tool and specifying a yaml configuration file. diff --git a/docs/details/docker.md b/docs/details/docker.md index cf5145b..3a5faf8 100644 --- a/docs/details/docker.md +++ b/docs/details/docker.md @@ -1,11 +1,29 @@ # Docker +## Docker Documentation For an introduction to Docker & containers, see the [official docs](https://www.docker.com/resources/what-container). -## TL;DR +## Containerisation -Get repeatable results on any machine without resorting to a virtual machine. +With traditional methods, code is developed in a specific computing environment which, when transferred to a new location, often results in bugs and errors. For example, when a developer transfers code from a desktop computer to a virtual machine (VM) or from a Linux to a Windows operating system. This is especially true when developing systems for hardware. For drones in particular, existing UAV software development often involved a lot of system specific solutions tied to the hardware. For example, most drones and controlling computers are configured only for their designated applications, tying both computer and drone to that application. It also meant that offline testing, and testing away from the physical drone was impossible, hampering the development process as well. -Allows us to test software against the simulator and deploy it to the real world using the exact same build*. +Containerisation eliminates this problem by bundling the application code together with the related configuration files, libraries, and dependencies required for it to run. This single package of software or “container” is abstracted away from the host operating system, and hence, it stands alone and becomes portable—able to run across any platform or cloud (UAV included), free of issues. -*Architecture dependant +Under the hood, containerisation is a form of system virtualisation where applications are run in isolated user spaces. This is also why they are often referred to as lightweight as containerisation does not virtualise the entirety of an operating system for each application, instead allowing for containers to share use of the host machine's operating system kernel. Because of this, containers are inherently smaller in capacity than a full VM and require less start-up time and system resources. In essence each container can be thought of simply as an easy to use 'executable' which is run by a runtime engine, in our case Docker. In addition the abstraction from the host operating system makes containerised applications portable and able to run uniformly and consistently across any platform or cloud. Containers can be easily transported from a desktop computer to a virtual machine (VM) or from a Linux to a Windows operating system, making it perfect for software development on any machine. + +## DockerFiles + +A Docker container is specified by the user using the definition of a Dockerfile. An example Dockerfile is given here in this code snippet. + +```dockerfile +FROM ${REGISTRY}uobflightlabstarling/starling-controller-base:${VERSION} +COPY example_controller_python /ros_ws/src/example_controller_python +RUN . /ros_ws/install/setup.sh && colcon build +CMD [ "/bin/bash" ] +``` + + A Dockerfile is then **built** to create an executable which can then be run. + + Note the difference between build time commands and runtime commands. + + A built Dockerfile comprises of layers, where, in general, each command specified in the Dockerfile describes a layer. Our example describes a container image with 4 layers, one for each command. This is important because layers can be downloaded once and shared between among multiple containers, again reducing runtime overhead - especially on resource constrained platforms such as the companion computers on UAVs. \ No newline at end of file diff --git a/docs/details/flight_arena.md b/docs/details/flight_arena.md index 4d96696..49b7797 100644 --- a/docs/details/flight_arena.md +++ b/docs/details/flight_arena.md @@ -2,19 +2,21 @@ [TOC] -## Architecture +## Architecture -A side effect of using kubernetes and docker is that the system is mostly network agnostic. As long as all elements of the system are on the same physical network, all elements should be able to communicate. +A side effect of using kubernetes and docker is that the system is mostly network agnostic. As long as all elements of the system are on the same physical network, all elements should be able to communicate. + +![arch2](../img/ArchitectureNetworking2.png) ### Flight Arena Networking -The flight arena (FA) runs on its own airgapped local network. The FA network exists on the CIDR subnet `192.168.10.0/24`. There is a external dhcp server sitting on `192.168.10.252` which (as of writing) is configured to serve addresses to new devices from `192.168.10.105 - 192.168.10.199`. Addresses below `105` are reserved for the flight arena machines, server and the VICON motion tracking system. +The flight arena (FA) runs on its own airgapped local network. The FA network exists on the CIDR subnet `192.168.10.0/24`. There is a external dhcp server sitting on `192.168.10.252` which (as of writing) is configured to serve addresses to new devices from `192.168.10.105 - 192.168.10.199`. Addresses below `105` are reserved for the flight arena machines, server and the VICON motion tracking system. -The flight arena machines then all have 2 network cards, one connecting to the flight arena network (grey cables), and one connecting to the internet via the UWE network (blue cables). These machines run from `192.168.10.83 - 192.168.10.85`. The flight arena server has been set up on `192.168.10.80` (this can also be accessed via `flyingserver.local`) and is host centralised aspects of the system. +The flight arena machines then all have 2 network cards, one connecting to the flight arena network (grey cables), and one connecting to the internet via the UWE network (blue cables). These machines run from `192.168.10.83 - 192.168.10.85`. The flight arena server has been set up on `192.168.10.80` (this can also be accessed via `flyingserver.local`) and is host centralised aspects of the system. ### Starling -The kubernetes master node is set to run on the flight arena server on `192.168.10.80` or `flyingserver.local`. Any worker nodes (drones) must have the master node registered. Any machines/ work laptops that want to submit deployments to the cluster must have the cluster server set to the address of the arena server within their `k3s.yaml` config file (`~/.kube/config/k3s.yaml`). +The kubernetes master node is set to run on the flight arena server on `192.168.10.80` or `flyingserver.local`. Any worker nodes (drones) must have the master node registered. Any machines/ work laptops that want to submit deployments to the cluster must have the cluster server set to the address of the arena server within their `k3s.yaml` config file (`~/.kube/config/k3s.yaml`). > Note: Security and proper cluster authentication should be set up in the future @@ -24,30 +26,47 @@ As all pods should be running `net=host` all traffic moves over the `192.168.10. The figure above shows an example application in action. There are essentially three independent communication channels: -1. Mavlink broadcast over the network on port 14550 UDP. -2. ROS2 DDS broadcast over the network. +1. Mavlink broadcast over the network on port 14550 UDP. +2. ROS2 DDS broadcast over the network. 3. Kuberenetes control layer networks (`10.42.0.0/24`) #### Mavlink Broadcast -When a drone (simulated or physical) is connected as a node and the `starling-mavros` container is run on it, `mavros` will begin broadcasting to port 14550. This broadcast is conducted over the flight arena network and any machine on the network can pick up these broadcasts. Any drone can be flown directly through mavlink if desired. +When a drone (simulated or physical) is connected as a node and the `starling-mavros` container is run on it, `mavros` will begin broadcasting to port 14550. This broadcast is conducted over the flight arena network and any machine on the network can pick up these broadcasts. Any drone can be flown directly through mavlink if desired. #### ROS2 DDS Broadcast -It is envisioned that the majority of user facing communication is conducted through ROS2. For instance communication between controllers, or controlling external hardware on the drones such as LED lights. In ROS2, communication is conducted through the [DDS middleware protocol](https://en.wikipedia.org/wiki/Data_Distribution_Service). DDS is a standard for machine-to-machine high-performance, real-time, scalable communication which uses a publish-subscribe pattern over UDP. Each ROS2 node will broadcast its list of available publishers to the whole network. Another node can subscribe and data transfer between the two will commence. +It is envisioned that the majority of user facing communication is conducted through ROS2. For instance communication between controllers, or controlling external hardware on the drones such as LED lights. In ROS2, communication is conducted through the [DDS middleware protocol](https://en.wikipedia.org/wiki/Data_Distribution_Service). DDS is a standard for machine-to-machine high-performance, real-time, scalable communication which uses a publish-subscribe pattern over UDP. Each ROS2 node will broadcast its list of available publishers to the whole network. Another node can subscribe and data transfer between the two will commence. -This means that any machine or laptop connected to the flight arena network and has ROS2 foxy running on the operating system will have access to the list of topics. *These external machines do not have to be part of the cluster*. This gives a user-friendly way of developing and interacting with the cluster. +This means that any machine or laptop connected to the flight arena network and has ROS2 foxy running on the operating system will have access to the list of topics. *These external machines do not have to be part of the cluster*. This gives a user-friendly way of developing and interacting with the cluster. #### Kubernetes Control Layer -Kubernetes generates its own network layer between its nodes on `10.42.0.0/24`. These are primarily extra deployments, daemonsets and services which facilitate pod failure recovery, automatic deployment and web based user interface port forwarding. +Kubernetes generates its own network layer between its nodes on `10.42.0.0/24`. These are primarily extra deployments, daemonsets and services which facilitate pod failure recovery, automatic deployment and web based user interface port forwarding. ## Flight Arena Details +### Using `kubectl` from Remote Machines + +To use `kubectl` from a remote machine, a copy of the `k3s.yaml` from the master machine is needed. + +On the machine you want to use `kubectl` from, run: + +`export KUBECONFIG=/path/to/k3s.yaml` + +This adds the `KUBECONFIG` variable to the environment. To make it permanent, it needs to be added to the `.bashrc`. + +Once this is done, `kubectl` can be used as normal and will connect to the master. To test, run: + +`kubectl cluster-info` + +You should see `Kubernetes control plane is running at...` reflecting your Kubernetes master. + + ### Automatic Container Deployment via DaemonSets -There are 3 daemonsets configured to perform automatic deployment of containers on any node which satisfies the daemonsets constraints on tags. This should be updated as more are added or they are taken away. +There are 3 daemonsets configured to perform automatic deployment of containers on any node which satisfies the daemonsets constraints on tags. This should be updated as more are added or they are taken away. 1. Deployment of `starling-mavros` on any node with tag `starling.dev/vehicle-class:rotary` 2. Deployment of `starling-vicon` on any node with tag `starling.dev/type: vehicle` @@ -105,7 +124,7 @@ sudo systemctl restart chrony ### Docker Local Registry as a Pass Through Cache -In order for the drones on the flight arena network to pull docker images from docker hub, the flight arena server has a docker registry running as a pull through cache. The drones must know to attempt to pull images from the local flight server first. +In order for the drones on the flight arena network to pull docker images from docker hub, the flight arena server has a docker registry running as a pull through cache. The drones must know to attempt to pull images from the local flight server first. #### Vehicle Side diff --git a/docs/details/kubernetes-dashboard.md b/docs/details/kubernetes-dashboard.md index bdf3f8e..020fabd 100644 --- a/docs/details/kubernetes-dashboard.md +++ b/docs/details/kubernetes-dashboard.md @@ -10,49 +10,51 @@ Connecting to [`https://localhost:31771`](https://localhost:31771) may show a security problem. This is caused by certificate issues. Accept the risk and continue if you are comfortable (this is serving locally anyhow :) ) -![page1](../img/starling-dashboard-1.png) +![page1](../img/starling-dashboard-1.png) -This will give the following log on page +This will give the following log on page -![page2](../img/starling-dashboard-2.png) +![page2](../img/starling-dashboard-2.png) The token can be found from the initial start of the dashboard, or can be accessed using the following command: ```bash -sudo k3s kubectl -n kubernetes-dashboard describe secret admin-user-token +kubectl -n kubernetes-dashboard describe secret admin-user-token +# or if using the starling cli +starling utils get-dashboard-token ``` -![token](../img/starling-dashboard-token.png) +![token](../img/starling-dashboard-token.png) ## Navigating the Dashboard -Once logged in, the dashboard frontpage looks like the following. This can be accessed via the 'Workloads' button. It shows the current status of the cluster. On the left hand side there is a navigation bar showing the various 'resources' or computational loads which are running on the cluster. +Once logged in, the dashboard frontpage looks like the following. This can be accessed via the 'Workloads' button. It shows the current status of the cluster. On the left hand side there is a navigation bar showing the various 'resources' or computational loads which are running on the cluster. -![page3](../img/starling-dashboard-3.png) +![page3](../img/starling-dashboard-3.png) The key types of resources for us is the *Deployment*, *Pod*, *Stateful Set* and *Services*. See [kubernetes docs page](/details/kubernetes.md) for more details. For example, selecting the *Pods* on the left hand panel opens up the currently running pods on the cluster. -Here you can see *starling-px4-0*, *gazebo-v1-* pods which run the simulated drone and the gazebo simulator, as well as a numver of other pods running networking and other functions. +Here you can see *starling-px4-0*, *gazebo-v1-* pods which run the simulated drone and the gazebo simulator, as well as a numver of other pods running networking and other functions. Any particular Pod can be inspected by clicking on the Pods name. -![page](../img/starling-dashboard-4.png) +![page](../img/starling-dashboard-4.png) For example, inspecting the *starling-px4-0* gives the following page. It specifies several useful details including: -- pod name, -- pod creation time, -- pod labels, +- pod name, +- pod creation time, +- pod labels, - ip address - and a number of other things You can access both the container *logs* and also access the container shell to directly execute commands for testing. The buttons for both are in the top right corner of the UI. -![page](../img/starling-dashboard-5.png) +![page](../img/starling-dashboard-5.png) -Once you have clicked the logs icon, the logs are streamed from the container inside the pod. For example here we see the *starling-px4-sitl* container within the *starling-px4-sitl-0* pod. +Once you have clicked the logs icon, the logs are streamed from the container inside the pod. For example here we see the *starling-px4-sitl* container within the *starling-px4-sitl-0* pod. The terminal can be scrolled up and down, but has maximum size. If the terminal output has reached maximum size, the arrows in the bottom right can be used to navigate terminal 'pages'. Also note that the terminal does not update in real time. To force update the terminal, click on one of the arrows in the bottom right, or select the auto-update option in the options menu in the top right next to the downloads symbol. -![page](../img/starling-dashboard-6.png) +![page](../img/starling-dashboard-6.png) Finally, if a pod has multiple containers running within it, the logs of each container can be viewed using the drop down menu accessed by clicking the container name. -![page](../img/starling-dashboard-7.png) \ No newline at end of file +![page](../img/starling-dashboard-7.png) \ No newline at end of file diff --git a/docs/details/kubernetes-deployment.md b/docs/details/kubernetes-deployment.md index af341bf..33df99e 100644 --- a/docs/details/kubernetes-deployment.md +++ b/docs/details/kubernetes-deployment.md @@ -1,9 +1,11 @@ -# Deployment with kubernetes (k8) +# Deployment with kubernetes and K3S + +**NOTE** This is now a little out of date and is superceeded by using KIND and the [starling CLI](../guide/cli.md). Please see that for local testing while this is being updated. ## Contents [TOC] -This system is intended to run as a cloud platform. We utilse [k3s](https://rancher.com/docs/k3s/latest/en/quick-start/) as our kubernetes manager. +This system is intended to run as a cloud platform. We utilse [k3s](https://rancher.com/docs/k3s/latest/en/quick-start/) as our kubernetes manager. Key concepts are as follows: - **pods** are a k8 concept. A pod contains one or more containers and has its own ip address. Containers within a pod communicate over localhost @@ -24,7 +26,7 @@ The .yaml files in this directory are all kubernetes configurations for various - `starling-sim-px4-sitl` - emulating px4-sitl. Talks to GCS software on port 14550 with replies on 18570. - `starling-mavros` - contains a ROS2 mavros node connected via udp://localhost:14540 to sitl. Talks to GCS on udp broadcast port 14553. - **k8.ap-sitl.amd64.yaml** :- [Needs updating]Currently runs a pod containing two containers - - `starling-ardupilot-sitl` - emulating ardupilot-sitl. Talks to GCS over 14553 as well. + - `starling-ardupilot-sitl` - emulating ardupilot-sitl. Talks to GCS over 14553 as well. - `starling-mavros` - contains a ROS2 mavros node connected via tcp://localhost:5762 to sitl. Talks to GCS on udp broadcast port 14553. - **k8.mavros.arm64.yaml** :- A mavros node designed to run on the raspberry pi/ drone control computer. This pod contains a single `starling-mavros` container. It reads of a px4 pixhawk assumed to be talking over usb serial connection `/dev/px4fmu` (set up via udev symlinks). Currently assumes mavlink sysid is 1. - **k8.ros_monitor.amd64.yaml** :- runs `starling-mavros` and a network-tools container. Can be used for debugging ROS2 and networking issues @@ -33,7 +35,7 @@ The .yaml files in this directory are all kubernetes configurations for various Once k3s has been installed (see below, or run `./run_k3s.sh` in the home directory), these configurations can be used in the cluster as follows: ```bash # Applying/ Creating them -sudo k3s kubectl apply -f +sudo k3s kubectl apply -f # Deleting the deployment sudo k3s kubectl delete -f -f ``` @@ -72,7 +74,7 @@ This script will download the latest version of k3s run the master kubernetes se '```curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--docker" sh -``` ' (will run in background as systemd - check `systemctl status k3s`) -This will open up a server with entrypoint on `0.0.0.0:6443` which corresponds to `:6443` +This will open up a server with entrypoint on `0.0.0.0:6443` which corresponds to `:6443` ### Pi / Drone / Agent First ensure that the pi has been correctly set up with an airgapped installation of k3s, [see here for installation instructions](https://rancher.com/docs/k3s/latest/en/installation/airgap/). Follow the Manually Deploy Images Method. The script below assumes that the the images file and the k3s executable are in the user home directory. @@ -114,7 +116,7 @@ sudo k3s agent -t ${K3S_TOKEN} -s ${K3S_SERVER} --node-name ${K3S_NODE_NAME} ``` The Pi should now be setup -Consider running the above using screen or somehow in the background just in case your ssh connection is unstable or you want to close it. +Consider running the above using screen or somehow in the background just in case your ssh connection is unstable or you want to close it. ### Post actions @@ -129,6 +131,3 @@ Are started automatically in the `./run_k3s.sh` script. - - - diff --git a/docs/details/kubernetes.md b/docs/details/kubernetes.md index 534fac7..65d7749 100644 --- a/docs/details/kubernetes.md +++ b/docs/details/kubernetes.md @@ -2,17 +2,84 @@ [TOC] -## Overview +## Kubernetes in Starling +Containers allow the developer to abstract away from the specific +hardware that the software will be running on. This is great for local +development where a user can simply spin up the docker containers +required, but what about in ’production’? Where do we actually run these +containers. -## Kubernetes in Starling +The deployment of containers to a computation cluster - such as the +cloud, a network of servers or a group of flying UAVs is not a trivial +task. Such a task must include managing available resources, creating +and managing the network connection within the cluster, service +scalability, and services such as failure recovery (self healing) and +logging. Thankfully this is handled by a class of applications known as +*container orchestration* platforms. This project uses Kubernetes as its +container orchestration platform which is an industry standard for +container deployments within a server farm. + +In Kubernetes in particular, each physical piece of server hardware +capable of running a container is referred to as a *Node*. The +kubernetes control plane containers then take in user created +configurations to decide where to run a particular container. This yaml +file is known as a *KubeConfig* file and specifies: + +- Defines which container images and which versions make up the + application, and where they are located (in what registry) + +- Provisions required storage and hardware capabilities using mounted + volumes and labels along with the concept of *taints* and + *tolerations* + +- Defines and secures network configurations + +## Kubernetes Deployment Types + +There are also a number of different types of deployment which are used +throughout Starling, all of which are specified by Kubernetes +Configuration. + +- *Pod* - A single deployment of a set of containers to a particular + node. + +- *Deployment* - A single self-healing pod deployment which can be + scaled. + +- *StatefulSet* - A deployment with which the hostnames follow ordinal + enumeration upon scaling. +- *DaemonSet* - A pod is deployed to every node which satisfies the + taints and tolerations. -## Practical Details +- *Service* - A special pod which manages networking and service + access. + +There are a number of different versions of kubernetes. Due to the +low-power, low-compute nature of UAV companion computers we have chosen +to use *k3s*[^1], a lightweight variant of kubernetes often used in edge +and iot computing applications. In addition, to streamline the +transitory development process from container to kubernetes, we also +make use of *kind*[^2] which allows the running of kubernetes inside a +docker container. The *kubectl*[^3] command line tool is primarily used +to interact with a kubernetes cluster. + +## Local Testing using Kubernetes In Docker (KIND) + +In order to test kubernetes locally, we have decided to use Kubernetes In Docker (Kind). Kind is a useful tool which runs Kubernetes between multiple docker containers, just as if the containers themselves were kubernetes physical nodes. This allows us to test deployments locally on a system which resembles the actual flight arena. + +The Starling CLI takes care of many of the details of how kind is setup in such a way as to mirror the real vehicles. This includes ensuring that vehicles have access to virtual `vehicle.config` files and other measures. + +It also means that any deployed daemonsets can be tested to ensure that they deploy correctly to the real vehicles. + +See the [CLI Documentation](../guide/cli.md) for further information. + +## Flight Arena K3S Practical Details ### Root/Non-root access -By default the k3s installation creates a configuration file `k3s.yaml` which stores access tokens, the IP address of the master servers and various other information. By default this is stored in `/etc/rancher/k3s/k3s.yaml`. Any time `k3s` is invoked, i.e. any time you call `k3s kubectl ...`, this configuration file is accessed and read. However because the configuration file is stored in the system directory `/etc/`, by default any call to `k3s` would need `sudo`, i.e. `sudo k3s kubectl ...`. +By default the k3s installation creates a configuration file `k3s.yaml` which stores access tokens, the IP address of the master servers and various other information. By default this is stored in `/etc/rancher/k3s/k3s.yaml`. Any time `k3s` is invoked, i.e. any time you call `k3s kubectl ...`, this configuration file is accessed and read. However because the configuration file is stored in the system directory `/etc/`, by default any call to `k3s` would need `sudo`, i.e. `sudo k3s kubectl ...`. This can be avoided by moving the `k3s.yaml` file to your local user directory. The recommended location is `~/.kube/config/k3s.yaml`, but it can be put anyway. The only requirement is that the `KUBECONFIG` environment variable should be set to the location of the configuration file. This can be achieved by adding the following line somewhere into your `~/.bashrc` (or `~/.zshrc` if using zsh): @@ -21,7 +88,7 @@ export KUBECONFIG=~/.kube/config/k3s.yaml ``` > **Note:** Do not forget to source your bashrc after making the change to see the change in your terminal `source ~/.bashrc` -Once this is set, any call of `k3s ...` will not require `sudo`. +Once this is set, any call of `k3s ...` will not require `sudo`. ### Access Across Machines @@ -32,7 +99,7 @@ apiVersion: v1 clusters: - cluster: certificate-authority-data: - server: https://127.0.0.1:6443 ### <---- This line! + server: https://127.0.0.1:6443 ### <---- This line! name: default contexts: - context: @@ -52,7 +119,7 @@ users: To therefore access the master node (and the dashboard and kubectl etc) on another machine, you must. 1. Copy the `k3s.yaml` file off of the server onto your own machine. (For the BRL, see below) -2. In the `k3s.yaml` change the server ip from `127.0.0.1` to the ip address of the master machine. +2. In the `k3s.yaml` change the server ip from `127.0.0.1` to the ip address of the master machine. 3. Ensure that either KUBECONFIG is set, or you have replaced the filee at `~/.kube/config/k3s.yaml` with your own modified one (may need sudo) Once that is set up verify that you can get the dashboard access token and log in. @@ -63,11 +130,8 @@ k3s kubectl -n kubernetes-dashboard describe secret admin-user-token | grep ^tok Also then verify that you can run any of the kubernetes deployments, and that they will be deployed to the master cluster (and not your own). -> **Note:** Even if the cluster is not running on your local machine, you will still need to install the k3s binaries. The command run by `run_k3s.sh` or `./scripts/start_k3s.sh` both download and then automatically start k3s in the background of your machine. To stop it from starting, pass the `--do-not-start` option to either command. If you have already have k3s running, run `k3s-killall.sh` which will stop all k3s processes without uninstalling it entirely (so you still get access to the k3s command line tools). +> **Note:** Even if the cluster is not running on your local machine, you will still need to install the k3s binaries. The command run by `run_k3s.sh` or `./scripts/start_k3s.sh` both download and then automatically start k3s in the background of your machine. To stop it from starting, pass the `--do-not-start` option to either command. If you have already have k3s running, run `k3s-killall.sh` which will stop all k3s processes without uninstalling it entirely (so you still get access to the k3s command line tools). In the BRL, to get the k3s.yaml file, you should simply be able to scp (ssh copy) the file from the master machine `~/.kube/config/k3s.yaml` to your local machine, and change the IP address. ## Configuration Files - - - diff --git a/docs/details/px4.md b/docs/details/px4.md deleted file mode 100644 index ce87ffe..0000000 --- a/docs/details/px4.md +++ /dev/null @@ -1 +0,0 @@ -# PX4 Implementation Details \ No newline at end of file diff --git a/docs/details/starling-vicon.md b/docs/docker-images/starling-vicon.md similarity index 100% rename from docs/details/starling-vicon.md rename to docs/docker-images/starling-vicon.md diff --git a/docs/guide/cli.md b/docs/guide/cli.md new file mode 100644 index 0000000..0340437 --- /dev/null +++ b/docs/guide/cli.md @@ -0,0 +1,298 @@ +# Starling CLI + +This guide/tutorial provides a top to bottom set of instructions on how to use the Starling Command Line Interface present within the [Murmuration repostiroy](https://github.com/StarlingUAS/Murmuration). + +This guide will be useful to those who have completed the first introduction and wish to start using starling more actively, especially with multiple vehicles. + +[TOC] + +## Background +The starling cli is comprised of a number of bash and python scripts to help streamline the usage of common aspects of the Starling vehicle controller simulation stack. + +It is envisioned as aiding the process of following the Starling application development workflow: + +1. Developing and testing the application locally in containers on a single simulated drone +2. Developing and testing the application locally within a simulated network archietcture of the flight arena using single or multiple simulated drones +3. Developing and testing the application on real vehicles within the flight arena. + +The only requirement of the starling cli is a terminal running a shell and git. However please make sure you are running on a suitable computer as some of the applications are quite resource intensive. + +## Installation + +There are no installation steps apart from cloneing this github repository into your local workspace. +```console +git clone https://github.com/StarlingUAS/Murmuration.git +``` + +In the root of the repository, there is the core cli script named `starling`. `starling` includes a further installation script to help install further requirements. This installation script will need to be run using root. See the following guide on which arguments you should give. + +> If running within Murmuration, swap `starling` for `./starling`. However for convenience, you can put `starling` onto your path. This can be done by adding `export PATH=:$PATH` into `~/.bashrc` and `source ~/.bashrc` , or running the same command locally in the terminal. Then you can use the below commands verbatim. + +### Only local development (step 1) +To install the bare minimum requirements of starling, run the following. +```console +sudo starling install +``` +This will + +- Update the system +- Install python3, pip, curl, udmap and a python utility called click +- Add the Mumuration directory to your path so you can run starling from anywhere +- Install docker + +### Multi-Vehicle Local testing (step 2) + +For this we utilise kubernetes in docker, a.k.a *kind* [link](https://kind.sigs.k8s.io/docs/user/quick-start/): +```console +sudo starling install kind +``` + +This is an application which will allow us to test out kubernetes deployments within a docker container without needing to install kubernetes elements locally. It also allows windows and mac users to develop starling applications as well. + +### Multi-Vehicle Multi-Computer Setup + +The final step allows you to setup the setup used in the flight arena using multiple physical machines. For this, we utilise k3s. + +> This is not recommended for most users. Most should be able to use kind for testing, to then deploy straight in the flight arena. + +```console +sudo starling install k3s +``` + +## Docker-Compose Usage + +The `starling` utility is made up of a number of useful scripts which can be used together to run the simulation and your own controllers. Using `starling` with `docker-compose` is very simple and essentially a drop in replacement. + +Once `starling` has installed, a docker compose configuration yaml file can be run with the following command: +```bash +starling deploy -f +# e.g. +starling deploy -f docker-compose.px4.yaml +# or if the system needs to build a local container +starling deploy -f docker-compose.px4.yaml --build +``` + +The container can then be stopped by pressing `CTRL+C`. + +If other docker-compose commands are required, you can just use the `docker-compose` cli. + +If you want to run a container standalone, please refer to normal usage of `docker run ....`. + +## Kind Usage + +The `starling` utility is made up of a number of useful scripts which can be used together to run the simulation and your own controllers + +### Starting or stopping the kind cluster + +Running the following will give you a single drone cluster +``` +starling start kind +``` + +If you are looking to experiment with a multi-drone system, you can specify the number using the `-n` option. For example for a 3 node (drone) cluster +``` +starling start kind -n 3 +``` + +To stop the cluster, simply run the following. Note that this will delete all changes within the kind cluster and maybe require re-setup. +``` +starling stop kind +``` + +### Monitoring: + + +> For more details, see the [following tutorial](https://docs.starlinguas.dev/details/kubernetes-dashboard/) for an illustrated guide. + +Once started, you can start the monitoring dashboard using the following command: + +``` +starling start dashboard +``` + +This will start up the [kubernetes dashboard](https://kubernetes.io/docs/tasks/access-application-cluster/web-ui-dashboard/). To access the dashboard, open up a browser and go to https://localhost:31771. + +> Note the browser may not like the security, we promise it is safe to access! If you click 'advanced options' and 'continue to website' you should be able to access the website. + +To log on, the above command should show something like the following: +```console +The Dashboard is available at https://localhost:31771 +You will need the dashboard token, to access it. +Copy and paste the token from below +-----BEGIN DASHBOARD TOKEN----- + +-----END DASHBOARD TOKEN----- +Note: your browser may not like the self signed ssl certificate, ignore and continue for now +To get the token yourself run: kubectl -n kubernetes-dashboard describe secret admin-user-token +``` +You can copy the `` and paste it into the dashboard token. You can also get the access token again by running: +``` +starling utils get-dashboard-token +``` + +You can also monitor the system using +```bash +starling status +# or to continually watch +starling status --watch +``` + +And finally, you can inspect the system using the standard [`kubectl` commands](https://kubernetes.io/docs/reference/kubectl/cheatsheet/) + +### Running the simulator + +Once the simulator in kind has started, we can start the general simulator. + +> **IMPORTANT**: The simulator can potentially be resource heavy to run. This is with respect to both CPU usage and RAM. Before going further, please ensure you have enough space on your primary drive (at least 30Gb to be safe, especially C drive on windows). This is especially true if running multiple vehicles. It is not recommended to run more than around 6. + +First we should load or download the required simulation containers locally. This can be done using the follwoing command. We need to run the load command as we want to load the local container images into the kind container. This avoids the need for the kind containers to download the containers themselves at each runtime. + +> This command can take as long as 30 minutes depending on internet connection. It goes through the deployment files and downloads a couple of large containers e.g. the gazebo and sitl containers. + +> If the container doesn't already exist locally, it will attempt to download it from docker hub + +``` +starling simulator load +``` + +Once containers are downloaded and loaded into Kind, we can start the simulator containers using the following: + +```bash +starling simulator start +# or to do both steps at the same time: +starling simulator start --load +``` + +Once run, you can monitor the deployments on the kubernetes dashboard. In particular you can inspect the **worloads** -> **pods**. If they show green the systems should hopefully have started correctly. + +> Again, see [this illustrated guide](https://docs.starlinguas.dev/details/kubernetes-dashboard/) to the dashboard + +> Note, if the load was unsucessful, or had issues, some containers might take a while to download starling containers. + +Once started, the simulator should be available to inspect from https://localhost:8080 + +At this point, you can run your own deployments or some of the examples. + +If at any point, you want to restart the simulation, you can run the following. This will delete the starling simulator containers, and then start them again. +```bash +starling simulator restart +``` + +If you want to stop the simulation, simply run +```bash +starling simulator stop +``` + +### Deploying containers + +The primary usage of starling is to test your local containers. To do so, please follow the following steps. + +First, if it is a local container - a container image which exists on your local machine either through having pulled it from docker hub, or built locally - you will need to load that image into kind. Each time you rebuild or change the image, you will need to load it into kind. This can be achieved using the following command: + +> Note: If the container doesnt exist locally it will automatically try and download it from docker hub + +```bash +starling utils kind-load +# e.g. +starling utils kind-load uobflightlabstarling/starling-mavros:latest +``` + +Secondly, you will need to write a kubernetes deployment file known as a kubeconfig. A kubeconfig is a yaml file which specifies what sort of deployment you want, along with many options (where to run your controller etc.). For now, see the *deployment* folder and the repositories in StarlingUAS for examples. In particular we mention the existence of two types of deployment: + +1. **Deployment**: This specfies the self-healing deployment of one *pod* (a.k.a a collection of containers) to a node. +2. **DaemonSet**: This specifies the self-healing automatic deployment of a pod to all nodes which match a given specification. Use this for a deployment to all vehicles. + +Once a kubernetes configuration has been written, it can be deployed to kind. +```bash +starling deploy -k start +``` + +> *Note* if you have added starling to path (through standard installation or otherwise), you can run this from any directory, in particular the directory of the project you wish to deploy. If you haven't, you may need to give a full absolute path. + +> *Note* if kind is not active or installed, `starling deploy` will interpret commands for `docker-compose`. + +To stop or restart a deployment you can use the following: +```bash +starling deploy -k stop +starling deploy -k restart +``` + +To restart a deployment with an update to the container, you just need to ensure you have loaded it in before you restart: +```bash +starling utils kind-load +starling deploy -k restart +``` + +Again, use the dashboard to monitor the status of your deployment + +# Docker-Compose Examples + +The [`docker-compose`](docker-compose) contains a number of preset examples which spin up at minimum a simulator, a software autopilot, mavros and a ros webridge, but also example ui's and basic controllers. + +Each example file has a *linux* and *windows* variant. +- The *linux* variant runs `network_mode=host` which allows for ROS2 traffic to be shared between the container and your local network. Therefore the traffic is visible to your machine's local (non-container) ROS2 foxy instance, allowing you to run bare-metal application such as rviz2 or your own controllers (i.e. you do not need to wrap your own controllers in a docker container). Any exposed ports are automatically exposed to `localhost`. +- The *windows* variant runs inside a docker-compose network named `_default`, (e.g. running a px4 example will create a local network `px4_default`). This network is segregated from your local network traffic *except* for the exposed ports in the docker-compose file which are now accessible from `localhost` (Windows has no support of `net=host`). Any other ROS2 nodes will need to be wrapped in a docker container for running and run with `--network px4_default` or `--network ardupilot_default`. See the example controller repository for an example ROS2-in-docker setup. + +## PX4 Examples + +The [docker-compose/px4](docker-compose/px4) folder contains a number of px4 based examples. See the [README](https://github.com/StarlingUAS/Murmuration/blob/main/docker-compose/px4/README.md) for more details. + +### Core System +To start the simulator, a px4 SITL, mavros and the web-bridge, use the following command: +```bash +# Pull and Run the docker-containers +starling deploy -f docker-compose/px4/docker-compose.core.linux.yml --pull +# or for windows +starling deploy -f docker-compose/px4/docker-compose.core.windows.yml --pull +``` + +### Simple Offboard with Trajectory Follower UI System +To start the core with a [simple-offboard controller](https://github.com/StarlingUAS/starling_simple_offboard), [simple-allocator](https://github.com/StarlingUAS/starling_allocator) and [Trajectory follower Web GUI](https://github.com/StarlingUAS/starling_ui_dashly), use the following command: + +```bash +# Run the docker-containers +starling deploy -f docker-compose/px4/docker-compose.simple-offboard.linux.yml --pull +# or for windows +starling deploy -fdocker-compose/px4/docker-compose.simple-offboard.windows.yml --pull +``` + +## Ardupilot Examples + +The [docker-compose/ardupilot](docker-compose/ardupilot) folder contains a number of px4 based examples. See the [README](https://github.com/StarlingUAS/Murmuration/blob/main/docker-compose/ardupilot/README.md) for more details. + +### Core System +To start the gazebo simulator, an arducopter SITL, mavros and the web-bridge, use the following command: +```bash +# Run the docker-containers +starling deploy -f docker-compose/ardupilot/docker-compose.ap-gazebo.linux.yml --pull +# or for windows +starling deploy -f docker-compose/ardupilot/docker-compose.ap-gazebo.windows.yml --pull +``` + +## Running External Examples +An example offboard ROS2 controller can then be conncted to SITL by running the following in a separate terminal. For this we have to use `docker`'s built in command line interface. + +``` +# Download the latest container +docker pull uobflightlabstarling/example_controller_python + +docker run -it --rm --network px4_default uobflightlabstarling/example_controller_python +# or for ardupilot +docker run -it --rm --network ardupilot_default uobflightlabstarling/example_controller_python +``` + +See [the docs](https://docs.starlinguas.dev/guide/single-drone-local-machine/#2-running-example-ros2-offboard-controller-node) for further details + +# Kubernetes Examples + +A folder of example deployments is provided in the repository. Currently there is only the one example, but hopefully more will follow! These can all be run using the `deploy` command with the name of the example (corresponds to name of the folder) +```bash +starling deploy example +#e.g. +starling deploy example simple-offboard +``` + +1. **simple-offboard** - This example deploys 3 elements which together allow the running of simple waypoint following examples through a graphical user interface on one or more UAVs. Can be run using `starling deploy example simple-offboard` once the simulator has been initialised + 1. A daemonset is used to deploy the [simple offboard controller](https://github.com/StarlingUAS/starling_simple_offboard) to each vehicle. This provides a high level interface for controlling a vehicle + 2. An example [Python based UI using the dash library](https://github.com/StarlingUAS/starling_ui_dashly). This provides a graphical user interface to upload and fly trajectories. + 3. An [allocator module](https://github.com/StarlingUAS/starling_allocator) which takes a trajectory from the UI and distributes it to a particular vehicle to fly. diff --git a/docs/guide/example-controller.md b/docs/guide/example-controller.md index 9b64d53..54331d6 100644 --- a/docs/guide/example-controller.md +++ b/docs/guide/example-controller.md @@ -1,5 +1,7 @@ # Example Python Controller +**NEEDS UPDATING** For now see the [example_python_controller](https://github.com/StarlingUAS/example_python_controller) repository for exact commands. The theoretical content is still useful though. + This is a very basic "controller" written in Python, using ROS2 and MAVROS to control a PX4-based vehicle. It commands all current vehicles to fly in a 1m radius circle at a height of 2.5m. It begins sending offboard setpoints, sets the mode to `OFFBOARD`, arms the vehicle, and finally begins moving the setpoint around the sky. @@ -18,7 +20,7 @@ docker run -it --network projectstarling_default example_controller_python ``` where `projectstarling_default` is the name of the default network created by the example `docker-compose.yaml` in the root directory. -Replace `example_controller_python` with `uobflightlabstarling/example_controller_python` if you have not locally built the controller. +Replace `example_controller_python` with `uobflightlabstarling/example_controller_python` if you have not locally built the controller. > **Note:** Due to the way rclpy works, this container can not be killed gracefully unless using the `-it` flags for docker run. See [this issue](https://github.com/ros2/rclpy/issues/527). @@ -30,25 +32,25 @@ To run the controller in kubernetes, k3s must be up and running, then simply app kubectl apply -f example_controller_python/k8.example_controller_python.amd64.yaml ``` -This will firstly run the local `uobflightlabstarling/example_controller_python` if one exists, otherwise it will pull from docker hub. +This will firstly run the local `uobflightlabstarling/example_controller_python` if one exists, otherwise it will pull from docker hub. ## Details ### Controller Phases 1. #### Initialisation - + In the initialisation phase, the controller sends a stream of setpoints to the vehicle while it waits for a valid position. This is needed as PX4 will not switch into `OFFBOARD` mode without a valid setpoint stream. In this phase, the controller also initialises the initial position of the vehicle. 1. #### Mode switching - + PX4 follows various types of setpoints when in `OFFBOARD` mode. The controller uses a ROS service to command the vehicle to switch into this mode. It will retry the mode switch once per second until it succeeds. - Once this is complete it will wait for a ros message from `/mission_start` before continuing on to ARM. + Once this is complete it will wait for a ros message from `/mission_start` before continuing on to ARM. 1. #### Arming @@ -73,13 +75,13 @@ This will firstly run the local `uobflightlabstarling/example_controller_python` vehicle. In this case, these follow a circle around the local coordinate system origin. -> **Note:** If a message is sent on the `/emergency_stop` topic this controller will send the PX4 e-STOP command. This stops the motors of the drones. +> **Note:** If a message is sent on the `/emergency_stop` topic this controller will send the PX4 e-STOP command. This stops the motors of the drones. ### Multiple Drones -If this controller is run with multiple SITL or real drone instances, each broadcasting their topics in the form `/mavros/...`, then one controller is deployed for each drone instance. +If this controller is run with multiple SITL or real drone instances, each broadcasting their topics in the form `/mavros/...`, then one controller is deployed for each drone instance. -By default, `launch/example_fly_all.launch.py` is the launch file which produces this behaviour. It provides an example for how to write an offboard multi-drone controller. +By default, `launch/example_fly_all.launch.py` is the launch file which produces this behaviour. It provides an example for how to write an offboard multi-drone controller. ### Coordinate Systems diff --git a/docs/guide/getting-started.md b/docs/guide/getting-started.md index b90d302..261a7b3 100644 --- a/docs/guide/getting-started.md +++ b/docs/guide/getting-started.md @@ -9,6 +9,20 @@ The only dependency of running and using Starling locally is the [Docker] contai ### Linux/ Ubuntu +#### Install Script +For linux, we have provided a helpful install script. First you will need to clone the Murmuration github repository: +``` +git clone https://github.com/StarlingUAS/Murmuration.git +``` + +Then run: +``` +sudo ./starling install +``` + +Once that completes you should be all good to go for the next step. + +#### Manual Installation For Linux systems, see the following [install page](https://docs.docker.com/engine/install/ubuntu/). There are multiple ways of installation docker, but we recommend installing using the repository method: 1. Update the `apt` repository and install requirements @@ -54,9 +68,9 @@ That is Docker on Linux installed. See [the original page](https://docs.docker.c For Windows systems, please see [the windows installation instructions for full details](https://docs.docker.com/docker-for-windows/install/). You will need to install Docker Desktop for Windows. -Follow the instructions and tutorial below, but refer to our [Windows Support page](windows-support.md) if you have issues. +Follow the instructions and tutorial below, but refer to our [Windows Support page](windows-support.md) if you have issues. -Starling has not been fully tested on Windows, but single-agent non-cluster (docker-only) applications should be compatible. +Starling has been fully tested on Windows, and both single and multi-agent cluster testing is available through the use of the Starling CLI. However it is recommended that you use WSL rather than powershell. ### Mac OS @@ -66,9 +80,16 @@ Starling has not be tested on MAC OS, so there is no guarantee that it is functi ## Get Starling -Clone the Starling repository: +For simple usage, or for usage as a simulator, you can use the Murmuration repository which gives a number of starling examples and a command line interface. + +``` +git clone https://github.com/StarlingUAS/Murmuration.git +``` - git clone https://github.com/StarlingUAS/ProjectStarling.git +For more advanced usage, or to see examples of the core elements, clone the Starling repository: +``` +git clone https://github.com/StarlingUAS/ProjectStarling.git +``` ## Using Starling diff --git a/docs/guide/using-kubectl.md b/docs/guide/using-kubectl.md deleted file mode 100644 index f3b1909..0000000 --- a/docs/guide/using-kubectl.md +++ /dev/null @@ -1,15 +0,0 @@ -# Using `kubectl` from Remote Machines - -To use `kubectl` from a remote machine, a copy of the `k3s.yaml` from the master machine is needed. - -On the machine you want to use `kubectl` from, run: - -`export KUBECONFIG=/path/to/k3s.yaml` - -This adds the `KUBECONFIG` variable to the environment. To make it permanent, it needs to be added to the `.bashrc`. - -Once this is done, `kubectl` can be used as normal and will connect to the master. To test, run: - -`kubectl cluster-info` - -You should see `Kubernetes control plane is running at...` reflecting your Kubernetes master. diff --git a/docs/guide/windows-support.md b/docs/guide/windows-support.md index 19ce695..cbf4756 100644 --- a/docs/guide/windows-support.md +++ b/docs/guide/windows-support.md @@ -18,6 +18,10 @@ versioning and is used for distributing the core Starling files. It can be downl [git-scm.com/download/win](https://git-scm.com/download/win). You could also install GitHub Desktop (other vendors are available). +## Installing WSL + +Windows Subsystem for Linux allows a windows machine to run a linux environment without needing a Virtual Machine or having to restart the computer. While it is not strictly necessary (most things can be run with Powershell), it is **hightly** recommended as it reduces most issues caused by windows itself. To install, you should follow the instructions from [https://docs.microsoft.com/en-us/windows/wsl/install](https://docs.microsoft.com/en-us/windows/wsl/install). + ## Getting Starling For simple usage of the system, clone the `Mumuration` repo to a working folder on your computer. If you are doing slightly more complex work, clone the `ProjectStarling` repo to a working folder on your computer. You probably want to avoid @@ -35,6 +39,8 @@ git clone --recurse-subdmoules https://github.com/StarlingUAS/ProjectStarling ## Running +Using the [Starling CLI](../guide/cli.md) should reduce many of the problems. + With Docker installed and the repo downloaded, navigate into the project folder in a terminal. Once there you should see a file called `docker-compose.tcp.yml`. This contains instructions for a tool called Docker Compose to setup a set of containers for you. Note that this version has been @@ -63,5 +69,4 @@ routed to the right place. ## Things to be aware of -Docker works slightly differently in Windows compared to Linux which can cause problems, especially with regards to networking. If you're webpages for gazebo or others are not connecting on local ports, it may be because you have run the linux docker-compose file by accident! - +Docker works slightly differently in Windows compared to Linux which can cause problems, especially with regards to networking. If you're webpages for gazebo or others are not connecting on local ports, it may be because you have run the linux docker-compose file by accident! diff --git a/docs/img/ArchitectureNetworking2.png b/docs/img/ArchitectureNetworking2.png new file mode 100644 index 0000000000000000000000000000000000000000..58c78cd66f86edea5fef6574264801f4585c98c2 GIT binary patch literal 201660 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

_~Q<%sY$zibK)P((!HxfoE9^y zraR1ZMq+%P?q(gW=oY`n_P&#!e&GINR|6w1mVYIS<-q*g|a@X z&}Mt+IBdJ11KqDlBZkE4^0`*bV{rFQmrr0chb3cQOyz~_ONM^)vsVsB#Y3KTnudJd zm#uq4R`a@E@J+wQ04QPf)G>_b(9#<~@jBPyuuJVWoeab4^{2)srBKKzCobZ5vp+js z+le7|9?aWPZ1bLW;&Se_z0Oq>`$xpMZ40`1<|oueyw>6)tO^R%=B)}`2Oi8Unn4o? zR?*0#*j87VnTYbDTUk^((K<5t4<9;uv!SxD(W3uz{8JE&Bh;y?LK(lfQ4YqAbg$%P z4BhTAg06L|@M4E1*MuvrL^P0~3E}llXB+W2T>LjfVC;6!WOh+H-ZSeoNo&m&y*riA z-7!G+QxdNF=-fJkx6Bk|cMfoCwI9O(!B{sImtA`mvv{%JcA23IXteM8Up(1Y%E zo<0v&{Uxg&NK>T51v3n91~u|lttZZvUXA&O#_`BrW#at$kFt@A?!)uO#k#?Bh0*Fn z7C|^Nze9QG3YSFEUm8gq!(8{-s_2N28X^ubXx?sRIWTTIawj28MHcrW z%CYrxt;2Wj72RnXcUz8v}wkpG7irYmlF- zc4?N}<=Dxh3~20eS?uN^~MbKx#Ry?lGX8Z=a!93WMAOBCgR>BgS&Udp@Kmxm@B9DKNRv#U_D~)oxX(6 z?noDVUZ)H!k8R)f_4?X5e)vjzX~96mJyCR?55CwZ{us7LxNauHXUR3Ar?N@$!_Ohi z!g=&LSDHEs=id8|BFcu4yua!C`hA2O`pUx3+6R}@ykp(2A|QImPB!lI>iJm$!h?oN zV#!PIEW}O%sXC|Q)3|wONm%X;wYfo1nX>F#qvd|xcVZ{acwBV*ut(?)Q_R4YVGlyT zDWOHle0fL7FY1g)4b|!8`6}Z@S5`94D!=WPBRE8S(5cONG#o*+w|;Z>=V$$NLZo1u zaF3~dtls&ZyBm{GFPns8F0XF=zPBExYxq|>(wDWMyk{xS%eW@t2a~$r2lLySd}cP< zc&)33#23SERh2z+jy7fdx}lwa>#0J(n!e9WJ(& zRkahcJ!G1=xv7GxIIg%>?6X!kmq$9|?|Y4-WqQW%4obCzSbH4L&<)HRIP|v89lNT;D!orb;G#Vi%;=>qPh7_1gdtbYW9v`i|5E%>)fZy zD6_JwP`vitk0I;ZS>|R6=HKsK0XjvZn?nv+|2;<=ht$}~KV zq_^o^4{TzTE1Sp2vS}wpd|5-+Y4gqOs8UAD2C7vrF6L()MMhB=X1uVwPGa~B7n}6( zS>?%-31#MJk-45jF7ZXZ$MxU186JFT9(v*KV*Mu`bZB;KNbuJM@~(D?4?g#xH}Gw% z&&+x-sdS)N-qU+I?#ke9t2 zogl0|oq~M4uZP;{VTPK8_t>AUw|%D!ABh77|tB zmu9e{n~*mfsMMgwmF{MLwj;!iD?#26UqZ+eo_9J^(cSdv)X{_%fdH-7;)Xk6;Ymv} z^EUfmow1GEz8;R*3H5uDVwiSLqQMRNxYS4+)^&bG8qIL<0fi5j^Wz@L7qHcD+*(R; zb}xCOw@NcJ3eKWyuR@(|^mHE@7LA&vEJzqr-uTbr(+f-{h-__?T zI|sgcWSp{I{(ruT{h=FuN?0qqgCaHKX{$-I4f>Rl2WIZE5#D9R=zL1v$Bqk%chlKk z=(3uJ2KR~+#l6dQ8^mO2=9Ten_pUG-N*rN9ID7);4iJu+EB?dzb*k$x#H;3Sg;p?utKq@7+Xjdp1nrq;nG>>_=X8*Y$dk(EjrXo7 zOOf}F-`{t#T8@{thlguBtzOYfcUx-e(z}syM*2>_gtV64c9D6w>z8XUE6*iu>bu~s z=zENzf)&ia-MbQ=;w8#V`bg`LI5y_Ew1gS4PtlU~!S{ldWX`WRbB`BYLj9E=TfZ-R z8H}+cep!n!KWT&)pMp$5=}FQxXa0C79guV1<}{~3FeR*}XB|IHp)YJHezfwud``y5 zuWCb&R%NRmIi6C|ta7EHO(sK6?^AgCw^alFejmV<*-tfYe_8ykH?*{x-8}C5ly=0o zOxN{d4EaTUz5>HZZP+sPhuU)F4e#62r#^XzuP)u-;EL#JoL8O#nYyDW9v(8-j2vz&ufm*MHRxNT?r<7sRb!o!&8KE3ISQ4B)w&nJagJK1AxoK{&EHtdZ7zxr4VNV8(=P_P6Pmn*y3388U=v$o7>4j3OV}&= z+cpE2SB3+>zbi1}R2tS^ddZS$PIv*c(wC(Cea>^wid|2(Iv$UxiZD0)QHaBoCl0ze zmH%tOboj|H0~L)w#@|QrVPXl}FSCRY_&76wGs#8F_WrpT?z^kIzRJlfYXRb#UayMt zMQ?h(n}77?1M0iqsA1~Agb)tv6K2hSU8V-i;>|aij{hHV?-|xq)`bn@Fat9-KvYml z6cnTg(mUvgB1)AG0t$wXh|~lEDk`0*H0iyU2uO#ZQUVD@dM8QCBr z1u1w z;1pY*bKsS=Zr|GO-X|q2(`|5^5d03YQhP}*%W?M8TG%1DY3_C=_m~weL{j4n?vRl> zv#z>1PX!30H~(%AN%lkLL&&iQK91pC=cm;v#T#7E#%X+S#S8CZt!o3nE#M9*#!<`iZRb)NKPpA;m@iCkt`83%dlt1H5B5z))>3dp z1qgH@^1XEPA?-pXPhA>}n;aubBtm%g$YYdY?@seMuPm#02o*~@BvX{uXceKT7vngN zt~~x>cP|$F8HE)*uOGr?0N5z3W790J$NGSAgXP`n0KsJw@H=mI(%2*PCY z&pO_=v3{_mruYC~g%ndvFB4Pr0;2}{g?4Y5%r-vyfc-1RC=5WJuJ)czy51T}U|&{R z!TMf3WiI9wSQPMjl+h`heYI`@mQF#-S=m1vZ=;nXL!lB@M`Dc@vpUPPSs|^ft-v>~ z&VLEmGtfT)eOB+-i;y;t6kmRe6UXwuzcR)op$k0~g9+*`ims6xuiYh&SSA^@^_jw9 z`A;faJAOo7g&DFCWzw*EJyh*uj5~1=?P=Adp!n)>f3L(t2|kp!f1N<@qs>YJSL=-M zn)OMw9j|^Mjrr@DuOq977Lg7SXuA(8rrkkvS#7;*>!i-Lr)L9`6raK>3Y~NPfk|F( z(`47rHHvd>*D>S@!+Et@Xaa!$BO|%qmP@o4&R)KjjtKs_M}r)N%8N(!cR{b7awWTA ziwTKBdtxixs6ATeEj%nt#7n3qhn+|KBMybjo^V?K;5ziat;2QklE`qyT+x!Mz}3&8KPZq>Ifc$ABNJ&Ixq7g z*G?|NQbH)<%7%{h05lO9fpkFLEZd`6i++(KvTJGTR2?&2m3&Q91JgM51>1gSQJLi% z#dAq1u9c3!zp*weP47glk!Vx>1){>vuQvE$<0C!;8hm}N&TJ^NT+Gf*#cvp!rc_m{ zp71IRkeVe5x`6h=)gLsDomihk(RH>Ip2m&i_l9-e#5vcGH1)Mr?WE7AsO7}>W`qs{ zp~ms{@CRKs>aWW^N?PW;<2M6-$M9K(_T9Jw9&cN1wbn-td7BA#?S?%`APQRfZ9#gg z0N?|YUIIr){~3~R*z^-E7Q9}~<>B?MW7H?Jp%?mEBzyP?cMw=0`oX22xmll}{yKQ) zjmf}yeUsP)6`t-2=o&L~WcWYUoa!Evq9N$V z8L3kk0#3EAjU+{R=;czDpNlP?zBUdd-SHh9RpEatB(#fkUSC}`AdrAGRHR4xNq?ue zE#{jv?1T=lno#@(R8|#>Y!jnINF9VTY={c*nKB)Gh%JTGgVseADS3*KzZ+&@Wjk_wn}={4U7s=aKuKblL!@-Z#=iH1sXg~@)X}PSdk-j zYzXneVib;|bqY5)9MQ)Uqg1T?c)`r33us(q(`_!4C9g%Y#hYb&s}HU~6i)DeQ2%!n z?j4EnlOwS8XdrS{F!}xEerf=_*e*z7X-L(#WTPITUtYVkSfAc7cVj^1bNpgu({Mlo zH(%f8syfy4k&R!EMvM3F3z)5*en0a!CE+-V*IMQeRn&`l9-sCv4uh;=U#!WBE9M?V zwpw&s-je6BYiX#w?nIxTt0Sv!`o+5UaS*-~C4UnR@jt~3)&LF=>K<=62>94JQ?l{I z+|C!-pM^D)TQ)w)wq^C7PD*;bTei7`P~~x6E46N4~LVrIEIT zFHsi3JW|5;fPMWH@+V@Lelgay$SU!&-XUR!g;1KF#mc#KFY0Td0R;oW%e;~|l<(U~ zC6>7^-HBt##|P!@dZCY7S2dNb!gn_Z7s`Mz@M9~l8LNSRJ~<2U7jBW-CE?6vWx$NK zxX9P23=4tu2ae$&NbDurBAO-5D4?9Ud$KA1(>JdQv zGo%BL-s24R9-SFwVUY!+N$Dm}ta=L$7F5H1O$Q>Lzeuj=Q7obCXX=I!L$MK8yVhAo zUjL?rE!T@tu1WAR7SDhc7`8t50141Z+@8|-lKx4WiI-%FjcW_>Z}pp!(OxTq2h>d2 z)Q6b$yi;+VR@=d;ZA{1nP<#SBpi%oI&!5TE%n|JOEyr3_x?ht;{7LV~o}FI%T#4@! zcgAfs9ap?J&XA4kI{@BeZfZ`fpR?VD|1VB5X(04IlZ_8>Hi2>0Wm_g23((#1kmHk0 zyR;_V(gqT`pnjkuJh-2h>_?H7{k$iyA#2hKMc&z5mG`a>+*e*@E?FtN?3>kgRJCqD zq5quit_}>l^n@qZnMZ0#ymqO}(dEToVLuu{{YnFfd~yG8fkQ@Hk64ebmr4&roprVJ zu=MMCZC9obU120vaENS8VRxx$6T(ClmlpxKLq_QC0EcgTks9Fvo9l=vXKGa@h__EB zDr&Mg*kCW)AKem(2sVs0!DZUz{ zQ*y>o&}^qye_dj_OiDXrJ6zXN(TY<57bl znxIW0JPlQFavgv+Tlr0&=Nbv$5%R>htLbcZj#Z;(phefa5+w+kWY}DD+PP8hQ(X1N z!IKLBk2bssR-+8v$HN8=j)oKtD8ZiHvV}s2A5#Taz=>d?4X#gWv z@Xil|Qj?ARWZ$rzCxE5RfZlC)0wp5SEF2c&+XA~A?B(;Pr?(av+HU8uJ4{aCG5Tar8PRw+P`;3Bao02 z4f5a7U6C2W?nbN$=mLzYfNs+~zF&Hws>UGIr1cZLX**9)&HvjGL7&=jo(-EDQ{Ot- z1=ZG{9}!U7=oe4Q#Z+N6ixdk=Qx)ILK3f(RD}oY)!~=R~z5!&dp9EyrZFi^x-Z1~! zvGYXqC7|B%xO>0eQMGpH)ahw1VT1uBTuMjnTMxqNJ&S*dxwj`CI8yfKmPhQdZE{Au zfkw&q@T(di@@uC_vIs(7v*Dv+NR6S2*S#~*gXI<`!UB#1Pxl6#P*>?6}4Z90@+!T8+o{g{QaU-YK zk%~uu-os7bZ--9h&hzQ2@tPFCWhd9Z3ZZtZ6*<@4^ZJDcS8bV2sT*<+mo(hKyPgt3 zf&2zoj1lmX0iXJO<@+g@hNhhlC(`TIJ{iDwqw9_<<&k7#p`72G51-2i3UO_B26~m# zSLE4#-D>auP^O9-!0kHo9Q6U>)ZIg$DDLM%&V*zM>lze0Y&1fV_&uC$<123{9^ z5;%AD((!5j*gW2qiOD7A!eP&q`SQUF{P#eo^m-np zsC&|$a9_Uct+mvPxAPN@-)+Wfl~#wU{R7B}-8@GDm7vUcPJ@U!#~Fk73@JH3VD`|d z4F|%+Wcu4~9{I3ex^8K!>1fU!zxg|GZ)yN~waLbdlOiR5-<&RTU`5O+O7n+PlsNeZ9(*~qN4&ddZY3}qjs$h{c(y9}2#Of% zs8C1b3=srz2s|s9Fbz!9T_16ZS&o~}by6-UTgFVXWpLso3 z(nT~pSB|Gbx0#EE2@?xIZ`;cKU;jgBD66@ZY<4%VkZzt2>84*0nJZo^>xj_8VU!^- zsf-ZCCF5#^$Cx9<$NQU3WcyPkiG1Uy}yLiufO6#qulq>>-LhFgN49xuyS)1y9x1YxmaDa&H^)GAs%Z z-dpg>ugvOJD=rDd4m|jJP&|YQ%REw?Ah%xy2w2BuX|I*2qHGW5>(_XgEDz4KynC`B z7Pz_S?fs`$%hHcG?F6R>Dz9wb7G~i*Ot>`{3b2_$|<|QMK$Y4>E+qhG|ICI5@>vlZWZ}KK$r6a4FX`(R2Yp+=E%Vi>y^{B8>~lNb(!lY47XPh?ex1$ht>}b2(57J5Rl~qQggFQ3 zP1aA(yx5K<27H~J$H7$yngDXVC(Xt}9~<%9)cR@vL7^M>$loOj(-t@GHI?g3HrUSp zzDSer?+wGj6$rtE`!$yM65+uG{&QDbI-9$?<>?>ivZj9pj!&46H(K1hb+MSeHDUU$ zgdDwn)?EX;E(AOx?{;LJRnuEy#`E+&)r$A#I^U<-#a0&#Ix{8tgfw22$9-U*@*D@I zEIx>Bx_cegR)7kug6`7tQawAf3ds`TxAm4}^&Kb5Njl)UQgd&+9J|TES_AW^Mp=rE zieu2e5_oFBR@S=WaBURtUGi^ige&`xl5@(F$uAC-8bsYioN<^s@0lUvS9s=Cqc}gu zG1-0s>-?6kJxJIsa7zdUQ=Fa|5}cUs8kB@(zqQtW>2<Cg_!5|#7>8V-`S~I01;g)x32=aWn~kGa>^HwkZdA@ zi+;u@MBYEzXq~zI$U7?!!m|;H-He76Ao(ict{{=MZp0~M@Eo>| zq&ihwG?ivh5NOp+(L1kqF{8KG!f6!t|5_luYlMmVi$Ucb#K;5&&GUHRlH*yipeR-71R8>WK_!fb z7ibthwuiQvgVqMc!P@rc3Y&ZiW#Uopw7Urj%5<^N12KqxkW*;KAD2^h<^0oqZ+oK@r>-66*;=Q@b0>b?PW73e?tnM$<#~LNm*CbZg!>v>!xmrW4ba5|CKbict{8*vK*?j6?#X!1Y{^4pih^tOM9o#CHZls@WD`X zzM?O*eb|~;$34!I@L2SwC=!nYDY;k%vZaj(NIVY-3EzB+Ur%!bw*K!T}k zV`l9Qx!Q$(5fW14Oxk;vlKsXNPL?;wVt^zW(AvA54RZB{dJB)VALsTs!XF<3Chj!u zHpFiP?)IxKTg1x*QtyX7M7!H@fJ$F=(u3ssl1d?-_#>Zv*I-yr%tps%W{mlURvAJ8!96AL| zyxX_k0}T>Afk)8dSc1)o#AeCKOqTpry(O>+mY+1835 zxzU-)1Wq*NKybjW#_r&>wOLvKS&s9uZQDQ~ zgXZS-q5;udyL-6CfCnb?CjKgx3L^DO!02EID{#B^kJ~$WnHa54vn1+lo4au~jTC)A zo3NCvUOnp=2fG@%jDhVfD`{_8d{GnUEP{bKYwxQ_-UesevXxY#0oa4Uo^sT=%SI7L z_iisLVaZ4M@6!4rf#pOoFKCW?8?&CaKkTtZwSn+${mby8MjyYz?%QzdZ1o>z8B%1( zlE-1?`%Q*`>gB#DPyCV*zlRLs_6g^Ji368dA<`o;pqPW<|F`S@oB}W{`_#bsFKQ`) z9T~sBT?%Db~%%AWHL&>Gfs6VAY!>Mi;Tx=n&z3_s)o{O^O_|6RH1bwJ8vbqP)= zxy?~s*onVyPzP}p2Ol{EZM78*zoxF<;gc~Z*rFPga z=g_NyRUGQH0#)E%ocNBR%Zt&es~;SvC^f?=d2{@Cf9RJ#JmIYPY;%mILEKX9G%ZnmSp=l6-bwy{{>#z$27fo3)o<{f?v6WR zejbTBZE~_?xj1TeHnOsCc*|}PA`bcHnLt`|dk@_y+kQUW=;UVB|D-WKRYzD`>u2XC z>>49hE`?QWEre1UcY}9lYLFVboztyvW`&*VUrZ`-=VfK{x#1Q~uq))jp2NHk*VnX% z;(0OCjl!-nymt|{)P|e<2C}S0caKHLwT&0beavxs4V=FD$70<*)(WYZ|GJi%03tTF&yrPDsbK z&XjMkv1g3gYn{ZjD^T5MXm++J z>RnP>as?hLj-wZs4PJ6i@JWDoqLXqwDs|Y4p((D|DsDFSq`&o4FJIJmyuwp^oM51^ zT5zsl*gQ_=)zo9h+Ca2MWMQ9rmR=$LO^;}n`_PQzQbob}bxEN{bha+V_pIw8b|b=e zU3%&rWGgq#;>&8`n<$U;^|9_Xq3VwHz%gFP!-WYk6n|28WS>*5Pr&av{dMKr;AH98 zLEO!82M`pr`s@z~s0rU1xvz5X*DoTxwq{3V&)^_uX_z6D@BIeKYE!OsxnuGGO0IU}ZjfL~iVSK);u>Ul-1?uWx3~_CXYadOyW~=) z@VG$Zi74=B6;kvpn+#L$>wNdnS9(y}e3(Om050=e$jz&5Jr~=HC6;ZfEQjvD(ig}H zwf!~AMHr#`Vv9(!k8Y|6cyt~;PZcg94N8_IRqGFINI1)~2na z0^|$^X67%HZHTLrs9MCuOZIJ+=aSm0Eu~-)RgSb=JP>_~t2Mo(2BX|wgGXBPlPkO) z#g*QRlkt9JxPnBwd>xDHL90ofRn``WTeRUDA`6gU(4FljB|?ybnWDG@i_IXh~n zvsMIJeijm?QI*2AfQr*DL|5tjp0A^mZ9P*5VEz8}s9sgpl!fD`^)Qs_X#f{O7W@8| zs*RR8cyU71vQ|`}OZw0(*6OG&$;C1{f6MSqUr)KH+qim$z{$$eWZ$m!-&)NuxM=-R zRa;U;r=S#Rs0HkO3~ie<=lgKH_BVP1?V$rF3YS*s6s-v)DEn6n|3SC`esnK>N?@?d zPziZ0?g58wxroJ_-9kB7TdhAGEb*W^N^>CB?OL1@>AjYmbLRWyq?j!`do3#`KcKG# z39Gs{E)5fr4tQJLnb%R(@Q@mP$`Y>8ttgiva0}DnSnc}UVtirihPi7|Cp${da~}n!}oD;fAu+x8|U9JZgbEQ zfFGCM`3tA^p2NQ1E!yz?7$Qyi8!F%-DFmhZgjB*(yE_AL*dp|+$1{}y{#uI{Xxh|9 zuK9r^Alq;3m- z!O#qp_mZX&A>%VQ+ycjEKC!56tHrUIWK4%!H(ljUbK7nY7KB4T+xF2m^s(gS=;5Y~ zzK*@_%8skk%bH`mCU!TR{J!0GzrKoJ&FPx6`Ik$t*b(PQI1Y zgJxB<9!rGv%xG9|PIP3Yt&c27O*InzhC7yflqFn2Y&7FC?;gK2l}MG=uLk128#AYf z5R>V2`Cnz`%Z=*3x9CeepY`$e=^% zk>Y_=qM$hRn+Ij``W6$#v?-%7AdSF#YzC6RhRS5xUOz<6poPHZ4+Qm*c&J_9L%mUZ zb?&0D!8~PRg0tVyH18*Ek5iP8zg%7(arCj9Ra)FH;{{elT|>MxTiq0}A*&GpxG)Y=M6C{o#`qz)mPQ z4<-&*ImWq|lx|jBdV9G?P0wcqwjxQ-yK^(@y_qrN{-q?_NB&G#Ytr2@_0 z9^GkG7y@c%8S7uBdg3WL{T*!y1hYFEw8a3oDUUE{UHJt8GdeqNJAA-QADgB5EoAWT zR0fSHknIsQ(_vdWOq|SNn)D}80XI;##=FnIol&E|m2QiV_4`>i)CkM}H&U3K`8sv{ z6(Fm;`!-E?86OI>u;(x=UXPZnqikCZ$Qf4nd0P_lxHCly)7H-)e(JR*7`QzO1IhR4 z;_O$)s^c3Ld1y#Tp*P$8U)TH7*@B-~f%y9x@)*+9(0;UE5P{lI@zODr3SN_j z&e)mPP~9%_5<1it$wcK{FQJTaG;fVkMTby@2P1c;lTABOMf>pTHG>i9b?5W=ru*~G zlECzIfq3fxh~)_e&YvcOw&I*7;uBlWOeYoW zM1S9sJ^As=d=fKT^p+aVY+|Ee7poxvzwaP@i+gnwKoQBvQPn}+h_;;palF0XA>*JO zI6-B_o7PBMHsfd#Z4@-gKtc$h)6fm6z+{%*&uy6_u!V5(k#Kv3p)`!xK{!q}p_{{Z z!%_rZao%~Mts$|*eq-zNSZ32tEh;~a-?Bbvtaq4ds*{XZPSdJ&$&R+FzcF4BTh|2b z+7<4AY`w)1zo#kp>3(L6c)15Az!+Hh#H4`PD@UGj+gTl8!zmG8LT_4Ip`RV=*RaufDOP7;vp> z__Y@x+MD&%6$6;a`Hj3ozdkKn9U;P6tRhNDTufywzg=Nc7N~f)j3N4wSeBMGVEPQE zDr%If>ZM5^I5mT)OYA}#20n4KHd151&jrbhOenC#th_8c=>GEXohA}_m-|o^8@e5dDKg}U$)cZG~)xf|5uPHqYDECIcKy>F;{C&nH+}| zq=iPndcOrpOs7-^`wv&=xK9Khn4|~DH?5JrO`Y2F2ce%rLOxQq0`PR>o#=TYoH zgk2aaV4!KH`NR7z<*ySS8`GA%b4vAsZRrPW_`V69V;MNYam0I@gO3c(UP`@$=Y3{r z^Ttp8?@*~N`LWSrUBz^1SA)WVrLK{!818ra&onpH`Cv=8fc`di^)m4tX5I)k`po^7 zd4;)&^N~2!2-vNs5?>_}T3xKS0vPVYHXGw7lVI~r4y`?yPgQFcO%S7`jT9^`WIdHL z=wK+ZBnOJ*mEC^oI$IIAp3vwU_~kn2^%Q%~1poUxqEFzr{&p-)rW&7cBy%_&WU&^g zcSx3zu2xs$MuZS6`u*y~ZPiZPFZNBg1Ehvz`5#ls4|uo~wCi(pJgN=DH)TRUS{d<= zX$&@*sx_ZjAaAIHG#U{E%+U^%L9rDo2M4ID~pq^J-l;t^cq(rjSbUPnK)<4r#B>G_4*F_vH}!2q4| zcguIMEDXggO_XxK3KkfMq5dh#-&;50+JRK&Q|T7{=l4f^JMAgz1$oRPzbMaE`0@4s zl>xf+9bvA2X1yjVFx$KK)6=d`#L11`bn77)bSa$bS7AU8}8` zhwS+59D~{TUOcCI|3k1@TNUYN^QQKn5hSq(Co1caAO=%V01*t>An?|)_aQiS>Ay5g z`|Uo*_jh{|)n)l(a=CxYG@IkNH)GSRnO0}k+%F7CLD>4y#>sdj=xN!sdt$3QFl<~- z^3wsVBoQJ&saQGVqYcFCfRMj z90+6YivrXRE&as1HmageSUB`!aqWL^w3F69fBMTB(i1oh#g;v;d zB}|RMwiPEGj#FKSiQds7y&Qe$sK7RIQ&!0A#ImV@#R*q!1;-Kfc-zN(Zk0(;p6Kvv z1{NHcIY+moZc8n1r$+P%@FU^qFC=-v+i59j@6JVP4zwdWa-FY;r`n&cfGyAtJtcpm zSb4i`WDptukg~)nwO8Y}Cm&oh3>He=+D(shYKRnV@uD9QkdTdZvYRmS!BW8|% zl}2KbZ{GU8m@vtn@OLrJ{|QPaMSfMYf_Ue7nMaTe3-jkHswNa!W_hvHFA|MayX)(@JO}Dmizm zAGOiQI(;lT{`>|AAMG0XR+N$j9#chsbId&YXvp_>rGTCC|1yPvNUQ3bDHcO{&j?Q9 zK`u_w>c+#0V6MB0MOIcboC7)#3w(_0q$EDPTxMD1(%Pl*s$7`w*)!v@!&%C&%vfU9 zrXltc*<9r+-Jh$(M%54K`zq|nZ0&v;6oIr@ZQ=q zvFaf1l%gyDfvwCRIlsT_w5i&2tM{tgQ<#-5z^@p7c&G%^K^m=|KwtL+$~HwE~H$Yw~U^N!-64gFVLs$DC+-)UtKY$QVn!=Dvdg$golcsTo$~*zw3)| zEf13|8o6QET|S{hgqhWfv!VFMx`VQ$@nwz)a<}ow2dbW`&HstSf2LFGbcT;x-~Ghv z{=y${g}NHS;yj_y=2PLZh-)9+M6^Azg|XU> z;$^jILa4s_xA+_iKHw!O>VvlJtm*@TI&{HyhOZmmC$As%HW$x_2|PU}P%j3wDIWRv z5c1qEv(K%(SYGCtsTlmGK!`N{VOAftJin&#F8S5DOa1cNr>aPAu2mtPj5~;yOS?Bf zRfrdymd4|+Nhz8<@OGo*5ba|(17?cl+gCcw##>Z4WW5ymQk%^{A?=qa7n4?WzwvaF z8$;GFc2=EJ z_EM041&cfhJyjHjGs`JcOU*gS7kMlxwF9#nc4MnhoBmPaeJL`X1TqNhM_l*Tje%}S zu&yrRq}c4W%a(BZ4=j{vBgXR&DFx%+jke=1{z4)K+z4zx9(JlMnWt$8jH&QXc>)4M z=-d%PNZLm@kcs^hUar43BA7jmRev4#$o#u{^N1&|_$M{%A2D$8;!27Udn~O`P~v{$ zW@8eN94^qzE~2sHQ6b~yDxu{M*>iW&r(HEa$kokp1a zbKOz=q5#COy-Z{L>I8>&X?bc$okI%lrz>i_ETbxaxZ4tXaE?_rddXaNG-Qu(vLw?S zkE(OAsy7vjrd6LZOW|2W?_3r2iHSd&xb?g@FKO^N_1*6Y8tG3Q>96VUz1Dy65=-p< zq}NjU=HG*CNjQWl48tTO?3WkglxV5zAEtO z51YEQTOPE;n6P>!lV*3?ELb(_8l|Pd^UTR`X-m?uyKqTV$9RJtiW_B7h%eKj`ud4f zNVZXbP2yEAZ&4a+clJ z*{d7ZXqVx6TI?u;-2N)}%P*$<>g10Xz>egs+D0zoP{z5T%9&bk+#uY@uGi`i@P2r= z<+@}$7|JPxLx}V3hV;6oJ6l$2&ieu|{6|JD`;FZWt4EED9nNpC!&e>#-d2yf=?Js< z4cCf@F15V3C^lT%Fl4Y-3)lk;+jCRWmMdT6zJf|79`J@Gbey>_s$Z9PRRO zJrbM0+g$E8Lv>2Y>*_9{;Ae_$4R@yGxQ_%J{l6%S(a3WwUM*dg%`vMiEX<27b2aQ{ zugLfEC*mSw4i~sDhbuQEr|O!m+S5W`or`kk@jX-i?wv-@5e8Z2CZ8WUL{STc9jXE% zd&F01TyoubY%sJ|K{7ZK^T?PdP367Jn@TQdn#zTT%O)RgjIKh)PcNXEW1c_^lupl~ ztA}Z#B^wE`YTb86Q#Z6@?(FRd;J!=zl#SK>PFJaqSGI@Ttc=tGftSqrB1e%C^!AKU zMG4WxPvwYIA#|NgkqM4^b#dD)L!E4GMue*Bv&fna_38{U$y-qfUJ2D)V7Q`nAf5BZ z@R~^#OTh7D$rSWiycc@Sc-CZ=f4U(|K3Bjt4e%eyCM#`}32jpiz($vo%1)FX7wAt+vmFsozaz<0>q?@sFD$6srY2LG+7B#T;yNwX2>3BKJY1Y-VS6*_5PfZgx!8sT@W{EidiHNmqdgbxi-mV zK!I~GIL-!&?YXk=^G87UM z6PsyOEhFa6{YaX(@G2Que7{l@(;tswe31p#7t**s1Bs7}&-39+iSn{^2z+pweQh3c z=;Q1GXAc}4?QMIQvQXuv6EnQ%)+ny8YMwfXE$b?VDU@MlZPL)#j=0d`rPcS*v0dY( zRk$_gBzeRc^A(nxQ0q7;G;ZPfrR(>?jWS>_%(yAvT8X~2nGO5Ynre{1-3OOc*O=3$ zIM(*tjQ8B%*}-sEJ+Prb`^#2w&q|D!^MEZ{>igD*f>e=sd#bw1yTQ?l_A!PGa35T% zI~aW>Twd0!iVg64Wn8N}t@a5TuZ=ROG6A1K(|>cTFJDW|kOp%ZKN`kw4!_(>F7|d( zRnN>Q&xX&l!L{2|iq_3sWG0!-r%p$wiiLp3?76SM#>e6_p4{};;MI1bEg{3e?n{=$ z5^cWjXPqmdKl1+vvhL7j^rDf|XP{}SS{~n$mF7Mk#EhIraT+hmeDc=9cM02OYDX6) zx-_pAIeXD>Yrh5=K|Jx##sSL+6he>_m|6VEKsQU^ptc*7ZcrpGS2FN(9Z9cKOVF^V z|ALe29^BKTp5@ar#YD#ad#k!b8#1Fh36^Z+?BSeiE>gNYg>?XpUuk428}W|FcloD@LW#)Kh7b-xTfE8$7`dXo51&Y}5u;KT-EE>?M; zh~x0{X+oJr@3ZJ-V|mD)jMW`KRu`%>C3>H}U6MqfRo{tTww<<+35&t-o4 z^+WN5_h5@EcpopwH_u={QM$}2*;W7XCzUetL(74OT@c^O2m`)8)lu-5&#^|s}5IQ#XH-*F+70NQ?t zGdN@}_&qmAd)rBMYQ%_o|C*+<;KImr7wa)yvkj8}W`&V*10I2 z>a1LM<@Ct?#feuNS0$=zIxJa$rcc_oFKg0(oUv{l`7+3;H42%q)upyWN=t1~FBH({ zNV!FZF^Gnby)Qq;&GlF6KN|lV##?nuRcBquF0!#l>ZbL^o6k?MmU`PU^mNzsf1zM~a+JGbtq9! zg{7iGTD2WtNKQAjAKbWbvT+O(#h-$|d}MMT;Q5oQdL4c2!iIMLn$$Y{PtK}g><#w! zLAl?^YmHluQ^3cH3L7ZL5P^+?$xr&q(nvUuB59T)|ND5DFwqJUk5)37`c3Y4OA*To z4`HGu^(mStPmjUvh}KUr>niMcRM)jerdj0Fy5UD-^GtZH3(|f5XD6%W<5{4tKaMjZ_HXu7G zk1w1_L==+_E?jRssy|xvDGaRR!j5ObG~AFr_h9tnuUK7(iYdHH}q4*#Z9t%%e`1>(Fd(kf={dzYkgn`d{t-oS@Vt1xlpkl zt@p}%MzEKie|MqkQ8ILuV{B8gxzn1z;9NR1&&7lCsVB)u9H2~pNh{O6nfc=%eGRRVnvy)V4o0t zL}T0+suxvjN58yWKhe||OBZmp&c(DP+9sqiy3W~tazA>_LN(O{qAb~cJ=q?MnDSAn zE=%d7dFw}+QHzhnUId`(05WhdF$Hv0>?M*#;+cKl54VSM0ZoX9@5Nxqb;aix5ye@O ztJ{fac1)!>+bQ$@fZKd94m9i+{FBCwF!Amnw>Q39mL@yDU1Sk$|Vd(T$i`|u3M8^Fz7ASBT$t-v~;h&+ifM4gjRZQiRUd1MwHm?>s zXFKOOm$<#1DkQzQtsOO0MT&}DEtys_Ow}*=%_OH5oFgz13%9vh<`KCWm@gUrPcj(9 zDDi3`4%c)og5uO*5Qvd5eG`dqQ%xkL+*mSb;q=n1vThVdJ+AQNi_%`K6pPXa_n_18 zm-#?+G6i4T=XcqyTr`b+yiK*-BNKc*;*R~MI~V|vKB6N^u3hpU#4;f~_x5%5Ury5u zZoFAm0+gNqab1pIIHtX5!p-cPG~2k}4Ivu{=P7%LsH^%Jbi>Oyjh745@TCz9T7MB^ zd%CP7N~u)S`*;O!*N=CaPM(p53?!gNM0zJ~B0&Z3Xq0-uILMeUDO0Wte$k)xm z$}neGBf4g&uFAcHY(lKOH*Bs(Z;*30FLZ+p8Q?EOs=U$H(OUKGtq~ZrL*+MOeKE0k z(T3@+h}(Cm@v-KRrQ1Ye1#Sr1lQ#kXA`ye(6IVmZ`#iHk?eN3C;JKNQmK>Q4+FR}+ zgqz;##TzL8wB4E@uCNZs6)jR)lPPxWs@kR8of){A z67?nUsg$$MynZb7)ZiS6y2G%hB0fU7Kt+}Rq&n!nF^mf}?NvY+Dkd6lq$SV$bU87{ zwPIp>hcv=vMaQP)pIzY>FvMsYX;&=-<~fyXvpWFi zUWp25#|9`Syy61ile2VAGmLcR8Lmy1zKkG$#`WX`sC{x7$Xm#AyMW&5mE8zx3rSo1 z5m(YNoBAV9Afjz=aP(n>DxQM4+@&H)iUWA} zLGFkwyJdZO2Iq-Mq9xZIQ|>=^&bFVFc+Kd=$L|{Cf%QKiREZU0%i%tKV~Q|y>_Bf4 z$7GXjp>ExYIZZqFhT{GABw=goeRl%trldYXA$eY)&x=Lll+mYS)nnl8Mhw#~icB|QYv7L%OnRgPk)#t8s?$famFU=UQ2|M>U zo&c%73s%K>^@r5ub(LFl&earj57p(1zbYIb3&Fkpn?ic7`?j^ktRU}MR}12ScVtf9 z8;!3(uN+XV40!InJ*w*LmicJ;iI|aiJT*U+TxEcWwPR0PMrqyS2X?}Lr-@nzN`>MR z??z&;t(*^86aHIVWu6g+QI_0KIe+>~TKC^P6LW)S4=f;6wiZ(J0yFi^!)zm&gsJl6*|5oQ5)4$&l2G}-neCcbsGTy-xy#8_jMf8l&< z?LXH;Y{l*M#7NuGq&F_o1PB?qpElp-!?(|TUd(S7B-~t3_xno25O6NEk*ZrTXGU?6Z@M+7Nu?p$|lyd}||$Aca0XT)oe z9Pf~ZUgIm5Y?Tx@sTFryLw{J~Zszk<_)#6S{e4bh(aje^HxH{xwVWR7u(9O`S(7JH z6OG)QQ~E-HmWyy!)a44l|1P39Xw2R6vNfWqvcJsy!an99DhImE}rQ+zr)hRVA0=eH^WAxZIL5k42Qnphl$U z=P_B!n2lvfI0=@H2PY|95Y~MMs3g@Z*kFSMBG~)!^G+;{$xqo6>a-KAXkB_*HxjFu zH@#TNzZ;lUA8Wmz?AK}s)X~n;-opBG#EzCLN`(0HF`+3@E6?QP@>wv77Aj|SEhX6WG}$kvzGFldy+Dnh=Z20C-W-RpO!FSiJUS)20E@Q7oM@>H z^|+U6ok7vZg6HJ)Mq%UfagyF!s;|U+cYY8;Wf_u?3945VrwEZsQdC!L+DL^v)Y+HU zL`jdqiEgR%7iD5`ew{L|^|yA;jH=f6dBT4-=NP_R8XoWGV3>O4zsvgp&+R+|w$Bv1 zSic|`UN)d}x74~W+WPK(S)}I&+Kv~m4EX`Za*R8)Tld|+`;KY**%t!Shq9MU1f28P{rP4#PrXwK!w zaX@`Zp;YuA@|&bx!RP#@s5cv;_(Few3+&r=EfmhX?zW@KxGi%XQAU zYc!xFvPX>D@|QME3)NF_iR#KR?mU>Jh|Lh+u1h$6!Y}X~2qk*5nb#U$w(%w~wC_?$ zRYyp}#4WDH#Fbx4!q@seU0+>@(y*iacO~a_*49Lif#X+Kt8|J!in&lX*4{YK8Ild( zsxA>Xp0$3&sFAtG4(#6krNS?FIuke!E4NCgCQgI-9s<+WJRF`YZI+dVH*0<^qqwCc zV3>R5zuZnBGvJXVudrcSyz5f})j=t^Qa?E-|eTCDaMjd8oHs=jcrzdqNEpN4(#73!7V&QqATc+e11P7EzjUWH`W`6s{{{=-O ztHLaA;?mI#E~bWW{J^E_U|a@xD#Puzff)Ph>u)jm5Yi&?onFG{{f>_3s~Pe<7TcfX z|D!L(%D5-+lK{@K(&Iu8~DCtcLX@~@fn)cPGUR{iO@bj{Ze(nXg;9b=4@Ly*6$A>%cRwmbf=mxfp>k8}s!B^r|Ip1vURT8-RgxRuSZ`m|8n0`yg;n?^kN-p4cSbdp ztqtQa1LFvyGAarZD=1ZvA_znUM1@eK8KemUB27900y5YTBs!GPdj|;}DG3$?Nf4w) zh$IR`AfZS|CQ1T-1ff+S$DRcC`pY4Jj+n0)C3_}89U-F7vii}0b!@;5#{#U81O zKvX3kC(f5v^~R^>J?NJljmo*IuI|BvnF6e7MrPT}eJ9(ngr21XbSoTo=H7nn0U)=| zl(hT5%2)zP&&SMCpsErkV-42MjzZ+cH3O2)_)wxsM$b!kfdd&sFMTxjXOP%xTGKQQ zl&|rpDV96-Q17+&Lh{hl3-Jo447bB#_a!I*&Vldr!9U#tZow##7Ma{Y%bn-LLHNA;R_+t9kpO_kI$kV~~17MoUsho3*3NKxPO$^d#Y2s}E>QHmdTY&z0?CHROdld9PuNwYXy| z6k4Ovb~p9Ya6otf9lSl{z+yp zXSdBfu8#}OWNaUN*nSPr#3-yE{=1AOi8fQXhFZp*eX`M&1MX=J(&UpBW2~DwF?-t+ zAgsYUthP>LP)1Lvt-8I9Qo%gvEl;J@NFgOFvP{>i_y8Hf_fEv!D1af<& zq<5OfSc&hvoY!Yp_~ano2Q>+x`bD4tt%ZEtpi2Uy?3d=lZZ$d9n3kiFg$uvf#?M6- ztsZJ|&)lP)NDBh46NAZj!|>?U{HTUY# zW4P^3F<3JouV?x!&KV+>sPih*qmbMynsM1eZnt;mW0%SH(bjp@{J!~wc98_k+^NI_ zyradM39)KZR2!DDAk5mLx)XeNqeD3esu}(@pYy*Z8O^dxca1P{B}1E7H=#Ri&r;ha z+HZ6H&;LXK$Hhtnc0cLAsyhFDUMCUz%RKYm}JBnIbo^@q+E-{)llFyPo|3yD>S9XSxTCrce!ydf%9*nMM(KZQTNJNa* zQUmyjKF@LSRgM^GZqt&%SP=A3c!S%r(T7P+M5~)*s*WrNT-$q1C*x}0;S!@y*n-u7 zpTN5T!9BI4xx&A=$oQm+E|=Q`q+72W_CQ`Zx0VJ_{SyMGC%Dbd(Sf&S#_PitO0-6} zrse@=NQ4lsVtt@+?TU+6y# z)D(s#bmH{OXyRHOI)p`B(kPYN^elk(h@TWFPqbV8q?-3v6NVy9 zTqs9}P&{2S6&gk#NK={w?O7+6v;v2smokH?S9(Ko;zNE6h|-#QU(4+2L{c)}VW;rebg{Vj1#iRUv} zo&yI8GbZE0SKCpBk|)N(LfVX44x=Sm^m#CI9L?68-;5~M|DnQ0W2V^hVd@haVdT5= zpOXlt@e&cuup!x!ym<(Bn0nhM7Ghud{#^mUY(>d)2YBPk^r_5BELzc8MpzeAa&{>a15 z7Q((#HO^<+4WXa@0zYU@8tn6xe5R7+6#yRnDD*zsi(TUoAQW?9V?H?yj$O!T2t-V! zo)3MuweBazy;Vzrun*0J1xwFl8uaY1OpS@pf2$uRT)P#*f->wYCB_&}>RxjcNbI*$ zy!<=@2)<6Xsb*jUHTg>{e2G>P+Tr9yB(#ikoS#3Mv=r75xBJY*=Xn#5+}aBg0WaUo ze&T7~v>=KAz|-m%tHyZ@YBgu6+_* zmu6qc%>-^I|Cxj{82YQ-2COWeRBU#%ZKHgFZaUuf>s3<| zj-G{TF8b zK8{6tKg$1FC8h}2V@KKOhGT4c&9fAs&~IPaf31WQn%#f9B+U629*cXkl|G|pOQVnC z3iFw7V=+fQI)wrSbg%g3?2jBRS5h0BFX#|`b#+%}DLtFCPyr{uDKTANzMnyB}mF$q2Nhg3GL7Yzr>+?N5*-(G+l<}+_ve?kdPR9AY(gk z8UL#^2P1_duhhgy*d>tCaJ*MW;>KL@#cYiKL`%SK3Mb5M`hz2g{n@WM#qQE=|J)?g z>0^9$Ty85GD8!(8;lhBgygzi@Vi3Lo3l;zfeNrrV?ob@H!mTdsy?t&&y?@DTp3jEHT+pzGQsThp|ow#48eQAuuPSnt!`Yi~qmH^Yk} z0=DCy4BsiV{XE?hmU@oddyAosp%P-3_lqACw^+I>0bxW8K$y<&xx97Ch~W0@@F}_0 zY;$+YmHyo=V>7M-UcYawXMvXr{W6t$ap?B;Q4uruFj8E&J#&j0Q~+f)yIriQv{|qD zk46M9bpeb{b04<%o?5RV?dVnu-+P6`e8e)E<<{2F4mmV62Kot2+4dR*PsUvbuUe^cB1ZCU>Wtn>vrH988j{f|!`oK4(& zXuWyGop~3azeOrNk)rQENVte= ze3oREE+yd0@vPfY+(F#uv{&o3>joJ&PE&#t@Zg|f@9w6NJ@pxrUW_%q1dkz^*U}Gkv7)dmt!z;UfPatkbBA#s^mblI%C>>RRb^Po zrsCAhV48w_?`iYP=y31a)m@C>eA8g96(R0Oc-GoAgDgsF0h5RHrriIDTY4yvZ)vv} zR!p?S85Ivwcbj@%hK*c=zeYrvabMs*ms3RSA8a;*|IR5$XwUHJ?E%m5nrA0kQW2#Z zCA3s&Mn|~BrA@#<`#t>#`erjo43TDqZybCM^b}pbS7uj2#y5Xb_PUbaWS*}z-mdcv z8Zr<8nFQgY_RP@Hp50Eo=2vUiMx{nJCm7^$A^)tZh%+f%#N?yMqF`^_GJ2FE_6`ac zyI4#qKi*3^&&XW-iMu-PlN2pd)?3J6^fjxNYb=fnBf?-Rr_D>E2BpxL$H8$IA3 zSWP8ca8W(X~Fx59m%oxOhiLgdB0 z*1h}t7}8z=*bD8=N}@?OsxER&2LhO&VrJbf+DB6kckFSY9(i-H56aF8M#T~=iKR4x zbW1xcdw`{98K%e01u}-EVkeOlLK$8Ej}O>;VlVVhNv=sFP#U?kG_2Q?)h>q$%Ju}I zvgVNdHBNuTML{3eis%dLN0GFfA?g$=tQ>ZLJc*5y2GWiK$&t}vQ}yOIeqb0T6q<>f zLf6jz!oI{^5BFJz%Cq0Yx>VPe;w5w#ErvoC#C#g&mHhylLQc6p@FV4obZt8DpR^u( z^Kn7$J8$nyBAGgrhf<7iL!+{08?u^iiy4U7=kvr;9n#$PV@Y@gBJ3D^AEl9DB1>Xi=VLPEVX zGe70@z?W$gNvHGee&G({K3jx&Er5Yui4@L-DUR_-9WbBK@(oxYC)FWqebla3to(V7 z2?Hm|{3-s#gRo+b;x8PlZ`2wrDO%Rs&Aeeuu~)d9pSeQjWUb?ql>LtNm2f|DE`l$~ zp+ag+l_Dt3fb)WagOUK2`y@``QztJYe$RnJ=ee+0SpBVuy2RqQ!K` zA4rpRLhEw=W@E^0;%Ak1(x^2kErr}sug2UwTu#jRLbOT9$+$LE|*Be z8=d;dv3$}JNS=`rG7~p14)a*`>VGZD1tj;$IZvvm`htsVD7PlWVLao zT)xl+_q9X`2~DnDb6&HNHjX8bxT$Joe5JAAMd$8vA}N~|k6q@I5-#G7eY=~YpGZ1C zU_>vOEA~ni*I1{C z;@2!o)+p#iYTR1NO{XtwVCujxLT)qft@f)9$Y)JThB0Q$0J|FqM@4)iOSEuY)_g~} zppL5|BUM`Ydj2LUu4#@}J7qH+S{%KN>ohiKA7~V69=#~6~o!_b*jWmI*lXKY?W-fOl|6iWBt#^P< zU=M*FfsNth6Ww6HN!|%NpULP1;cxm2_YbjrI}Hs~;QUVmBz{*63h0yMR3vGb2_qi4 zx2CK%^-YW6{_*t84WiLPxIvNJ>l3!~5BfrB^xss`S7~<5R z!Q0T~3phid8{dYw*=YLd))=c~KIdaL4qWj>7#`-rp4 z=zNJsHTyKaX$qeM7p^oc&<4UALTOV#O}G-uAmJmo0pGwCRbi(t+N5@Z_KF+WCVA;{ zpsq^--o4L!_2^>L`+E>38U^?Z-l4WGaaJj5Z}j&N#Ft*+1+T&kpeYJWw@h}fD=cb^ zQPfCeiA1L@-B-40a)+meFTRpDtlw$5N@$t6qFO3@Y_35rex`*ci1mk`GSWQZ1#KI& z>#r49!&|nb6eQ)VWQO4omf-0s@jIq$P2f2rzd zI2lltJb0C&CnFz3EEf9aJ1%UN18!`jX#|J8(~2tbwXB-HHC#a9#d1Pl?c4$>=xSW5 z3{S5zobK~LTl#gS$-I%l)x8ALe588VWLIV#!qKW;A!4tL;<0-RO`R}1=PL*khBtQ6 z+2TsC-jy*A+ac9SLP3Zk(;MIt?5hI}8G2JmtY{1Z*8Ri|SwQq?DvtYMt+( z=C+*Ypv<%Zs!e()^@_^?Z$8!UF{=y$dMfZK-C(B4z$4NBT%z-Czd;)>k4Sjy<977x z-m*T->_h(%#}db8_Lfh{@h@L7sA-Vv&d&HUYMqUQolETzc#Sgg)3FvAh+P00>9>~V z?%*ZNjCKn3OV6UO1^4;S1k2%0_61-tosvm`iUY@%6yj~Vxwb(Us+)h%^!D*BVAr7w zKzG6Sy2w56X=k2H6V$sjB6pH+omKfT&KmsCos)Sn6JG^XPInTN(>>Jd;Q|MnoK{Z- zbBYta?%#tWiy?fQg{YmTlA~8y_YWaL9_K>~;Ul8d#`e`Kj%yEH22R;XkZ-~UNSpmM ztCY&?w^K@gs;-lj#HUDboHuTu%U8ZOp|sOwfNa|ajZ4aB4!XYGrMHl#k9b2vI$}ey zVMh4Og8|O_#v0Qm#JCMNLz$uWTyp*#mzWN;rE0bD8+tAaGM44nOoQ=DhdwfnJOTwS z2GDM=EP1{iOe~L7StyCn2PO4sF^I^iG$po%x7z+(pG026*sfYYx4nF1=!5I7Q&@f0 zC0nQDJF)p5zjb%;iD|afZR((jJveGA=HgCDbZO`stJ2|Kp|V>8Zdt8}bi!fBZZw@< zadOQoG|hM*DZ3El%H)*Uc1eudGT1dEE!#;$YiCiSezsVKx_p>CvhqL;=7|_t`Some zUC?XVqVN8c*?Bo}b?EBX_ILW~&{}6u5z!J#_DLS9sJ+cMnIgBmahtO1 z&gdHzx_zLU$?5T)BJ;%ZwO_0no8R=QN+WDWc`~1xDfsIhtltJE)jD3#B(L}{;B-4pS3Q$g0pVvN_tF-;Vz5U-e$E!8Xy~fS@4#Br_8@3I(T?74+j*dPpC6$155unyPM> zbc1beX45eamCcLT6Q%f2J1FOje{Cgf&WrDM zOSFU$n~ZH8!HES_5^`bigwv@*J1t{rGJP>URUBhtG{)9@q+kAL)=4B(3uEZ?IcVn) zdw;r9c}F8lkE|ets`CX?8v&*?``~*a0@9=WP78TM7y2wF4MJok!8F*fD99~|hwD!J_+SeR2xl84)u zboK#vmW6jpAu~(sLL1fH!Y@TW7B_vtf0TeV+q#X}XaJsr%J}U$EC9br;=hRt(TOK7 zZX(i}gZ4Cv#nIUb!bidc8{1NHY4PoO_=SFdkASGhDroC!%s`IfDH{q(H%&4&h5mFm znF(cm4#}nZFFloUtb3nHWHAf;7Rv3;wMeE8n}FNOJOd}tfy@W5pm;6!Qc>LIexKiu z^-_fSUDt&f=PjK4L1G>Zlb!=b#9Az8(P&(=v%Ns!GcjbS>Hl;wTxQJ~o z#OJ?&ex6c4gDh55BcC3W!TOFy9)KGL$@uI`;j;zm04Ip97sv3~8iBT-%JKCwS72*t zKH*hQG^#w#)?5I``lLcxTU_!N;C^U~M}RD{U-LuUwzH8pJ)CH(yR*ftWl>Ei9G$Ia zt8l-_ah|2lw*m(ZsU=UO-LdI5!LO!Gr(7e*P!5u};Hb*-HWIeGyoo2VbT=t1qk6G& z5+;Z}NeMQn?qzs;D#Fith-1h6Ce-ecz28K)=h(68XtnuQuAS{iWK&fZE*n-XE2q|i zy-w7ogGgl^pKlJrW`c=?{($FYNzlP*TPft5lul$RMZR3S#rXk>w%lZpg`KvR8*N^R zXIK~ja63T=NEDBbKJLXSLiqxK1Nuk)m09;Ze?k+U8tNWWvjK4O^~cCy^8O4ykkZK= zks>yHKEtuzyN1+`@w*I%VNs>g0>3NmvrM+^YY0r~ZH4V3Kg*wMvBr;=jrmn+5U{4@ zbLuO^0I}-D-CY+aSjNLX>}CLjrBUrZqjE=adA_@qUbSg-HO?k`$4FI!yxuQl6e2B` zT#}L2<9ln;C+hqm>QXI2Rng@ue%_C1cOE2UQ5&r8MYoz1w?`bscMt0!FH4(u@{w1j zzrye$&R1C;GU1Z(Zu-P7cF-{GhW-TAOm(GEf;}t~B9Kf`b@FeLXsM#-Yi$(TLEYS)W> z`~2iC`ZtqT7Tpxwet64oW40A!c8vd8N*B5>zQE%{FHSmz_A@SmIBj)uh>@xMh!VHc zVUQu$ZJs<)#=cYC>k2itUDBPlDmSp<=VbkM6gQ49nGqNA&mhdcq-nUgD&o|$V=7^k zAEqWe234b0Q=CYzt;YAB^p+tr-mX}%FL|CqRbvZ%3*jB^Xb`7ttWV*3ID{M@{O-#}`7^?ksVLAIHFBkTaka&AgBhb3D-2)tm4y zA`P6>9m(hEEJ&BX*vFGxy6MNvpr4hpOtvzAmYs8S$T5RClP51GW(w6M2x}@_-*t`m z1o@Uh-|5F+;O(e4MYnZY*lbhmX1-ND(eK9wh%3 z++Kw&nvUj(dA<;KK?MhG$G6YB0g6xp6}|dAC$89LHopopTP2@viRghHTaaGpZFNsB z5OS$~?y+bt&qzSvJ+LSA`^-_igFWC&#IQFEEAPUYd|PQgv&3m}AfoVI_pi2umDV_N z@~U;DW$^LLz|%K&VJ#zL_J+Erm{D`;%vZ3Rj*wekXLT#K?Y}Fu{FAqM)7JM#)A?*1 zc=RVl67&cwQUKyFPAKKij~(TA(g=u`ODuJFYG|_EA}RXg4QZo-#VNw|n^32PGoy7H zO|xDW>attn>#V^j8{B}dVCS)dD*vkl%>tGx-_}$aM@9XjmpGLTThAO+5LVp*)#+_S{z|55qma$OQAJ*PAi_+ z9Syh^GKEQn<@yciG3tBh)F;`%xC}(B(;)n%gyLPo7gVN*u>%Jy9dsxJz&e&*f~!$ zN85ChWms5AvrwgU0&HJSc%GJft6|Y{-`XekwjTkFT7~+(HM704i7F;?PUpQ2E-F!1 zqIg>+6mnlJ+Hl$9K}wD9WH>jn@#%ZUuA?(87gu!uX(_WlR_TIO*a9T3kQtEl@<%D) z#!@ZcI`|Lf!6VwFEr6|53H@pj4|Fgc(rQ%K+VpybfnNPUf9q>Ib^?v;+3q()_Y%7if^;*L2dZ{{*R6S3az{NO0i!*uPP-jalI_bI=5Bo#2%U#%dmL(il=&0M=< zciu>DgJ}-94l;JLM^AB$`cSvF4+ro z@dbpI)fBh0Klky>+`TsK6e3(Sv9ED?f!hq-AMg%LVu8=C=A4Y8C8L`CXf(Uf_VIza zaLzac#C}L`JtUXQM~VF`4AMus5Dt}}9TmKnC<1g3pJbP5PTl+XY=_s$yjIZq^pVUV zqGk98-afAktp#(``_zR~)31>IroUAjH3FP0Ti4#0wI&2jjuubc*=?r;qeRQ6Y0Rs2 zr>zaJF7I~j3*Cn787p0E*4~|u>zmn*RE|;xy5d@T+MwPFy*$HMF3A^ctxay-GY@o_ z2l^9VXSj1#7Ua>Bo{V4j^SJX^qS$w_US-XnIwV0MovwxHHw^)olP&C_x{W*U+uh># z`YvnC)QoxgfBx`fE~h2fi+Ue?`KQ+XJOUATP5MjwqZ(TaE6~?#ZciruD%Vboj zY;RK2V3edd{Qa>)v2aA3k=CqjHqotN5I;3~CXxU0Brign@6pwt@1FshWuihR?>#pT z66@uA(Rg6(i|fihewml*_hDA0zF}bg9{+SKdI^}$mxN_a0r#~v3|dZ=+Z4L+2r)n0 zKY5L3&!B+9&pg^Ubw;$o0(`(vd4ji<&#r!P0Y5wH8f33In<1I*Hw{<19+*lH^PL5P zQuKNtW5lgN+=05OIq25IdzRw%SGULB=dSC`wn(1iKkD^!_s0YLG<^EH7M!WT7aUTEla zzE)fVrtm*4@WZo5I`A`KfxG)1&V2{*L-+4NG=s1~x6p=U2K@r5A{Z5MN_HY*C-cP^ zO6>Gi0Lq)VP#*Pv6r+A0_&zTJ3;;&;55Tw!tnrj)>h?xX`7^+-XMxWn!Kpu12q;Wu z2mP*a>jjTu*%pUwJa;81`-_`T? z*L~cVcT==)oHYa5Zv0LOpCfIEPkIKnJ&|NyL4z%Tt}F`46q3PDxAqU%F}{K+K`F0*CvG;>acAIt@>z~=e&uxhCeyNxDVR1X9$QLuA;Ki;@(l$Qa` z$Lrh(J!HPp3dAs{f1clljg%Jn2{;OE01J(cInRKZjvxMVd}Y7Pxs`xd-XwetG7h!6 z7WbM@8I{&GLDA?ny%L%uu%UaOvA2N@{qPTv-bp#pR&T7l0t0}3m)Hmd50*++7qN5m zQGFD4u5?Q-O)DX=s2M_*m14+Rz*EgvfW`q8+O670g~|au+Rw!k9_=%1USNeU9&HQf zQhbF}GpcxPoFV094fIUuNN_PHHqoBHI!xIRM});;y-Liqu!sw0xAwh|76uLwU~PYU zfC)vUqRmE(Lj#5V--Qi<3Dp?iqK3>og@}upzuN2F7tQz&2dGoRL@GQEzC@mRaKQ|c zb)x}@qen zwyWX&sg~kJ8o5b2nz>xoRY)I}lQ6z|UZnYDEiO`etE|F(ZG8dHSaWdktRlm(5!`C7 z&yECs8hx}QT-0U6gB!nir!A}aOPUPOW31QMTTg!}4!?Ol>WtW>QLhtptxJC_AxURsSF>-5zwLHbBEO}W)&KA& z`6pD!0P!dPmYJ(UDH^lI;FAr&WgkZM+MVOicLM8Z%|61{j#@%c}RarnQbTj!y=swa&A<}CAvPF>|7HnxRTuCy_qZH&y zKx%oceC!i5cDK7|nk++T{p2pLdfX-Il{7)P6p_joS48+N(6G9zv{S-Fp8>fKbyXP> z$-B109|pVm3fLR)vFGztc5WlC{}cvMCU8|H%8L0}FMOg?DfJpbL9c}l+)zTj?|g2fCc)? z98l3Ja3Kj5R9*twb)N430p%R290zZn3HeV_df|0|!nTnAwZ zHWDV)nF}7G^i<_z#a?AM-~u&MwK9DPxG{KYHQ#{~E(yDv{-nHEyE)K+O#yKpe$DA; z0@L*BqA$h+j)XC<0!X_C_70D0;Ywdv%Ql>v!aw;P3@V9y7orGHh52^CXKg@9_h93U>%TeTb+ki-4C{ zYrQTAH4kI^*~p}}Ln9yl6DlS#3j5nlG9@HVIjJ?g8w>>Q%Twt-e1pV3E@8w%GWoeg zbKuCDiuT;0KUh!Xg?^9crwXAH=570-#e6T7E*C^Rb#D?mAARPjf#7}DELpy3C6Oe- zidR}qa(JyVmznW{yn*F)mRbfma`Hmu0Fvwnko#m9I@~NUIkK90G6HG93GkAFaO;89jtPxm zTC)o{nD*WUxU@m7dv&%0vHG^&DD3H_aE2|e)^@52un!Wxu%9_2tvQy3N>E{QHT+hJSrxx+WflTLG^SndAu zv%{ykjF8d0`z55}3HZ|##ZwrV?ODO|!xWVd`})ep9z6YJ&q*%vS(etz-gi`qCcW(& z82Lgwr>*bQL~!{Fg|YaP_jK2TkAJrqK0`pVJRhKV;6jM7O`}Eh<<3!6x}Sf2abPG2 zx@Vi^0S_+jJq<>~JBH7;W-~<6cfQ;};-a5pT?f5w>VexZ)8|5a$5-ZHryJiq()!%g zm6kXM1qXUIhKcr9zEXZW-==_LX)V(`UbU>ugt)eFBah8H;x+OhWP~tx zyS2fD-vv|lZ!K2FLu}rppOGr})4GL>_unDe{#D03E}{9`zkAPIU7bKn6bk z4{Jyr4uNFcXrPlFZptE$`PeO>dM z%d49k1ojvF!F&7&Y0!``!FlE0IA|29SD9`r4e7hp2QQ8Q&lBpsT5o{zt`TmuT51@m%dB=E6O%{YTmRKyqpku1xVrYS2&%&sV>PNUhDp^Q_2-PBW<+2 z_wX8BigZ=DT*z*#+q~toqr$vLsuVD!R0?~y5?gt#VClQcMep93cFl}m~jZdC>)pT#5veo zipaU3E87>K@No}JH+Zg3C%9P4LS|_!M$why9g{tgzxK#$_hOGwG+inZD?D+QsSq>_ zh4t}qMS3ZofGm^M5wk+{KozVvC<>eAlAQP9{YB4=@tO{0#q;&o*z)?5)%Ors`+t|v zc04Ux$7pu^(7{iC?`|)}hp$p(GDU(GB3?uab)1CGr1AMSIbM`>jHUF=_AmPp2iSLaLlI zSLGinRl)@{Rz4()@_MZmb409hm?I^jjupo-NA^a#kX89p(v(FC{1vZE+~AlbLL=e% zHvF5N@HGNyT((&{KuC0ddo7h++Oo{P&;|}&;pq2`G5-Z$hwg^hm9ExCo6}zoGtG#I z`Kn2@8B7BmGiG9^nD|NyH|DB{&1=T`FVGQ*Q}^Cbwlv!G^=>#Gpby_1))*|SkD%fm z{VW9VU#$eLsD5^;0NU<9a_;SNmB=8Yy>m<>e}|1JoJ)!CjtB3Q^2rgk7E23Ya|xHb8E%i~Xk!BGX(R-H7Y2 z2l-h?3%q}F@tY*jSGivGMg@{{SD~md%1m&2vn*?PCvpxQTl3g5E9!E${`KQ;C9lI- z#~1e~C3sl=MPdYW)NRH=62nABAOArM?*nq@Obv7H_RRIVF*3i zPpZ0s&a`JthXQo}o?dKxL?ula9>2hS53v^89&IG*IFEqA!#4!_UEu=Fn?dInlz`jk z&~I<06>e8}JZzSDxqLNrrF?Qe`4u2lDPN`P^zVd|Q5V4i3dpZ=;$z(@w;jq7=W4Z> zmM{907|2i+TrO2{gRic@J7A-e(9r);=;WeB8*hz zn{pUE!8hd4dx9RJX(y+fw_EGFWx3bh8iK#3R`s%=rp<`Qx~|XHYQ9lc+$4BVd3C|k zO#L3Pbc^O**s|gtwrYMi(+6WaH!vp+rr$Nl0Zh+lhR^<1`VVDwsE7iUb<6d8sv_!* zaoA0&kR)ba@%2=l1S|Q_7OZ6}%cgJa#6PJBN8Gm5k0kMUyyydt9$PhcWB~C1V}ZP2 zwkO^d+Z=e|Z%@av)%^9d&0PJ!3hmLs2ibP7|KEOL`@O-xrM8{zAKkRBXzDaiY>EQ~ z^{Bizu(fck7n%Um`pUOrb~n6DVgt2 zEojnbS4jAKfab@m3-HWdk{u-VX&HejwmT;~`%2!&XG|yFIV*loN;=&@T@2Rw^{HehMG~CRzH?Fh2-h1LoeNm3swiutX zkX}@&LQ(Pa<3M2HgTFw`tHU??YCm5XyeyHVlWvoEiFx&D%r`|JK(vVg*00c}Cm1~v zNF67!G&oWZ)kz0!pMxJf$-G`_hR>d{if2Q=iPoym(fT6Kvutu%0DXkmAT-G!vf!oQgmygiOGb-rkJJF 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 HaJcaggUaD2 literal 0 HcmV?d00001 diff --git a/docs/other/related_projects_and_links.md b/docs/other/related_projects_and_links.md index 1efe788..78b003a 100644 --- a/docs/other/related_projects_and_links.md +++ b/docs/other/related_projects_and_links.md @@ -28,7 +28,7 @@ In addition it also contains the source for the following containers for simulat * [uobflightlabstarling/starling-sim-ardupilot-copter](https://hub.docker.com/repository/docker/uobflightlabstarling/starling-sim-ardupilot-copter) - Base Container with ArduCopter * [uobflightlabstarling/starling-sim-ardupilot-gazebo](https://hub.docker.com/repository/docker/uobflightlabstarling/starling-sim-ardupilot-gazebo) - Gazebo Simulator for use with Ardupilot -* [uobflightlabstarling/starling-sim-iris-ap](https://hub.docker.com/repository/docker/uobflightlabstarling/starling-sim-iris-ap) - Base gazebo container with ardupulot sitl installed and spawns the iris quadcopter model with camera. +* [uobflightlabstarling/starling-sim-iris-ap](https://hub.docker.com/repository/docker/uobflightlabstarling/starling-sim-iris-ap) - Base gazebo container with ardupulot sitl installed and spawns the iris quadcopter model with camera. It also contains some example usage: @@ -38,7 +38,7 @@ It also contains some example usage: link - [https://github.com/StarlingUAS/Murmuration](https://github.com/StarlingUAS/Murmuration) -This repository contains all of the docker-compose and kubernetes deployment files. These deployment files rely on a number of the containers in this file. +This repository contains the main Command Line Interface and all of the docker-compose and kubernetes deployment files. These deployment files rely on a number of the containers in this file. ### Starling Simple Offboard @@ -84,7 +84,7 @@ This repository contains libraries and dockerfile for building the software laye * [uobflightlabstarling/starling-clover](https://hub.docker.com/repository/docker/uobflightlabstarling/starling-clover) -### Fenswood Farm +### Fenswood Farm link - [https://github.com/StarlingUAS/FenswoodScenario](https://github.com/StarlingUAS/FenswoodScenario) diff --git a/mkdocs.yml b/mkdocs.yml index 3632ad2..9913b65 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -27,16 +27,18 @@ nav: - "Background": 'details/background.md' - "Ecosystem": 'other/related_projects_and_links.md' - "Tutorials": - - "Introduction to Starling": 'tutorials/introduction.md' + - "1. Introduction to Starling": 'tutorials/introduction.md' + - "2. The Starling CLI": 'guide/cli.md' - "Single Drone User Guides": - "Single Drone Simulation on Local Machine": "guide/single-drone-local-machine.md" - "Single Drone Simulation on Local Cluster": "guide/kube-single-drone-local-machine.md" - "Single Drone on Hardware": "guide/single-drone-drones.md" - - "Windows Support": "guide/windows-support.md" + - "Multiple Drone User Guides": - "Multiple Drones Simulation on Local Cluster": "guide/multiple-drone-local-machine.md" - "Multiple Drones on Hardware": "guide/multiple-drone-drones.md" - "Additional Guides": + - "Windows Support and FAQ": "guide/windows-support.md" - "Developing a controller": 'guide/writing-a-controller.md' - "Development Guide": 'guide/development.md' - "Understanding Docker": 'details/docker.md' @@ -56,6 +58,7 @@ nav: - "starling-sim-ardupilot-copter/plane": "docker-images/sim-ardupilot-vehicle.md" - "starling-sim-ardupilot-gazebo": "docker-images/sim-ardupilot-gazebo.md" - "starling-sim-iris-ap": "docker-images/sim-iris-ap.md" + - "starling-vicon": "docker-images/starling-vicon.md" - "Miscellaneous": - "Ideas": "other/ideas.md" - "MATLAB (unstable)": "other/MATLAB.md" From 308045f64494945d3f7dc9867df2772cbbb92ada Mon Sep 17 00:00:00 2001 From: mickey li Date: Fri, 25 Mar 2022 17:21:51 +0000 Subject: [PATCH 22/36] Started writing docs on how to use other vehicles --- docs/guide/different_vehicles.md | 52 ++++++++++++++++++++++++++++++++ mkdocs.yml | 3 +- 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 docs/guide/different_vehicles.md diff --git a/docs/guide/different_vehicles.md b/docs/guide/different_vehicles.md new file mode 100644 index 0000000..833d4c5 --- /dev/null +++ b/docs/guide/different_vehicles.md @@ -0,0 +1,52 @@ +# Different Vehicles in Simulation + +With the way the system is setup, it can be quite simple to run another vehicle in simulation, this page shows two different methods of achieving this effect. + +[TOC] + +## Using Existing Models + +There are a number of already loaded models into the SITL and Simulation containers. + +- For PX4 SITL container, the following models already exist: [github link](https://github.com/PX4/PX4-SITL_gazebo/tree/822050a7ab6fd87972e59f16312f451bce217a56/models) and [here](https://docs.px4.io/master/en/simulation/gazebo_vehicles.html) +- For Ardupilot SITL container, not many exist ... see the next section for how to add your own. + +For those which already exist it is simply a case of setting the `PX4_SIM_MODEL` envrionment variable when you run + +- `uobflightlabstarling/starling-sim-iris` in order to specify the model which will be rendered. By default this is `iris` +- `uobflightlabstarling/starling-sim-px4-sitl:latest` in order to ensure that the correct SITL parameters have been loaded. By default this is also `iris` + +### Example: Rover + + +Example docker-compose to run the [`r1-rover`](https://docs.px4.io/master/en/simulation/gazebo_vehicles.html#differential-ugv) +```yaml +version: '3' +services: + simhost: + image: uobflightlabstarling/starling-sim-iris + environment: + - "PX4_SIM_MODEL=r1-rover" #new! + ports: + - "8080:8080" + sitl: + image: uobflightlabstarling/starling-sim-px4-sitl:latest + environment: + - "PX4_SIM_HOST=simhost" + - "PX4_OFFBOARD_HOST=mavros" + - "PX4_SIM_MODEL=r1-rover" # new! + ports: + - "18570:18570/udp" + mavros: + image: uobflightlabstarling/starling-mavros:latest + command: ros2 launch launch/mavros_bridge.launch.xml + environment: + - "MAVROS_TGT_SYSTEM=1" + - "MAVROS_FCU_IP=0.0.0.0" + - "MAVROS_GCS_URL=tcp-l://0.0.0.0:5760" + ports: + - "5760:5760" +``` + + +## Adding your own Models into the container \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 9913b65..3d09470 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -33,10 +33,11 @@ nav: - "Single Drone Simulation on Local Machine": "guide/single-drone-local-machine.md" - "Single Drone Simulation on Local Cluster": "guide/kube-single-drone-local-machine.md" - "Single Drone on Hardware": "guide/single-drone-drones.md" - - "Multiple Drone User Guides": - "Multiple Drones Simulation on Local Cluster": "guide/multiple-drone-local-machine.md" - "Multiple Drones on Hardware": "guide/multiple-drone-drones.md" + - "Simulation and SITL": + - "Spawning Different Vehicles and Objects": "guide/different_vehicles.md" - "Additional Guides": - "Windows Support and FAQ": "guide/windows-support.md" - "Developing a controller": 'guide/writing-a-controller.md' From 2ec6a72e8191cadfb9eea1500acd64bfe448d053 Mon Sep 17 00:00:00 2001 From: Mickey Li Date: Tue, 29 Mar 2022 15:13:34 +0100 Subject: [PATCH 23/36] Removed PX4 build artefacts reduced size by 800MB --- simulator/base/core/Dockerfile | 15 ++++++--- simulator/base/px4/Dockerfile | 56 ++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 4 deletions(-) diff --git a/simulator/base/core/Dockerfile b/simulator/base/core/Dockerfile index 1a0d98e..cc1720b 100644 --- a/simulator/base/core/Dockerfile +++ b/simulator/base/core/Dockerfile @@ -39,13 +39,19 @@ EXPOSE 11345 RUN echo "source /usr/share/gazebo/setup.sh" >> /ros.env # Install gazebo_ros, gazebo ros extra packages (e.g. plugins) and xacro +# RUN apt-get update \ +# && apt-get install --no-install-recommends -y \ +# ros-foxy-gazebo-ros \ +# && rm -rf /var/lib/apt/lists/* RUN apt-get update \ && apt-get install --no-install-recommends -y \ - ros-foxy-gazebo-ros \ ros-foxy-gazebo-ros-pkgs \ + && rm -rf /var/lib/apt/lists/* +RUN apt-get update \ + && apt-get install --no-install-recommends -y \ ros-foxy-xacro \ ros-foxy-image-transport-plugins \ - && rm -rf /var/lib/apt/lists/* + && rm -rf /var/lib/apt/lists/* # Install gzweb dependencies RUN apt-get update \ @@ -77,11 +83,12 @@ ENV GZWEB_WS /root/gzweb #RUN git clone -b gzweb_1.4.0-gazebo11 https://github.com/eurogroep/gzweb.git ${GZWEB_WS} # Additionally patched version RUN git clone --depth 1 -b gzweb_1.4.2 https://github.com/rob-clarke/gzweb.git ${GZWEB_WS} +RUN sed -i "s$\"node-gyp\": \"\"$\"node-gyp\": \"8.4.1\"$" ${GZWEB_WS}/package.json -# Build gzweb (default model generation) +# Build gzweb (no model generation) RUN source /ros.env \ && source /root/.nvm/nvm.sh \ - && cd ${GZWEB_WS} && npm run deploy --- -m + && cd ${GZWEB_WS} && npm run deploy --- -m local # Setup ports # Main webserver port diff --git a/simulator/base/px4/Dockerfile b/simulator/base/px4/Dockerfile index 400525a..cbc6d9a 100644 --- a/simulator/base/px4/Dockerfile +++ b/simulator/base/px4/Dockerfile @@ -12,6 +12,62 @@ INCLUDE+ ./px4builder.Dockerfile # Build the gazebo link RUN make px4_sitl sitl_gazebo +# See below, needs to be of format '! -path ./ ! -path ./' etc +ARG KEEP_PX4_VEHICLES="" + +# Default list of available plugins which are built. +#libgazebo_airship_dynamics_plugin.so X +#libgazebo_airspeed_plugin.so X +#libgazebo_barometer_plugin.so +#libgazebo_camera_manager_plugin.so X +#libgazebo_catapult_plugin.so X +#libgazebo_controller_interface.so +#libgazebo_drop_plugin.so X +#libgazebo_gimbal_controller_plugin.so X +#libgazebo_gps_plugin.so +#libgazebo_groundtruth_plugin.so +#libgazebo_gst_camera_plugin.so +#libgazebo_imu_plugin.so +#libgazebo_irlock_plugin.so X +#libgazebo_lidar_plugin.so +#libgazebo_magnetometer_plugin.so +#libgazebo_mavlink_interface.so +#libgazebo_motor_model.so +#libgazebo_multirotor_base_plugin.so +#libgazebo_opticalflow_mockup_plugin.so X +#libgazebo_opticalflow_plugin.so X +#libgazebo_parachute_plugin.so X +#libgazebo_sonar_plugin.so X +#libgazebo_user_camera_plugin.so X +#libgazebo_usv_dynamics_plugin.so X +#libgazebo_uuv_plugin.so X +#libgazebo_video_stream_widget.so X +#libgazebo_vision_plugin.so +#libgazebo_wind_plugin.so X +ARG REMOVE_GAZEBO_LIBRARIES="libgazebo_video_stream_widget.so libgazebo_airspeed_plugin.so libgazebo_drop_plugin.so libgazebo_user_camera_plugin.so libgazebo_camera_manager_plugin.so libgazebo_gimbal_controller_plugin.so libgazebo_opticalflow_mockup_plugin.so libgazebo_opticalflow_plugin.so libgazebo_airship_dynamics_plugin.so libgazebo_catapult_plugin.so libgazebo_irlock_plugin.so libgazebo_parachute_plugin.so libgazebo_sonar_plugin.so libgazebo_usv_dynamics_plugin.so libgazebo_uuv_plugin.so libgazebo_wind_plugin.so" + +# Remove a bunch of stuff before copying over +# Remove other simulators +WORKDIR /src/PX4-Autopilot/Tools +RUN rm -r flightgear_bridge jMAVSim jsbsim_bridge + +# Remove unused default models +# See the list here: https://github.com/PX4/PX4-SITL_gazebo/tree/25138e803ee8525ee5fe4e6d511506e88e3f819c/models +# Remove everything but the ones specified in the command here. +WORKDIR /src/PX4-Autopilot/Tools/sitl_gazebo/models +RUN find . -maxdepth 1 -type d \ + ! -path . \ + ! -path ./sun \ + ! -path ./gps \ + ! -path ./iris \ + ! -path ./r1_rover \ + ${KEEP_PX4_VEHICLES} \ + -exec rm -r '{}' \; + +# Remove unused build artefacts and extra px4-gazebo plugin libraries +WORKDIR /src/PX4-Autopilot/build/px4_sitl_default/build_gazebo +RUN rm -r CMakeFiles ${REMOVE_GAZEBO_LIBRARIES} *.cc *.h *.ninja unit_tests include + FROM ${REGISTRY}uobflightlabstarling/starling-sim-base-core:${VERSION} # Copy built PX4 repo into this image From 73c4df1349470544b079388932a328beeb177265 Mon Sep 17 00:00:00 2001 From: Mickey Li Date: Tue, 29 Mar 2022 20:55:36 +0100 Subject: [PATCH 24/36] Removed PX4 build artifacts from px4-SITL, reduced to 820Mb --- simulator/base/core/Dockerfile | 5 ----- simulator/base/px4/sitl.Dockerfile | 8 +++++--- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/simulator/base/core/Dockerfile b/simulator/base/core/Dockerfile index cc1720b..522471d 100644 --- a/simulator/base/core/Dockerfile +++ b/simulator/base/core/Dockerfile @@ -38,11 +38,6 @@ EXPOSE 11345 # Add gazebo environemnt setup to /ros.env RUN echo "source /usr/share/gazebo/setup.sh" >> /ros.env -# Install gazebo_ros, gazebo ros extra packages (e.g. plugins) and xacro -# RUN apt-get update \ -# && apt-get install --no-install-recommends -y \ -# ros-foxy-gazebo-ros \ -# && rm -rf /var/lib/apt/lists/* RUN apt-get update \ && apt-get install --no-install-recommends -y \ ros-foxy-gazebo-ros-pkgs \ diff --git a/simulator/base/px4/sitl.Dockerfile b/simulator/base/px4/sitl.Dockerfile index fa905b1..86f7595 100644 --- a/simulator/base/px4/sitl.Dockerfile +++ b/simulator/base/px4/sitl.Dockerfile @@ -11,7 +11,8 @@ RUN make px4_sitl FROM ros:foxy-ros-base-focal # Copy built PX4 repo into this image -COPY --from=px4builder /src /src +COPY --from=px4builder /src/PX4-Autopilot/build/px4_sitl_default/bin /src/PX4-Autopilot/build/px4_sitl_default/bin +COPY --from=px4builder /src/PX4-Autopilot/ROMFS /src/PX4-Autopilot/ROMFS ENV PX4_INSTANCE 0 ENV PX4_INSTANCE_BASE 0 @@ -47,7 +48,8 @@ ENTRYPOINT [ "/entrypoint.sh" ] VOLUME ${SIM_WD} # Relies on remote host option for simulator +# Relative to px4_sitl_default CMD /src/PX4-Autopilot/build/px4_sitl_default/bin/px4 \ - -d -i ${PX4_INSTANCE} -s etc/init.d-posix/rcS \ - -w /sim_wd \ + -d -i ${PX4_INSTANCE} -s /src/PX4-Autopilot/ROMFS/px4fmu_common/init.d-posix/rcS \ + -w ${SIM_WD} \ /src/PX4-Autopilot/ROMFS/px4fmu_common \ No newline at end of file From e79fd957fc8f002501cdb0b9edc08534a1676232 Mon Sep 17 00:00:00 2001 From: Mickey Li Date: Tue, 29 Mar 2022 21:35:07 +0100 Subject: [PATCH 25/36] Added multistage to ardupilot sitl, removed build artefacts reduced from 3Gb to 738Mb --- simulator/base/ardupilot/sitl.Dockerfile | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/simulator/base/ardupilot/sitl.Dockerfile b/simulator/base/ardupilot/sitl.Dockerfile index 4b33f68..0c02335 100644 --- a/simulator/base/ardupilot/sitl.Dockerfile +++ b/simulator/base/ardupilot/sitl.Dockerfile @@ -6,9 +6,6 @@ FROM ardupilot/ardupilot-dev-chibios as ardupilot_builder ARG VEHICLE=copter ARG BRANCH=ArduCopter-stable -# Install PyGeodesy -RUN python -m pip install --no-cache-dir pymap3d - # Fetch ArduPilot RUN git clone --recurse-submodules --depth 1 --shallow-submodules -j4 \ -b ${BRANCH} https://github.com/ArduPilot/ardupilot /src/ardupilot @@ -17,6 +14,16 @@ RUN git clone --recurse-submodules --depth 1 --shallow-submodules -j4 \ WORKDIR /src/ardupilot RUN ./waf configure && ./waf ${VEHICLE} +FROM ardupilot/ardupilot-dev-base + +ARG VEHICLE + +COPY --from=ardupilot_builder /src/ardupilot/build /src/ardupilot/build +COPY --from=ardupilot_builder /src/ardupilot/Tools /src/ardupilot/Tools + +# Install PyGeodesy +RUN python -m pip install --no-cache-dir pymap3d + WORKDIR /home/root COPY entrypoint.sh \ offset_location.py \ From 8d747bf4c10c93b6d70fd5c075197fd71af24eff Mon Sep 17 00:00:00 2001 From: Mickey Li Date: Tue, 29 Mar 2022 21:58:22 +0100 Subject: [PATCH 26/36] Added multistage to ardupilot gazebo, removed build artefacts reduced to 3.02Gb --- simulator/base/ardupilot/Dockerfile | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/simulator/base/ardupilot/Dockerfile b/simulator/base/ardupilot/Dockerfile index 6342345..d8accf7 100644 --- a/simulator/base/ardupilot/Dockerfile +++ b/simulator/base/ardupilot/Dockerfile @@ -4,7 +4,7 @@ ARG VERSION=latest ARG REGISTRY -FROM ${REGISTRY}uobflightlabstarling/starling-sim-base-core:${VERSION} +FROM ${REGISTRY}uobflightlabstarling/starling-sim-base-core:${VERSION} as apgbuilder # Clone and build the AP Gazebo plugins RUN git clone https://github.com/khancyr/ardupilot_gazebo \ @@ -15,8 +15,16 @@ RUN git clone https://github.com/khancyr/ardupilot_gazebo \ && make -j4 \ && make install +FROM ${REGISTRY}uobflightlabstarling/starling-sim-base-core:${VERSION} + +# Only adding libArduPilotPlugin.so, ignoring ArduCopterIRLockPlugin. +COPY --from=apgbuilder /ardupilot_gazebo/build/libArduPilotPlugin.so /ardupilot_gazebo/build/libArduPilotPlugin.so +COPY --from=apgbuilder /ardupilot_gazebo/models/iris_with_standoffs /ardupilot_gazebo/models/iris_with_standoffs +COPY --from=apgbuilder /ardupilot_gazebo/worlds /ardupilot_gazebo/worlds + # Setup a ros.env.d folder for AP to add to the resource paths for Gazebo RUN mkdir -p /ros.env.d/00_ardupilot \ + && echo 'export GAZEBO_PLUGIN_PATH=/ardupilot_gazebo/build:${GAZEBO_PLUGIN_PATH}' >> /ros.env.d/00_ardupilot/setup.bash \ && echo 'export GAZEBO_MODEL_PATH=/ardupilot_gazebo/models:${GAZEBO_MODEL_PATH}' >> /ros.env.d/00_ardupilot/setup.bash \ && echo 'export GAZEBO_RESOURCE_PATH=/ardupilot_gazebo/worlds:${GAZEBO_RESOURCE_PATH}' >> /ros.env.d/00_ardupilot/setup.bash From 4e7d9084a7ce40e1da08e01a8f127cc3505cb5b4 Mon Sep 17 00:00:00 2001 From: Mickey Li Date: Tue, 29 Mar 2022 22:35:35 +0100 Subject: [PATCH 27/36] Updated documentation to include new env vars --- docs/docker-images/sim-base-px4.md | 59 ++++++++++++++++++++++++++++++ mkdocs.yml | 1 + 2 files changed, 60 insertions(+) create mode 100644 docs/docker-images/sim-base-px4.md diff --git a/docs/docker-images/sim-base-px4.md b/docs/docker-images/sim-base-px4.md new file mode 100644 index 0000000..e0806e3 --- /dev/null +++ b/docs/docker-images/sim-base-px4.md @@ -0,0 +1,59 @@ +# `starling-sim-base-px4` + +Based on [`starling-sim-base-core`](../sim-base-core) + +This image contains the base Gazebo installation and adds the PX4 specific plugins. + +## Environment Variables + +Name | Default Value | Description +----------------------|------------------------------|------------ +`PX4_SIM_HOST` | | The host address of the simulation, if set, it will look up the ip address and assign to `PX4_SIM_IP` +`PX4_OFFBOARD_HOST` | | The host adress of the offboard (mavros), if set, it will look up the ip address and assign to `PX4_OFFBOARD_IP` +`PX4_INSTANCE` | 0 | The instance of the SITL (set to PX4 SYS_ID set to `PX4_INSTACE+1`), can be set to `ordinal` to automatically derive from last number of hostname (e.g. hostname-4), or set to 0 < instace < 254. +`PX4_HOME_LAT` | 51.501582 | Home Latitude +`PX4_HOME_LON` | -2.551791 | Home Longitude +`PX4_HOME_ALT` | 0 | Home Altitude +`PX4_SIM_MODEL` | iris | The PX4 simulation model which matches the available PX4 model library (see `KEEP_PX4_VEHICLES`) +`PX4_SIM_INIT_LOC_X` | 0 | Virtual X location for vehicle to spawn +`PX4_SIM_INIT_LOC_Y` | 0 | Virtual Y location for vehicle to spawn +`PX4_SIM_INIT_LOC_Z` | 0 | Virtual Z location for vehicle to spawn +`PX4_SIM_FORCE_USE_SET_POSITION` | false | If multiple vehicles are spawning, by default they will spawn in a spiral. This forces the use of Init Locations +`PX4_SYSID_SITL_BASE` | 200 | The base value of the minimum STIL instances actual instance is `PX4_SYSID_SITL_BASE` + `PX4_INSTACE` (not implemented yet) +`KEEP_PX4_VEHICLES` | "" | [**BUILD ARG**] A string of the format `! -path ./ ! -path ./`... which specifies a list of extra vehicle PX4 models to keep on top of `sun`, `gps`, `iris` and `r1_rover`. +`REMOVE_GAZEBO_LIBRARIES` | "libgazebo_video_stream_widget.so libgazebo_airspeed_plugin.so libgazebo_drop_plugin.so libgazebo_user_camera_plugin.so libgazebo_camera_manager_plugin.so libgazebo_gimbal_controller_plugin.so libgazebo_opticalflow_mockup_plugin.so libgazebo_opticalflow_plugin.so libgazebo_airship_dynamics_plugin.so libgazebo_catapult_plugin.so libgazebo_irlock_plugin.so libgazebo_parachute_plugin.so libgazebo_sonar_plugin.so libgazebo_usv_dynamics_plugin.so libgazebo_uuv_plugin.so libgazebo_wind_plugin.so" | [**BUILD ARG**] A space seperated list of libraries to be removed from the image. See below for details. + + +### `REMOVE_GAZEBO_LIBRARIES` + +Default list of available plugins which are built by the PX4_SITL: +``` +libgazebo_airship_dynamics_plugin.so X +libgazebo_airspeed_plugin.so X +libgazebo_barometer_plugin.so +libgazebo_camera_manager_plugin.so X +libgazebo_catapult_plugin.so X +libgazebo_controller_interface.so +libgazebo_drop_plugin.so X +libgazebo_gimbal_controller_plugin.so X +libgazebo_gps_plugin.so +libgazebo_groundtruth_plugin.so +libgazebo_gst_camera_plugin.so +libgazebo_imu_plugin.so +libgazebo_irlock_plugin.so X +libgazebo_lidar_plugin.so +libgazebo_magnetometer_plugin.so +libgazebo_mavlink_interface.so +libgazebo_motor_model.so +libgazebo_multirotor_base_plugin.so +libgazebo_opticalflow_mockup_plugin.so X +libgazebo_opticalflow_plugin.so X +libgazebo_parachute_plugin.so X +libgazebo_sonar_plugin.so X +libgazebo_user_camera_plugin.so X +libgazebo_usv_dynamics_plugin.so X +libgazebo_uuv_plugin.so X +libgazebo_video_stream_widget.so X +libgazebo_vision_plugin.so +``` +A space seperated list of these files can be given at build time to remove all of the plugins you dont need in order to save space. Each library is around 7 to 9Mb. \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 3632ad2..1674713 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -53,6 +53,7 @@ nav: - "starling-ui": "docker-images/ui.md" - "starling-mavros": "docker-images/mavros.md" - "starling-sim-base-core": "docker-images/sim-base-core.md" + - "starling-sim-base-px4": "docker-images/sim-base-px4.md" - "starling-sim-ardupilot-copter/plane": "docker-images/sim-ardupilot-vehicle.md" - "starling-sim-ardupilot-gazebo": "docker-images/sim-ardupilot-gazebo.md" - "starling-sim-iris-ap": "docker-images/sim-iris-ap.md" From 3cd7c3a3efc946e69dbc7ace5a48f19f790a5157 Mon Sep 17 00:00:00 2001 From: mickey li Date: Fri, 1 Apr 2022 17:23:49 +0100 Subject: [PATCH 28/36] Added docs on simulation --- docs/docker-images/controller-base.md | 22 ++ docs/guide/different_vehicles.md | 287 +++++++++++++++++++++++++- mkdocs.yml | 1 + 3 files changed, 307 insertions(+), 3 deletions(-) create mode 100644 docs/docker-images/controller-base.md diff --git a/docs/docker-images/controller-base.md b/docs/docker-images/controller-base.md new file mode 100644 index 0000000..87cae72 --- /dev/null +++ b/docs/docker-images/controller-base.md @@ -0,0 +1,22 @@ +# `starling-controller-base` + +This image contains the minimal dependency requirements for a user to implement their own controller. In particular it contains a number of automated steps to aid in setup. + +This image acts as a base to build controllers + +## `/ros.env.d` + +To ease the expansion of this image, a mechanism has been built to enable expansion by volume mount. + +As part of the +entrypoint, the image will look for any folders in `/ros.env.d` that contain a `setup.bash` file. + +Any such file will be +sourced as part of the entrypoint. This allows for arbitrary expansion of the entrypoint by adding additional volume +mounts. + +## Environment Variables + +Name | Default Value | Description +----------------------|------------------------------|------------ +`USE_SIMULATED_TIME` | false | A variable which can be used in ros2 launch scripts to toggle the `use_sim_time` parameter of a ros node. \ No newline at end of file diff --git a/docs/guide/different_vehicles.md b/docs/guide/different_vehicles.md index 833d4c5..2a48448 100644 --- a/docs/guide/different_vehicles.md +++ b/docs/guide/different_vehicles.md @@ -6,9 +6,9 @@ With the way the system is setup, it can be quite simple to run another vehicle ## Using Existing Models -There are a number of already loaded models into the SITL and Simulation containers. +To reduce the size of the containers, they only contain the minimal number of vehicle models -- For PX4 SITL container, the following models already exist: [github link](https://github.com/PX4/PX4-SITL_gazebo/tree/822050a7ab6fd87972e59f16312f451bce217a56/models) and [here](https://docs.px4.io/master/en/simulation/gazebo_vehicles.html) +- For PX4 SITL container, there are only two models (1) iris (2) r1_rover. - For Ardupilot SITL container, not many exist ... see the next section for how to add your own. For those which already exist it is simply a case of setting the `PX4_SIM_MODEL` envrionment variable when you run @@ -48,5 +48,286 @@ services: - "5760:5760" ``` +### Using existing models which are not included in the container -## Adding your own Models into the container \ No newline at end of file +As mentioned above, there are a number of models which should be included, but have been deleted by default to reduce the size of the container. + +For example for the PX4 SITL container, the following models should all exist: [github link](https://github.com/PX4/PX4-SITL_gazebo/tree/822050a7ab6fd87972e59f16312f451bce217a56/models) and [here](https://docs.px4.io/master/en/simulation/gazebo_vehicles.html), but almost all of them are deleted. + +You can specify other vehicles to keep by rebuilding `starling-sim-base-core` with the build arg `KEEP_PX4_VEHICLES` set to the vehicles you wish to keep. To rebuild you will need to clone the `ProjectStarling` repository, navigate to simulation and locally rebuild the correct controller: +``` +docker build --build-args KEEP_PX4_VEHICLES="! -o -path iris_with_standoffs" +``` + +## Adding your own Models into the container + +There will be many cases in which you will want to add extra models into your simulations. This might be to create an environment, or to add your own custom vehicles. For some exaples of projects which utilise custom models or worlds, please see the following: + +- [FenswoodScenario](https://github.com/StarlingUAS/FenswoodScenario) where a number of custom models (downloaded from the gazebo repository) and a custom world is defined. +- [BRLFlightArenaGazebo](https://github.com/StarlingUAS/BRLFlightArenaGazebo) which is comprised of a custom world and a custom gimbal-tripod model built from scratch. + +### Background and `/ros.env.d` + +Both the [base simulation container](../docker-images/sim-base-core.md), and the [base controller container](../docker-images/controller-base.md) both use a `/ros.env.d` mechanism. + +1. Both containers are set-up with a special folder in the root directory called `/ros.env.d`. +2. When the container is started up, there is a special script that runs (called the *entrypoint* script) which looks at all of the folders within `/ros.env.d` and whether they have their own `setup.bash` script. +3. If a `setup.bash` script exists, it will be [`source`'d](https://superuser.com/questions/176783/what-is-the-difference-between-executing-a-bash-script-vs-sourcing-it), i.e. script is run with any environment variables defined being exported to be used in later processes. + +By mounting external folders into the Dockerfile into `/ros.env.d`, we can add external and extra resources into a given controller or simulation container. + +### Setting up the project + +The general format for a starling project with extra assets is the following: + +``` +project/ +├─ custom_models/ +│ ├─ model_1/ +│ │ ├─ setup.bash +│ │ ├─ models/ +│ │ │ ├─ model_1/ +│ │ │ | ├─ meshes/ # optionally populated +│ │ │ │ ├─ model.config +│ │ ├─ model.sdf.xacro +│ ├─ model_2/ +│ │ ├─ setup.bash +│ │ ├─ models/ +│ │ │ ├─ model_2/ +│ │ │ │ ├─ model.config +│ │ ├─ model.sdf.xacro +├─ custom_world/ +│ ├─ world/ +│ │ ├─ my_custom_world.world +│ ├─ models/ +│ │ ├─ some_downloaded_model_asset/ +│ ├─ setup.bash +├─ docker-compose.yaml +├─ system.launch.xml +├─ Makefile +├─ Dockerfile +``` + +This project seperates custom models and the custom worlds into two seperate folders. The `system.launch.xml` defines the roslaunch file being run by the container. The Dockerfile will then copy all of these elements into the container at build time. + +### Custom Model + +#### Specifying the model + +As above, it is recommended that you have 3 items within the custom model folder. + +1. `setup.bash` which we will populate later +2. `model.sdf.xacro` defines the model as an sdf file with xacro argument replacement, I will explain what this means next +3. `models/` folder which creates a template of the file structure required by gazebo for any model. + +Xacro is handy tool for specifying arguments to an sdf file in order to generate custom sdf files. This is needed as the sdf file which describes a model is usually hard coded to a particular vehicle instance. Xacro allows us to dynamically generate these files. + +An example model which uses the `model.sdf.xacro` is given here: +```xml + + + + + + + + 0.5 0.5 1.5 0 0 0 + + + + + + + 0.5 0.5 1.5 0 0 0 + + + file://media/materials/scripts/gazebo.material + + + + + + + model://gimbal_small_2d + 0 0 1.0 1.57 0 1.57 + + + + tripod + gimbal_small_2d::base_link + + + 0 + 0 + + 0 0 1 + true + + + + + +``` + +This example describes the sdf file for a gimbal mounted on a simple tripod block. The xacro elements specify a 'property' of `ros_namespace`. When compiling with the xacro.py tool, it can generate a new sdf file with that property set to a value you want. + +You must then setup a model directory looking like the following (with optional meshes if using a model from the internet) +``` +│ │ ├─ models/ +│ │ │ ├─ model_1/ +│ │ │ | ├─ meshes/ # optionally populated +│ │ │ │ ├─ model.config +``` + +The exact contents of the model file follow gazebo guidelines. There is an overview [here](http://gazebosim.org/tutorials?tut=build_model). There are a number online as well. + +> Note that Xacro does not change the sdf or gazebo model syntax. It can be thought of as doing a copy paste to your existing file. + +#### Building the model (`setup.bash`) + +Now we have the model defined, we now want to ensure that the model can now be built/configured/compiled during the container runtime. This involves writing the `setup.bash` file. Usually there are 3 steps: + +1. Making sure the script is being run from the correct directory, and that the relevant paths are set. +2. Apply xacro with environment variables (given by the user or the container itself) and saving it in the model folder path (next to `model.config`) +3. Adding the model to the gazebo model path + +Here is an example `setup.bash` for the gimbal example. + +```bash +#!/bin/bash +SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" + +XACRO_PATH=${SCRIPT_DIR}/model.sdf.xacro +MODEL_PATH=${SCRIPT_DIR}/models/gimbal_tripod/model.sdf + +xacro ${XACRO_PATH} \ + ros_namespace:=${GIMBAL_NAMESPACE} \ + -o ${MODEL_PATH} + +echo "Written Gimbal model to ${MODEL_PATH}" + +export GAZEBO_MODEL_PATH="${SCRIPT_DIR}/models:${GAZEBO_MODEL_PATH}" +``` + +In particular the `xacro` command takes the `sdf.xacro` we created previously and creates a new version with the parameters realised - here `ros_namespace` is set to the environment variable `GIMBAL_NAMESPACE`. Environment variables are often defined in the Dockerfile or by the user. + +### Custom World + +#### Specifying the world + +A world can be defined by the worlds folder. Inside the worlds folder, there is the world file which describes the base world gazebo will generate. The world file itself is an sdf file with the `world` tag. Since the world is fixed, there is also no need for xacro (although you could use it if you wanted to). + +```xml + + + + + + EARTH_WGS84 + 51.4233628 + -2.671671 + 100.0 + + + + model://sun + + + ... All the contents of the world ... + + + +``` + +An example can be found in these repositories [FenswoodScenario](https://github.com/StarlingUAS/FenswoodScenario/blob/master/fenswood/worlds/fenswood.world), [BRLFlightArenaGazebo](https://github.com/StarlingUAS/BRLFlightArenaGazebo/blob/master/flightarena/worlds/flightarena.world). + +The contents of the world file uses the same syntax as your normal model files. See the examples for adding primitive objects. For models, you will need to include the models inside this directory (and add that folder onto your path in the next step). +#### Building the model (`setup.bash`) + +Similar to setup for the custom model, we need to define a setup for the custom world. For worlds, this usually just involves ensuring that the world and asscoiated models are on the gazebo paths: + +```bash +SRC_PATH="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" + +# Add flightarena.world to the Gazebo resource path +export GAZEBO_RESOURCE_PATH=$SRC_PATH/worlds:${GAZEBO_RESOURCE_PATH} +echo "RESOURCE_PATH: $GAZEBO_RESOURCE_PATH" + +# Add the 'grass_box' model to the Gazebo model path +export GAZEBO_MODEL_PATH=$SRC_PATH/models:${GAZEBO_MODEL_PATH} +echo "GAZEBO_MODEL_PATH: $GAZEBO_MODEL_PATH" + +# Make the default media files work +cp -r /usr/share/gazebo-11/media $GZWEB_WS/http/client/assets/ +(cd /root/gzweb/http/client/assets/media/materials/textures \ + && for f in *jpg; do convert $f ${f%.*}.png; done) +``` + +The final line is to ensure that all of the textures are compatible with the Gazebo web interface. + +### Building and Running the models + +Now that all of the models and worlds have been created, we then need to either bundle it up, or inject it into the container so it can be included. + +There are two methods. Both of which are applicable depending on eventual usecase. + +1. Docker-Compose: The models/worlds can be mounted into the simulation container at runtime +2. Building a new Docker Container: If you know that you want to build upon the models or worlds into multiple differing applications, it's probably best to package it up and use as a dependency for the child projects. + +#### Docker-Compose + +As mentioned earlier, as long as the folders have a `setup.bash` script inside, it is compatible with automation within `/ros.env.d`. Therefore you can mount your folders into the container as volumes within the docker-compose file: + +```yaml +simhost: + image: uobflightlabstarling/starling-sim-iris:${STARLING_RELEASE:-latest} + volumes: + - ./flightarena:/ros.env.d/02_flightarena + - ./systems/models/gimbal_small_2d:/ros.env.d/03_gimbal_small_2d + - ./systems/models/gimbal_tripod:/ros.env.d/04_gimbal_tripod + - ./system.launch.xml:launch/system.launch.xml + env: + - GIMBAL_NAMESPACE=gimbal_1 + command: ["ros2", "launch", "launch/system.launch.xml"] + ports: + - "8080:8080" +``` + +Where the syntax is `:`. Also note that inside `ros.env.d` we name the folders `XX_` where `XX` is a two digit number describing the order of script loading from 0 being first to 99 being last. This is useful for dependencies. + +#### DockerFile + +The dockerfile is an alternative way where we explicitly copy the files into the newly built container. The following dockerfile does exactly this. + +```dockerfile +ARG VERSION=latest +ARG REGISTRY +FROM ${REGISTRY}uobflightlabstarling/starling-sim-iris:latest + +# Copy in the xacro & bash file to setup the model +# Using ros.env.d automatic sourcing on entrypoint (see /simulator/base/core/Dockerfile) +COPY flightarena /ros.env.d/02_flightarena +COPY systems/models/gimbal_small_2d /ros.env.d/03_gimbal_small_2d +COPY systems/models/gimbal_tripod /ros.env.d/04_gimbal_tripod + +# Build gimbal plugin with ROS2 on path and add plugin to path +COPY systems/gimbal_plugin /ros_ws/src/gimbal_plugin +RUN cd /ros_ws \ + && . /opt/ros/foxy/setup.sh \ + && export CMAKE_PREFIX_PATH=$AMENT_PREFIX_PATH:$CMAKE_PREFIX_PATH \ + && colcon build --cmake-force-configure \ + && rm -r build \ + && echo 'export GAZEBO_PLUGIN_PATH=/ros_ws/install/gimbal_plugin/lib:${GAZEBO_PLUGIN_PATH}' >> /ros.env.d/03_gimbal_small_2d/setup.bash + +# Copy in the vehicle launch file +COPY system.launch.xml /ros_ws/launch/ + +WORKDIR /ros_ws + +ENV GIMBAL_NAMESPACE gimbal_1 + +# Launch gazebo and spawn model +CMD [ "ros2", "launch", "launch/system.launch.xml" ] +``` + +Often you want to also setup the build process. See the examples for details! \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 3d09470..3af5881 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -55,6 +55,7 @@ nav: - "Image Documentation": - "starling-ui": "docker-images/ui.md" - "starling-mavros": "docker-images/mavros.md" + - "starling-controller-base": "docker-images/controller-base.md" - "starling-sim-base-core": "docker-images/sim-base-core.md" - "starling-sim-ardupilot-copter/plane": "docker-images/sim-ardupilot-vehicle.md" - "starling-sim-ardupilot-gazebo": "docker-images/sim-ardupilot-gazebo.md" From 8deb57e1c5980a01aae38d0ef17f033221cf35c3 Mon Sep 17 00:00:00 2001 From: Mickey Li Date: Mon, 4 Apr 2022 18:20:44 +0100 Subject: [PATCH 29/36] 00 instead of 0 --- docs/guide/different_vehicles.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guide/different_vehicles.md b/docs/guide/different_vehicles.md index 2a48448..ffcdc92 100644 --- a/docs/guide/different_vehicles.md +++ b/docs/guide/different_vehicles.md @@ -293,7 +293,7 @@ simhost: - "8080:8080" ``` -Where the syntax is `:`. Also note that inside `ros.env.d` we name the folders `XX_` where `XX` is a two digit number describing the order of script loading from 0 being first to 99 being last. This is useful for dependencies. +Where the syntax is `:`. Also note that inside `ros.env.d` we name the folders `XX_` where `XX` is a two digit number describing the order of script loading from 00 being first to 99 being last. 00 to 02 are already used in setup, it is recommended you use numbers > 03. This is useful for dependencies. #### DockerFile @@ -330,4 +330,4 @@ ENV GIMBAL_NAMESPACE gimbal_1 CMD [ "ros2", "launch", "launch/system.launch.xml" ] ``` -Often you want to also setup the build process. See the examples for details! \ No newline at end of file +Often you want to also setup the build process. See the examples for details! From 2943ed41438db21e71e6f95badc28545f06018dc Mon Sep 17 00:00:00 2001 From: Mickey Li Date: Mon, 9 May 2022 22:43:57 +0100 Subject: [PATCH 30/36] Added estop node to mavros container --- system/mavros/Dockerfile | 8 +++ system/mavros/mavros_bridge.launch.xml | 3 ++ system/mavros/ros_entrypoint.sh | 3 ++ .../starling_mavros_extras/CMakeLists.txt | 50 +++++++++++++++++++ .../starling_mavros_extras/include/estop.hpp | 41 +++++++++++++++ .../mavros/starling_mavros_extras/package.xml | 22 ++++++++ .../starling_mavros_extras/src/estop.cpp | 50 +++++++++++++++++++ 7 files changed, 177 insertions(+) create mode 100644 system/mavros/starling_mavros_extras/CMakeLists.txt create mode 100644 system/mavros/starling_mavros_extras/include/estop.hpp create mode 100644 system/mavros/starling_mavros_extras/package.xml create mode 100644 system/mavros/starling_mavros_extras/src/estop.cpp diff --git a/system/mavros/Dockerfile b/system/mavros/Dockerfile index a0a3a77..de98568 100644 --- a/system/mavros/Dockerfile +++ b/system/mavros/Dockerfile @@ -56,6 +56,14 @@ ENV MAVROS_PLUGINLISTS_PATH="/mavros_pluginlists_px4.yaml" ENV BRIDGE_MOD_CONFIG_PATH="/bridge_config_mod.yaml" ENV BRIDGE_CONFIG_PATH="/bridge_config.yaml" +# Build extra custom nodes in Mavros +COPY starling_mavros_extras /ros_ws/src/starling_mavros_extras +RUN . /opt/ros/${ROS_DISTRO}/setup.sh \ + && export CMAKE_PREFIX_PATH=$AMENT_PREFIX_PATH:$CMAKE_PREFIX_PATH \ + && cd /ros_ws \ + && colcon build --packages-select starling_mavros_extras \ + && rm -r build + # Add custom ROS DDS configuration (force UDP always) COPY fastrtps_profiles.xml /ros_ws/ ENV FASTRTPS_DEFAULT_PROFILES_FILE /ros_ws/fastrtps_profiles.xml diff --git a/system/mavros/mavros_bridge.launch.xml b/system/mavros/mavros_bridge.launch.xml index 526a416..574f7d5 100644 --- a/system/mavros/mavros_bridge.launch.xml +++ b/system/mavros/mavros_bridge.launch.xml @@ -36,4 +36,7 @@ launch-prefix="bash -c 'sleep $(var bridge_launch_delay); $0 $@' "> + + + \ No newline at end of file diff --git a/system/mavros/ros_entrypoint.sh b/system/mavros/ros_entrypoint.sh index 4299c8d..27db0b7 100755 --- a/system/mavros/ros_entrypoint.sh +++ b/system/mavros/ros_entrypoint.sh @@ -14,6 +14,9 @@ source "/opt/ros/$ROS2_DISTRO/setup.bash" # Source the from-source ros1_bridge workspace source "/bridge_ws/install/setup.bash" +# Source any extra nodes inside mavros container +source "/ros_ws/install/setup.bash" + # Source the mavros_setup for any user defined edits to the environment source "/ros_ws/mavros_setup.sh" diff --git a/system/mavros/starling_mavros_extras/CMakeLists.txt b/system/mavros/starling_mavros_extras/CMakeLists.txt new file mode 100644 index 0000000..662c7ae --- /dev/null +++ b/system/mavros/starling_mavros_extras/CMakeLists.txt @@ -0,0 +1,50 @@ +cmake_minimum_required(VERSION 3.5) +project(starling_mavros_extras) + +# Default to C99 +if(NOT CMAKE_C_STANDARD) + set(CMAKE_C_STANDARD 99) +endif() + +# Default to C++14 +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 14) +endif() + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) +endif() +# find dependencies +find_package(ament_cmake REQUIRED) +# uncomment the following section in order to fill in +# further dependencies manually. +# find_package( REQUIRED) +find_package(rclcpp REQUIRED) +find_package(std_msgs REQUIRED) +find_package(mavros_msgs REQUIRED) + +set(HEADER_FILES include/estop.hpp) + +add_executable(estop src/estop.cpp ${HEADER_FILES}) +ament_target_dependencies(estop + rclcpp std_msgs mavros_msgs +) +target_include_directories(estop PRIVATE include) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + # the following line skips the linter which checks for copyrights + # uncomment the line when a copyright and license is not present in all source files + #set(ament_cmake_copyright_FOUND TRUE) + # the following line skips cpplint (only works in a git repo) + # uncomment the line when this package is not in a git repo + #set(ament_cmake_cpplint_FOUND TRUE) + ament_lint_auto_find_test_dependencies() +endif() + +install(TARGETS + estop + DESTINATION lib/${PROJECT_NAME} +) + +ament_package() diff --git a/system/mavros/starling_mavros_extras/include/estop.hpp b/system/mavros/starling_mavros_extras/include/estop.hpp new file mode 100644 index 0000000..df8eefa --- /dev/null +++ b/system/mavros/starling_mavros_extras/include/estop.hpp @@ -0,0 +1,41 @@ +#ifndef ESTOP_HPP +#define ESTOP_HPP + +/* + * Trajectory follower + * Copyright (C) 2021 University of Bristol + * + * Author: Mickey Li (University of Bristol) + * + * Distributed under MIT License (available at https://opensource.org/licenses/MIT). + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + */ +#include "rclcpp/rclcpp.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "mavros_msgs/srv/command_long.hpp" + + +class EstopHandler : public rclcpp::Node +{ + public: + EstopHandler(); + + private: + void emergency_stop(); + rclcpp::Client::SharedPtr mavros_command_srv; + rclcpp::Subscription::SharedPtr estop_sub; +}; + + +#endif diff --git a/system/mavros/starling_mavros_extras/package.xml b/system/mavros/starling_mavros_extras/package.xml new file mode 100644 index 0000000..0845439 --- /dev/null +++ b/system/mavros/starling_mavros_extras/package.xml @@ -0,0 +1,22 @@ + + + + starling_mavros_extras + 0.0.0 + TODO: Package description + root + TODO: License declaration + + ament_cmake + + rclcpp + std_msgs + mavros_msgs + + ament_lint_auto + ament_lint_common + + + ament_cmake + + diff --git a/system/mavros/starling_mavros_extras/src/estop.cpp b/system/mavros/starling_mavros_extras/src/estop.cpp new file mode 100644 index 0000000..4cb6c3c --- /dev/null +++ b/system/mavros/starling_mavros_extras/src/estop.cpp @@ -0,0 +1,50 @@ +/* + * Trajectory follower + * Copyright (C) 2021 University of Bristol + * + * Author: Mickey Li (University of Bristol) + * + * Distributed under MIT License (available at https://opensource.org/licenses/MIT). + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + */ +#include "estop.hpp" + +EstopHandler::EstopHandler() : + Node("estop_handler") +{ + this->mavros_command_srv = this->create_client("mavros/cmd/command", rmw_qos_profile_services_default); + this->estop_sub = this->create_subscription( + "/emergency_stop", 1, [this](const std_msgs::msg::Empty::SharedPtr s){(void)s; + this->emergency_stop(); + }); + + RCLCPP_INFO(this->get_logger(), "Estop Initialised"); +} + +void EstopHandler::emergency_stop() { + RCLCPP_ERROR(this->get_logger(), "EMERGENCY STOP RECIEVED, SENDING KILL MESSAGE TO AUTOPILOT"); + // Kill Drone Rotors + auto commandCall = std::make_shared(); + commandCall->broadcast = false; + commandCall->command = 400; + commandCall->param2 = 21196.0; + while (!this->mavros_command_srv->wait_for_service(std::chrono::duration(0.1))) { + if (!rclcpp::ok()) { + RCLCPP_ERROR(this->get_logger(), "Interrupted while waiting for command service. Exiting."); + throw std::runtime_error("Interrupted while waiting for command service. Exiting."); + return; + } + RCLCPP_INFO(this->get_logger(), "service not available, waiting again..."); + } + auto result_future = this->mavros_command_srv->async_send_request(commandCall); +} + +int main(int argc, char **argv) +{ + rclcpp::init(argc, argv); + auto handler = std::make_shared(); + rclcpp::spin(handler); + rclcpp::shutdown(); + return 0; +} \ No newline at end of file From ef8d51693faf504cb8f6ee261fe9c5e8402d9b3a Mon Sep 17 00:00:00 2001 From: Mickey Li Date: Mon, 9 May 2022 22:51:58 +0100 Subject: [PATCH 31/36] Updated README.md --- docs/docker-images/mavros.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/docker-images/mavros.md b/docs/docker-images/mavros.md index 406e9d8..012f320 100644 --- a/docs/docker-images/mavros.md +++ b/docs/docker-images/mavros.md @@ -21,6 +21,10 @@ The image has been setup to automatically configure itself in some scenarios: - Running on a vehicle - Running in Kubernetes +There are also some additional general nodes running in the background. In particular: + + - Emergency Stop listener on the `/emergency_stop` topic of type `std_msgs/msg/Empty`. It is hard-wired to forcefully disarm the vehicle (stop motors) for both PX4 and Ardupilot in simulation and reality. + ## Configuration Options There are many configuration options available for this image to allow it to be used flexibly across different @@ -43,7 +47,7 @@ Name | Default Value | Description `MAVROS_MOD_CONFIG_PATH` | "/mavros_config_mod.yaml" | Path for modified MAVROS config to be written to `BRIDGE_CONFIG_PATH` | "/bridge_config.yaml" | Path for initial bridge config file `BRIDGE_MOD_CONFIG_PATH` | "/bridge_config_mod.yaml" | Path for modified bridge config to be written to -`BRIDGE_LAUNCH_DELAY` | 2.0 | Time delay in seconds between starting ROS1 mavros and the ROS1-2 bridge. Increasing this value reduces the chance of a race condition causing mavros failure due to the bridge being unable to read ros1 parameters. See [this issue](https://github.com/StarlingUAS/ProjectStarling/issues/124) for more details. Recommendation is to increase the value to 10 seconds if this occurs. +`BRIDGE_LAUNCH_DELAY` | 2.0 | Time delay in seconds between starting ROS1 mavros and the ROS1-2 bridge. Increasing this value reduces the chance of a race condition causing mavros failure due to the bridge being unable to read ros1 parameters. See [this issue](https://github.com/StarlingUAS/ProjectStarling/issues/124) for more details. Recommendation is to increase the value to 10 seconds if this occurs. ### Configuring the Connection @@ -161,7 +165,7 @@ The first part of this is checking for the existance of the `/stc/starling/vehic is assumed that the container is running on a real vehicle. In this case, the file is sourced and values for the MAVLink system ID, vehicle name, FCU URL, and firmware type are obtained. -The next phase is determining the appropriate target system ID. This is configured by the +The next phase is determining the appropriate target system ID. This is configured by the Once the setup script has run, the default behaviour is to launch the `mavros_bridge.launch.xml` file. This behaviour From 32125ab02b2c5f9d267185b4f121ecb64bcd6664 Mon Sep 17 00:00:00 2001 From: Mickey Li Date: Mon, 9 May 2022 22:55:25 +0100 Subject: [PATCH 32/36] Copied fix for node-gyp from other branch --- simulator/base/core/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/simulator/base/core/Dockerfile b/simulator/base/core/Dockerfile index 1a0d98e..305b083 100644 --- a/simulator/base/core/Dockerfile +++ b/simulator/base/core/Dockerfile @@ -77,6 +77,7 @@ ENV GZWEB_WS /root/gzweb #RUN git clone -b gzweb_1.4.0-gazebo11 https://github.com/eurogroep/gzweb.git ${GZWEB_WS} # Additionally patched version RUN git clone --depth 1 -b gzweb_1.4.2 https://github.com/rob-clarke/gzweb.git ${GZWEB_WS} +RUN sed -i "s$\"node-gyp\": \"\"$\"node-gyp\": \"8.4.1\"$" ${GZWEB_WS}/package.json # Build gzweb (default model generation) RUN source /ros.env \ From 692f90501a01afa623e5d0330f488c0adbc4faf9 Mon Sep 17 00:00:00 2001 From: Mickey Li Date: Mon, 30 May 2022 13:42:04 +0100 Subject: [PATCH 33/36] Added support for configurable external vision stream for SITL --- simulator/base/px4/entrypoint.sh | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/simulator/base/px4/entrypoint.sh b/simulator/base/px4/entrypoint.sh index a11652f..20ff273 100755 --- a/simulator/base/px4/entrypoint.sh +++ b/simulator/base/px4/entrypoint.sh @@ -34,4 +34,17 @@ fi export PX4_SYSID=$((PX4_INSTANCE + 1)) echo "PX4 SYSID is set to $PX4_SYSID" +# Enable simulated external vision system in SITL +# To be used with the gazebo ros state (libgazebo_ros_state) plugin to emulate VICON +# See: https://docs.px4.io/master/en/advanced_config/tuning_the_ecl_ekf.html#external-vision-system +if [[ $ENABLE_EXTERNAL_VISION ]]; then + # Check if params have already been set + RCS_FILE="/src/PX4-Autopilot/ROMFS/px4fmu_common/init.d-posix/rcS" + if ! grep -q EKF2_AID_MASK "$RCS_FILE"; then + echo "Enabled External Vision in SITL, set EKF2_HGT_MODE to 3 and EKF2_AID_MASK to 24 (EV_POS+EV_YAW)" + sed -i '/param set IMU_INTEG_RATE 250/a param set EKF2_HGT_MODE 3' "$RCS_FILE" + sed -i '/param set IMU_INTEG_RATE 250/a param set EKF2_AID_MASK 24' "$RCS_FILE" + fi +fi + /ros_entrypoint.sh "$@" From b088faf3f9ee81705c1375080d86b917be50cebf Mon Sep 17 00:00:00 2001 From: Mickey Li Date: Tue, 31 May 2022 12:41:22 +0100 Subject: [PATCH 34/36] Updated README.md --- docs/docker-images/sim-base-px4.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/docker-images/sim-base-px4.md b/docs/docker-images/sim-base-px4.md index e0806e3..a42e666 100644 --- a/docs/docker-images/sim-base-px4.md +++ b/docs/docker-images/sim-base-px4.md @@ -20,6 +20,7 @@ Name | Default Value | Description `PX4_SIM_INIT_LOC_Z` | 0 | Virtual Z location for vehicle to spawn `PX4_SIM_FORCE_USE_SET_POSITION` | false | If multiple vehicles are spawning, by default they will spawn in a spiral. This forces the use of Init Locations `PX4_SYSID_SITL_BASE` | 200 | The base value of the minimum STIL instances actual instance is `PX4_SYSID_SITL_BASE` + `PX4_INSTACE` (not implemented yet) +`ENABLE_EXTERNAL_VISION` | | If this variable exists, sets set px4 params `EKF2_HGT_MODE` to 3 and `EKF2_AID_MASK` to 24 (EV_POS+EV_YAW) for use with a simulated vicon system. `KEEP_PX4_VEHICLES` | "" | [**BUILD ARG**] A string of the format `! -path ./ ! -path ./`... which specifies a list of extra vehicle PX4 models to keep on top of `sun`, `gps`, `iris` and `r1_rover`. `REMOVE_GAZEBO_LIBRARIES` | "libgazebo_video_stream_widget.so libgazebo_airspeed_plugin.so libgazebo_drop_plugin.so libgazebo_user_camera_plugin.so libgazebo_camera_manager_plugin.so libgazebo_gimbal_controller_plugin.so libgazebo_opticalflow_mockup_plugin.so libgazebo_opticalflow_plugin.so libgazebo_airship_dynamics_plugin.so libgazebo_catapult_plugin.so libgazebo_irlock_plugin.so libgazebo_parachute_plugin.so libgazebo_sonar_plugin.so libgazebo_usv_dynamics_plugin.so libgazebo_uuv_plugin.so libgazebo_wind_plugin.so" | [**BUILD ARG**] A space seperated list of libraries to be removed from the image. See below for details. From cb8790cda1421ec3b00b5d3334384ce1bf0dd349 Mon Sep 17 00:00:00 2001 From: mickey li Date: Fri, 17 Jun 2022 14:50:31 +0100 Subject: [PATCH 35/36] Updated dockerfile and cpp format --- simulator/base/core/Dockerfile | 1 - system/mavros/starling_mavros_extras/src/estop.cpp | 11 +++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/simulator/base/core/Dockerfile b/simulator/base/core/Dockerfile index 305b083..1a0d98e 100644 --- a/simulator/base/core/Dockerfile +++ b/simulator/base/core/Dockerfile @@ -77,7 +77,6 @@ ENV GZWEB_WS /root/gzweb #RUN git clone -b gzweb_1.4.0-gazebo11 https://github.com/eurogroep/gzweb.git ${GZWEB_WS} # Additionally patched version RUN git clone --depth 1 -b gzweb_1.4.2 https://github.com/rob-clarke/gzweb.git ${GZWEB_WS} -RUN sed -i "s$\"node-gyp\": \"\"$\"node-gyp\": \"8.4.1\"$" ${GZWEB_WS}/package.json # Build gzweb (default model generation) RUN source /ros.env \ diff --git a/system/mavros/starling_mavros_extras/src/estop.cpp b/system/mavros/starling_mavros_extras/src/estop.cpp index 4cb6c3c..219fa0a 100644 --- a/system/mavros/starling_mavros_extras/src/estop.cpp +++ b/system/mavros/starling_mavros_extras/src/estop.cpp @@ -15,11 +15,14 @@ EstopHandler::EstopHandler() : { this->mavros_command_srv = this->create_client("mavros/cmd/command", rmw_qos_profile_services_default); this->estop_sub = this->create_subscription( - "/emergency_stop", 1, [this](const std_msgs::msg::Empty::SharedPtr s){(void)s; - this->emergency_stop(); - }); + "/emergency_stop", 1, + [this](const std_msgs::msg::Empty::SharedPtr s){ + (void)s; + this->emergency_stop(); + } + ); - RCLCPP_INFO(this->get_logger(), "Estop Initialised"); + RCLCPP_INFO(this->get_logger(), "ESTOP Initialised"); } void EstopHandler::emergency_stop() { From 6119b7863642d1180829a2e93f463fe9830abd74 Mon Sep 17 00:00:00 2001 From: mickey li Date: Fri, 17 Jun 2022 18:48:59 +0100 Subject: [PATCH 36/36] changed to gzweb 1.8.5 to fix node-gyp issue --- simulator/base/core/Dockerfile | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/simulator/base/core/Dockerfile b/simulator/base/core/Dockerfile index 522471d..a0df598 100644 --- a/simulator/base/core/Dockerfile +++ b/simulator/base/core/Dockerfile @@ -77,13 +77,12 @@ ENV GZWEB_WS /root/gzweb # Patched version #RUN git clone -b gzweb_1.4.0-gazebo11 https://github.com/eurogroep/gzweb.git ${GZWEB_WS} # Additionally patched version -RUN git clone --depth 1 -b gzweb_1.4.2 https://github.com/rob-clarke/gzweb.git ${GZWEB_WS} -RUN sed -i "s$\"node-gyp\": \"\"$\"node-gyp\": \"8.4.1\"$" ${GZWEB_WS}/package.json +RUN git clone --depth 1 -b gzweb_1.4.5 https://github.com/rob-clarke/gzweb.git ${GZWEB_WS} # Build gzweb (no model generation) RUN source /ros.env \ && source /root/.nvm/nvm.sh \ - && cd ${GZWEB_WS} && npm run deploy --- -m local + && cd ${GZWEB_WS} && npm run deploy -- -m local # Setup ports # Main webserver port