diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..d40588b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,25 @@ +name: SITL Test + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + sitl-test: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Pull SITL Docker image + run: | + docker pull ghcr.io/uaarg/sitl:latest + + - name: Run tests + run: | + docker run --rm \ + -v $PWD:/workspace \ + ghcr.io/uaarg/sitl:latest \ + bash /workspace/scripts/dockertest.sh diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..15bc72f --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 769d737..90147eb 100644 --- a/README.md +++ b/README.md @@ -1,49 +1,41 @@ -# mavctl-python +![act-logo](https://raw.githubusercontent.com/wiki/nektos/act/img/logo-150.png) -An Open Source Library for Autonomous Drone Navigation. +# Overview [![push](https://github.com/nektos/act/workflows/push/badge.svg?branch=master&event=push)](https://github.com/nektos/act/actions) [![Go Report Card](https://goreportcard.com/badge/github.com/nektos/act)](https://goreportcard.com/report/github.com/nektos/act) [![awesome-runners](https://img.shields.io/badge/listed%20on-awesome--runners-blue.svg)](https://github.com/jonico/awesome-runners) -## Things to be Familiar With +> "Think globally, `act` locally" -Before diving into mavctl, it is recommended that you read up on or familiarize yourself with the following so that you have an easier time working on mavctl +Run your [GitHub Actions](https://developer.github.com/actions/) locally! Why would you want to do this? Two reasons: -1. MAVLink (https://mavlink.io/) -Take a look through introduction just to get an idea of how the MAVLink protocol works +- **Fast Feedback** - Rather than having to commit/push every time you want to test out the changes you are making to your `.github/workflows/` files (or for any changes to embedded GitHub actions), you can use `act` to run the actions locally. The [environment variables](https://help.github.com/en/actions/configuring-and-managing-workflows/using-environment-variables#default-environment-variables) and [filesystem](https://help.github.com/en/actions/reference/virtual-environments-for-github-hosted-runners#filesystems-on-github-hosted-runners) are all configured to match what GitHub provides. +- **Local Task Runner** - I love [make](). However, I also hate repeating myself. With `act`, you can use the GitHub Actions defined in your `.github/workflows/` to replace your `Makefile`! -2. Shepard (https://github.com/uaarg/shepard) -Take a look through here (https://github.com/uaarg/shepard/blob/main/src/modules/autopilot/navigator.py) to get an idea of what the navigation functions should sort of look like -Another thing to take a look at is the flight tests scripts as well (https://github.com/uaarg/shepard/blob/main/src/modules/autopilot/navigator.py) +> [!TIP] +> **Now Manage and Run Act Directly From VS Code!**
+> Check out the [GitHub Local Actions](https://sanjulaganepola.github.io/github-local-actions-docs/) Visual Studio Code extension which allows you to leverage the power of `act` to run and test workflows locally without leaving your editor. -3. DroneKit (OPTIONAL) (https://github.com/dronekit/dronekit-python) -While this is optional, a lot of things from DroneKit are borrowed, specifically the things that DroneKit does well. -Overall, mavctl-python is similar to dronekit but it is revamped and is meant to replace DroneKit for all of its functionality. -Over time we will reach a point where we will have exceeded dronekit but that time has not been reached just yet. +# How Does It Work? -## Contribution Guidelines +When you run `act` it reads in your GitHub Actions from `.github/workflows/` and determines the set of actions that need to be run. It uses the Docker API to either pull or build the necessary images, as defined in your workflow files and finally determines the execution path based on the dependencies that were defined. Once it has the execution path, it then uses the Docker API to run containers for each action based on the images prepared earlier. The [environment variables](https://help.github.com/en/actions/configuring-and-managing-workflows/using-environment-variables#default-environment-variables) and [filesystem](https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#file-systems) are all configured to match what GitHub provides. -Push all of your changes to a seperate branch and make a pull request if you would like to merge to main +Let's see it in action with a [sample repo](https://github.com/cplee/github-actions-demo)! -IMPORTANT: FOLLOW THE STRUCTURE. -It is very important for you to follow the structure that `mavctl-python` follows in terms of its operation. +![Demo](https://raw.githubusercontent.com/wiki/nektos/act/quickstart/act-quickstart-2.gif) -`mavctl-python` works as follows: +# Act User Guide -`MAVLink` connection is created and a `PyMAVLink` object is passed to the `Navigator` class. -In the `Navigator` class, there are methods which conduct the drone navigation (ex. move the drone 1m North) +Please look at the [act user guide](https://nektosact.com) for more documentation. -More advanced maneuvers should be located in their own files, but should NOT be in their own respective class. -Instead, you can pass the `Navigator` class object into these advanced maneuver functions. +# Support -The structure of that would look as follows: +Need help? Ask in [discussions](https://github.com/nektos/act/discussions)! -``` -import advanced +# Contributing -master = connect() -nav = Navigator() - -advanced_maneuver = advancedManeuver(nav) -``` - -`advanced` would be a file named `advanced.py` and `advancedManeuver()` would be the advanced maneuver method in question. +Want to contribute to act? Awesome! Check out the [contributing guidelines](CONTRIBUTING.md) to get involved. +## Manually building from source +- Install Go tools 1.20+ - () +- Clone this repo `git clone git@github.com:nektos/act.git` +- Run unit tests with `make test` +- Build and install: `make install` diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..69a8be9 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,25 @@ +FROM ubuntu:24.04 + +ARG DEBIAN_FRONTEND=noninteractive + +RUN apt update && apt install -y python3 python3-pip netcat-traditional +RUN apt install -y git + + +WORKDIR /work +RUN git clone --recurse-submodules https://github.com/ArduPilot/ardupilot.git +WORKDIR /work/ardupilot + +COPY patches /work/patches +RUN git apply /work/patches/01-SITL-ubuntu-deps.patch +RUN git apply /work/patches/02-SITL-no-usermod.patch + +RUN Tools/environment_install/install-prereqs-ubuntu.sh -y +RUN echo '. ~/venv-ardupilot/bin/activate' >> /root/.profile + +RUN . ~/venv-ardupilot/bin/activate && \ + ./waf configure --board sitl && \ + ./waf copter + +CMD . /root/venv-ardupilot/bin/activate +# Tools/autotest/sim_vehicle.py -v Copter --no-mavproxy -w -S 10 --udp diff --git a/docker/Dockerfile.sitl b/docker/Dockerfile.sitl new file mode 100644 index 0000000..54997fc --- /dev/null +++ b/docker/Dockerfile.sitl @@ -0,0 +1,36 @@ +FROM ubuntu:22.04 + +ENV DEBIAN_FRONTEND=noninteractive + +# Install dependencies +RUN apt update && apt install -y \ + git \ + python3 \ + python3-pip \ + python3-dev \ + build-essential \ + ccache \ + g++ \ + make \ + wget \ + netcat \ + && rm -rf /var/lib/apt/lists/* + +# Clone ArduPilot +WORKDIR /opt +RUN git clone https://github.com/ArduPilot/ardupilot.git +WORKDIR /opt/ardupilot + +RUN git submodule update --init --recursive + +# Install ArduPilot prerequisites +RUN Tools/environment_install/install-prereqs-ubuntu.sh -y + +# Configure + build SITL +RUN ./waf configure --board sitl +RUN ./waf copter + +# Install pytest + mav tools +RUN pip3 install pytest pymavlink mavctl + +WORKDIR /workspace diff --git a/mavctl/connect/conn.py b/mavctl/connect/conn.py index d97d25d..75267e9 100644 --- a/mavctl/connect/conn.py +++ b/mavctl/connect/conn.py @@ -13,7 +13,7 @@ class Connect: """Handles MAVLink connections and heartbeat monitors""" def __init__(self, - ip: str = "udp:127.0.0.1:14552", + ip: str = "udp:127.0.0.1:14551", baud: int = 57600, heartbeat_timeout: Optional[int] = None): @@ -31,7 +31,7 @@ def __init__(self, self.ip = ip - def connect(self, ip: str = "udp:127.0.0.1:14552", + def connect(self, ip: str = "udp:127.0.0.1:14551", baud: int = 57600, heartbeat_timeout: Optional[int] = None) -> mavutil.mavlink_connection: """ @@ -49,7 +49,7 @@ def connect(self, ip: str = "udp:127.0.0.1:14552", ConnError: If heartbeat or the connection fails """ try: - master = mavutil.mavlink_connection(ip, baud) + master = mavutil.mavlink_connection(device=ip, baud=baud) msg_recv = master.recv_match(type="HEARTBEAT", blocking=False) while not msg_recv: i = 0 diff --git a/patches/01-SITL-ubuntu-deps.patch b/patches/01-SITL-ubuntu-deps.patch new file mode 100644 index 0000000..a355d46 --- /dev/null +++ b/patches/01-SITL-ubuntu-deps.patch @@ -0,0 +1,91 @@ +diff --git a/Tools/environment_install/install-prereqs-ubuntu.sh b/Tools/environment_install/install-prereqs-ubuntu.sh +index 2dfcd34ec4..cccae745d2 100755 +--- a/Tools/environment_install/install-prereqs-ubuntu.sh ++++ b/Tools/environment_install/install-prereqs-ubuntu.sh +@@ -3,11 +3,6 @@ echo "---------- $0 start ----------" + set -e + set -x + +-if [ $EUID == 0 ]; then +- echo "Please do not run this script as root; don't sudo it!" +- exit 1 +-fi +- + OPT="/opt" + # Ardupilot Tools + ARDUPILOT_TOOLS="Tools/autotest" +@@ -29,7 +24,7 @@ while getopts "yq" opt; do + esac + done + +-APT_GET="sudo apt-get" ++APT_GET="apt-get" + if $ASSUME_YES; then + APT_GET="$APT_GET --assume-yes" + fi +@@ -173,7 +168,7 @@ if [ "$ARM_PKG_CONFIG_NOT_PRESENT" -eq 1 ]; then + $APT_GET install pkg-config + if [ -f /usr/share/pkg-config-crosswrapper ]; then + # We are on non-Ubuntu so simulate effect of installing pkg-config-arm-linux-gnueabihf. +- sudo ln -sf /usr/share/pkg-config-crosswrapper /usr/bin/arm-linux-gnueabihf-pkg-config ++ ln -sf /usr/share/pkg-config-crosswrapper /usr/bin/arm-linux-gnueabihf-pkg-config + else + echo "Warning: unable to link to pkg-config-crosswrapper" + fi +@@ -241,17 +236,17 @@ function install_arm_none_eabi_toolchain() { + heading "Installing toolchain for STM32 Boards" + echo "Installing toolchain for STM32 Boards" + echo "Downloading from ArduPilot server" +- sudo wget --progress=dot:giga https://firmware.ardupilot.org/Tools/STM32-tools/gcc-arm-none-eabi-10-2020-q4-major-x86_64-linux.tar.bz2 ++ wget --progress=dot:giga https://firmware.ardupilot.org/Tools/STM32-tools/gcc-arm-none-eabi-10-2020-q4-major-x86_64-linux.tar.bz2 + echo "Installing..." +- sudo chmod -R 777 gcc-arm-none-eabi-10-2020-q4-major-x86_64-linux.tar.bz2 +- sudo tar xjf gcc-arm-none-eabi-10-2020-q4-major-x86_64-linux.tar.bz2 ++ chmod -R 777 gcc-arm-none-eabi-10-2020-q4-major-x86_64-linux.tar.bz2 ++ tar xjf gcc-arm-none-eabi-10-2020-q4-major-x86_64-linux.tar.bz2 + echo "... Cleaning" +- sudo rm gcc-arm-none-eabi-10-2020-q4-major-x86_64-linux.tar.bz2 ++ rm gcc-arm-none-eabi-10-2020-q4-major-x86_64-linux.tar.bz2 + ) + fi + echo "Registering STM32 Toolchain for ccache" +- sudo ln -s -f $CCACHE_PATH /usr/lib/ccache/arm-none-eabi-g++ +- sudo ln -s -f $CCACHE_PATH /usr/lib/ccache/arm-none-eabi-gcc ++ ln -s -f $CCACHE_PATH /usr/lib/ccache/arm-none-eabi-g++ ++ ln -s -f $CCACHE_PATH /usr/lib/ccache/arm-none-eabi-gcc + echo "Done!";; + + aarch64) +@@ -261,17 +256,17 @@ function install_arm_none_eabi_toolchain() { + heading "Installing toolchain for STM32 Boards" + echo "Installing toolchain for STM32 Boards" + echo "Downloading from ArduPilot server" +- sudo wget --progress=dot:giga https://firmware.ardupilot.org/Tools/STM32-tools/gcc-arm-none-eabi-10-2020-q4-major-aarch64-linux.tar.bz2 ++ wget --progress=dot:giga https://firmware.ardupilot.org/Tools/STM32-tools/gcc-arm-none-eabi-10-2020-q4-major-aarch64-linux.tar.bz2 + echo "Installing..." +- sudo chmod -R 777 gcc-arm-none-eabi-10-2020-q4-major-aarch64-linux.tar.bz2 +- sudo tar xjf gcc-arm-none-eabi-10-2020-q4-major-aarch64-linux.tar.bz2 ++ chmod -R 777 gcc-arm-none-eabi-10-2020-q4-major-aarch64-linux.tar.bz2 ++ tar xjf gcc-arm-none-eabi-10-2020-q4-major-aarch64-linux.tar.bz2 + echo "... Cleaning" +- sudo rm gcc-arm-none-eabi-10-2020-q4-major-aarch64-linux.tar.bz2 ++ rm gcc-arm-none-eabi-10-2020-q4-major-aarch64-linux.tar.bz2 + ) + fi + echo "Registering STM32 Toolchain for ccache" +- sudo ln -s -f $CCACHE_PATH /usr/lib/ccache/arm-none-eabi-g++ +- sudo ln -s -f $CCACHE_PATH /usr/lib/ccache/arm-none-eabi-gcc ++ ln -s -f $CCACHE_PATH /usr/lib/ccache/arm-none-eabi-g++ ++ ln -s -f $CCACHE_PATH /usr/lib/ccache/arm-none-eabi-gcc + echo "Done!";; + esac + } +@@ -290,7 +285,7 @@ function maybe_prompt_user() { + } + + heading "Add user to dialout group to allow managing serial ports" +-sudo usermod -a -G dialout $USER ++usermod -a -G dialout $USER + echo "Done!" + + # Add back python symlink to python interpreter on Ubuntu >= 20.04 diff --git a/patches/02-SITL-no-usermod.patch b/patches/02-SITL-no-usermod.patch new file mode 100644 index 0000000..6678f76 --- /dev/null +++ b/patches/02-SITL-no-usermod.patch @@ -0,0 +1,15 @@ +diff --git a/Tools/environment_install/install-prereqs-ubuntu.sh b/Tools/environment_install/install-prereqs-ubuntu.sh +index cccae745d2..beb03e4694 100755 +--- a/Tools/environment_install/install-prereqs-ubuntu.sh ++++ b/Tools/environment_install/install-prereqs-ubuntu.sh +@@ -284,10 +284,6 @@ function maybe_prompt_user() { + fi + } + +-heading "Add user to dialout group to allow managing serial ports" +-usermod -a -G dialout $USER +-echo "Done!" +- + # Add back python symlink to python interpreter on Ubuntu >= 20.04 + if [ ${RELEASE_CODENAME} == 'focal' ]; + then diff --git a/scripts/dockertest.sh b/scripts/dockertest.sh new file mode 100755 index 0000000..55e834a --- /dev/null +++ b/scripts/dockertest.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +set -e + +source /root/venv-ardupilot/bin/activate +pip install --upgrade pip +pip install pytest pymavlink pexpect + +# Start SITL if not running +if ! pgrep -f ArduCopter > /dev/null; then + echo "Starting SITL..." + Tools/autotest/sim_vehicle.py -v ArduCopter \ + --no-mavproxy \ + --no-rebuild \ + --speedup 1 \ + --udp \ + --out=udp:127.0.0.1:14550 \ + --instance 0 & + SITL_PID=$! +else + echo "SITL already running" +fi + +mavproxy.py \ + --master=udp:127.0.0.1:5760 \ + --out=udp:127.0.0.1:14551 \ + --daemon & +MAVPROXY_PID=$! + +# Ensure SITL is ready +echo "Waiting for SITL process..." +until pgrep -f ArduCopter > /dev/null; do + sleep 1 +done +echo "SITL process detected" + +echo "SITL ready" + +sleep 30 + +cd /workspace +# Run tests +echo "Running python script" + +#echo "Running pytest..." +PYTHONPATH=. pytest + +# Kill SITL if started here +if [ ! -z "$SITL_PID" ]; then + echo "Stopping SITL..." + kill $SITL_PID + wait $SITL_PID || true +fi + +# Kill Mavproxy if started here +if [ ! -z "$MAVPROXY_PID" ]; then + echo "Stopping MAVROXY" + kill $MAVPROXY_PID + wait $MAVPROXY_PID || true +fi diff --git a/test.py b/test.py deleted file mode 100644 index 5f71414..0000000 --- a/test.py +++ /dev/null @@ -1,14 +0,0 @@ -from mavctl.messages.navigator import Navigator -from mavctl.connect.conn import Connect -import time - -CONN_STR = "udp:127.0.0.1:14553" - -connect = Connect(ip = CONN_STR) -drone = Navigator(connect.master) - -time.sleep(2) - -drone.DO_NOT_USE_SET_MODE() -time.sleep(1) -drone.DO_NOT_USE_ARM() diff --git a/tests/test_arm_disarm.py b/tests/test_arm_disarm.py new file mode 100644 index 0000000..122a31d --- /dev/null +++ b/tests/test_arm_disarm.py @@ -0,0 +1,33 @@ +import time +import pytest + +from mavctl.messages.navigator import Navigator + +CONN_STR = "udp:127.0.0.1:14551" + + +@pytest.fixture(scope="module") +def drone(): + """ + Create a Navigator instance once per test module. + """ + nav = Navigator(ip=CONN_STR) + + yield nav + + +def test_arm_disarm(drone): + """ + Basic integration test: + - Arm vehicle + - Confirm armed + - Disarm vehicle + - Confirm disarmed + """ + + drone.arm() + time.sleep(2) + + drone.disarm() + time.sleep(2) +