From 16f49d4a3f44e093117cb5591a9bd674bab582df Mon Sep 17 00:00:00 2001 From: Alex Davies Date: Wed, 16 Oct 2024 09:13:46 -0300 Subject: [PATCH] Update docs --- .copier/answers.docs.yml | 5 ++ .github/workflows/build-docs.yaml | 44 ++++++++++++ README.md | 37 ++++++++-- docker-compose.yml | 10 ++- docs/Makefile | 20 ++++++ docs/README.md | 11 +++ docs/make.bat | 35 ++++++++++ docs/source/_static/custom.css | 34 +++++++++ docs/source/conf.py | 65 ++++++++++++++++++ docs/source/index.rst | 20 ++++++ .../source/logos/SPIRI_STLockup_Mixed_RGB.png | Bin 0 -> 14499 bytes guiTools/Dockerfile | 4 +- guiTools/launcher.py | 11 +-- 13 files changed, 277 insertions(+), 19 deletions(-) create mode 100644 .copier/answers.docs.yml create mode 100644 .github/workflows/build-docs.yaml create mode 100644 docs/Makefile create mode 100644 docs/README.md create mode 100644 docs/make.bat create mode 100644 docs/source/_static/custom.css create mode 100644 docs/source/conf.py create mode 100644 docs/source/index.rst create mode 100644 docs/source/logos/SPIRI_STLockup_Mixed_RGB.png diff --git a/.copier/answers.docs.yml b/.copier/answers.docs.yml new file mode 100644 index 0000000..d1c3d88 --- /dev/null +++ b/.copier/answers.docs.yml @@ -0,0 +1,5 @@ +# Changes here will be overwritten by Copier; NEVER EDIT MANUALLY +_commit: v1.0.2 +_src_path: https://git.spirirobotics.com/Spiri/template-docs.git +author_name: Spiri Robotics +project_name: spiri-sdk diff --git a/.github/workflows/build-docs.yaml b/.github/workflows/build-docs.yaml new file mode 100644 index 0000000..7985db5 --- /dev/null +++ b/.github/workflows/build-docs.yaml @@ -0,0 +1,44 @@ +name: Build Docs + +on: + push: + +env: + REGISTRY: git.spirirobotics.com + IMAGE_NAME: ${{ github.repository }} + +jobs: + build: + runs-on: ubuntu-latest + container: sphinxdoc/sphinx-latexpdf + + steps: + - name: Install sphinx-rtd-theme + run: pip install sphinx-rtd-theme + - name: Install node so that custom actions work. + run : apt-get update && apt-get --yes install nodejs git + + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Build Docs + run: make html latexpdf + working-directory: docs # assuming your documentation is in a 'docs' folder + + - name: Save PDF Artifacts + run: mv docs/build/latex/*.pdf ${{ github.workspace }}/docs.pdf + + - name: Compress HTML + run: tar -czvf docs_html.tar.gz -C docs/build/html . + + - name: Upload Docs + uses: actions/upload-artifact@v3 + with: + name: docs_html.tar.gz + path: docs_html.tar.gz + + - name: Upload PDF + uses: actions/upload-artifact@v3 + with: + name: docs.pdf + path: docs.pdf diff --git a/README.md b/README.md index 53d5858..5fda9a0 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,10 @@ -# Spiri SDK - Simulated robot - -The Spiri SDK consists of a number of components. What you're looking at right now -is the drone simulation component, which is the core of the SDK. +# Spiri SDK Spiri Robots run a number of docker containers to achieve their core functionality, we try to keep these essential docker containers in one docker compose file. The docker compose file you'll find in this repository starts an ardupilot-based UAV simulation -as well as a ROS master, and mavproxy to tie it together. +as well as a ROS master, and mavproxy to tie it together, mirroring the core deployment of +a spiri robot. To get started you can simply clone this repository and run `docker compose --profile uav-sim up`. @@ -15,6 +13,35 @@ MavLink compatible software. We expose the UAVs Mavlink conenction on tcp port 5 There is experimental GUI support you can enable by running `docker compose --profile uav-sim --profile ui up`. + +## Prerequisites + +The Spiri-SDK is intended for use on linux. +It was tested with docker engine `20.10.24` and NVIDIA Container Toolkit CLI version `1.16.2`. + +UI features like virtual camera support were tested with Nvidia GPUs using CDI passthrough, but could +work with other GPUs as long as they support CDI. + +Machine-learning features like image recognition are expected to only work with NVIDIA GPUs. + +### Installing Docker + +There are many ways to install docker for your platform. We recomend using your linux distribution's package +manager to install docker, using a command like `sudo apt-get install docker-compose-v2`. The exact command +will vary depending on your exact linux distribution. + +You can see the [official docker documentation](https://docs.docker.com/engine/install/) for more details. + +### Installing nvidia-container-toolkit + + +We recomend following the [offical guide](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html) to install the nvidia-container-toolkit. + +Make sure you run `sudo nvidia-ctk runtime configure --runtime=docker` and `sudo systemctl restart docker`. + +When installed correctly you should see your gpu available in `nvidia-ctk cdi list`. + + ## Creating a new project We provide project templates you can use for development that integrate seamlessly into diff --git a/docker-compose.yml b/docker-compose.yml index cabba4d..cb765d2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,13 +21,12 @@ services: # Wayland socket - ${XDG_RUNTIME_DIR}/wayland-0:${XDG_RUNTIME_DIR}/wayland-0 # Allow access to the host's GPU - - /dev/dri:/dev/dri - devices: - # Provide access to GPU devices - - /dev/dri:/dev/dri + # - /dev/dri:/dev/dri + # devices: + # # Provide access to GPU devices + # - /dev/dri:/dev/dri # network_mode: host ipc: host - #user: "${UID}:${GID}" privileged: true # Allow privileged access if necessary (e.g., for GPU access) # restart: unless-stopped # command: /bin/bash -c "source /opt/ros/foxy/setup.bash && rvis2" # Replace with the actual command to run Gazebo Ignition @@ -40,7 +39,6 @@ services: device_ids: - nvidia.com/gpu=all - ardupilot: image: git.spirirobotics.com/spiri/ardupilot:spiri-master command: > diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..d0c3cbf --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..44b6013 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,11 @@ +If you have a correctly configured sphinx environment you can build this project +using `make html latexpdf`. + +You can also use nektos/act to build this project in the same way our build does. +```bash +cd ../ #Make sure you're in the project root, you should have a hidden folder +# named ./.github/workflows available. +act --artifact-server-path ./doc-build +``` + +Your compiled doc project will now be in the ./doc-build folder. diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..747ffb7 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/source/_static/custom.css b/docs/source/_static/custom.css new file mode 100644 index 0000000..cdc1a92 --- /dev/null +++ b/docs/source/_static/custom.css @@ -0,0 +1,34 @@ +.wy-side-nav-search { + background: #FFFFFF !important; +} + + +.wy-nav-side { + background-color: #FFFFFF !important; +} + + +/* Add borders and box-shadow */ +.wy-side-nav { + border: 1px solid #899CA3 !important; +} + +.logo { + width: 100px !important; +} + +.wy-menu-vertical a { + color: #899CA3 !important; /* Change to your desired color */ +} + +.document-title { + color: #000 !important; + font-size: 24px !important; + text-transform: uppercase !important; +} + +.icon-home { + font-weight: bold !important; + text-transform: uppercase !important; + color: #000 !important; +} diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 0000000..d5b77c9 --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,65 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information +import sphinx_rtd_theme + +project = "spiri-sdk" +copyright = "2024, Spiri Robotics" +author = "Spiri Robotics" + +html_logo = "logos/SPIRI_STLockup_Mixed_RGB.png" # For HTML output +html_logo_width = '200px' +latex_logo = "logos/SPIRI_STLockup_Mixed_RGB.png" +latex_logo_width = '5cm' + + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +extensions = [ + "sphinx.ext.duration", + "sphinx.ext.doctest", + "sphinx.ext.autodoc", + "sphinx.ext.autosummary", + "sphinx.ext.intersphinx", + "sphinx.ext.todo", +] + + +numfig = True + +todo_include_todos = True +todo_emit_warnings = True +todo_link_only = True + + +templates_path = ["_templates"] +exclude_patterns = [] + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_theme = "sphinx_rtd_theme" +html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] + +html_static_path = ['_static'] + +html_css_files = [ + 'custom.css', +] + + +html_theme_options = { + 'collapse_navigation': True, + 'sticky_navigation': True, + 'navigation_depth': 4, #could be set to -1 if we want unlimited depth + 'includehidden': True, + 'titles_only': False +} + +latex_engine = "xelatex" +# Configure LaTeX options for PDF generation diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 0000000..e564e24 --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,20 @@ +.. spiri-sdk documentation master file, created by + sphinx-quickstart on Wed Feb 14 11:51:45 2024. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to spiri-sdk's documentation! +============================================ + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/source/logos/SPIRI_STLockup_Mixed_RGB.png b/docs/source/logos/SPIRI_STLockup_Mixed_RGB.png new file mode 100644 index 0000000000000000000000000000000000000000..e1a4800c7004200a51268dcea1a169e97e8051d5 GIT binary patch literal 14499 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