Work on moving sim-drone to our user interface

This commit is contained in:
Alex Davies 2024-11-05 14:49:32 -04:00
parent 15110bd830
commit ec7a53201f
7 changed files with 158 additions and 26 deletions

View File

@ -26,7 +26,10 @@ services:
# Allow access to the host's GPU
- /dev/dri:/dev/dri
#Auto reload on code changes
- ./guiTools/spiri_sdk_guitools/:/app/spiri_sdk_guitools
- ./guiTools/spiri_sdk_guitools/:/app/spiri_sdk_guitools/
# Enable launching the SDK from the SDK
- ./:/app/sdk
- /var/run/docker.sock:/var/run/docker.sock
devices:
# Provide access to GPU devices
- /dev/dri:/dev/dri

View File

@ -4,15 +4,14 @@ RUN apt-get update
RUN apt-get -y install qterminal mesa-utils \
libgstreamer1.0-dev \
libgstreamer-plugins-base1.0-dev \
python3.12-venv \
python3-pip \
gstreamer1.0-libav \
gstreamer1.0-gl \
gstreamer1.0-plugins-good \
gstreamer1.0-plugins-bad \
gstreamer1.0-plugins-ugly
#Install poetry
RUN curl -sSL https://install.python-poetry.org | python3 -
ENV PATH="/root/.local/bin:$PATH"
COPY --from=git.spirirobotics.com/spiri/gazebo-resources:main /plugins /ardupilot_gazebo/plugins
COPY --from=git.spirirobotics.com/spiri/gazebo-resources:main /models /ardupilot_gazebo/models
@ -26,14 +25,21 @@ RUN chmod +x /spawn_drones.sh
WORKDIR /app
COPY ./pyproject.toml /app/pyproject.toml
COPY ./poetry.lock /app/poetry.lock
RUN python3 -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
RUN pip3 install poetry
COPY ./pyproject.toml ./poetry.lock ./README.md ./
COPY ./spiri_sdk_guitools ./spiri_sdk_guitools
RUN poetry env use python3
RUN poetry config virtualenvs.create false && poetry install --no-dev --no-interaction --no-ansi
RUN poetry install --no-root
COPY ./spiri_sdk_guitools /app/spiri_sdk_guitools
CMD poetry run python3 spiri_sdk_guitools/launcher.py

34
guiTools/poetry.lock generated
View File

@ -666,6 +666,24 @@ MarkupSafe = ">=2.0"
[package.extras]
i18n = ["Babel (>=2.7)"]
[[package]]
name = "loguru"
version = "0.7.2"
description = "Python logging made (stupidly) simple"
optional = false
python-versions = ">=3.5"
files = [
{file = "loguru-0.7.2-py3-none-any.whl", hash = "sha256:003d71e3d3ed35f0f8984898359d65b79e5b21943f78af86aa5491210429b8eb"},
{file = "loguru-0.7.2.tar.gz", hash = "sha256:e671a53522515f34fd406340ee968cb9ecafbc4b36c679da03c18fd8d0bd51ac"},
]
[package.dependencies]
colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""}
win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""}
[package.extras]
dev = ["Sphinx (==7.2.5)", "colorama (==0.4.5)", "colorama (==0.4.6)", "exceptiongroup (==1.1.3)", "freezegun (==1.1.0)", "freezegun (==1.2.2)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v1.4.1)", "mypy (==v1.5.1)", "pre-commit (==3.4.0)", "pytest (==6.1.2)", "pytest (==7.4.0)", "pytest-cov (==2.12.1)", "pytest-cov (==4.1.0)", "pytest-mypy-plugins (==1.9.3)", "pytest-mypy-plugins (==3.0.0)", "sphinx-autobuild (==2021.3.14)", "sphinx-rtd-theme (==1.3.0)", "tox (==3.27.1)", "tox (==4.11.0)"]
[[package]]
name = "markdown2"
version = "2.5.1"
@ -1919,6 +1937,20 @@ files = [
{file = "websockets-13.1.tar.gz", hash = "sha256:a3b3366087c1bc0a2795111edcadddb8b3b59509d5db5d7ea3fdd69f954a8878"},
]
[[package]]
name = "win32-setctime"
version = "1.1.0"
description = "A small Python utility to set file creation time on Windows"
optional = false
python-versions = ">=3.5"
files = [
{file = "win32_setctime-1.1.0-py3-none-any.whl", hash = "sha256:231db239e959c2fe7eb1d7dc129f11172354f98361c4fa2d6d2d7e278baa8aad"},
{file = "win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2"},
]
[package.extras]
dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"]
[[package]]
name = "wsproto"
version = "1.2.0"
@ -2032,4 +2064,4 @@ propcache = ">=0.2.0"
[metadata]
lock-version = "2.0"
python-versions = "^3.11"
content-hash = "cd634ccba7b1059ce9cf2e92503f3ccce24693405aa53f78509482f944c55d5b"
content-hash = "941bf4750c3b8a3045cfd01bb1ab4af7c1e8fd2d5fa54cd85d8ac3a0c8837238"

View File

@ -1,16 +1,17 @@
[tool.poetry]
name = "spiri-sdk-guitools"
name = "spiri_sdk_guitools"
version = "0.1.0"
description = ""
authors = ["Alex Davies <traverse.da@gmail.com>"]
authors = ["Spiri Robotics"]
readme = "README.md"
[tool.poetry.dependencies]
python = "^3.11"
nicegui = "^2.5.0"
pywebview = "^5.3.2"
loguru = "^0.7.2"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

View File

View File

@ -1,5 +1,6 @@
from nicegui import ui
from nicegui import ui, binding, app
import subprocess
from collections import defaultdict
# Dictionary of applications: key is the button text, value is the command to execute
applications = {
@ -21,19 +22,38 @@ def launch_app(command):
# Create the NiceGUI interface
ui.label("Spiri Robotics SDK").style('font-size: 40px; margin-bottom: 10px;').classes('w-full text-center')
robots = []
from spiri_sdk_guitools.sim_drone import Robot
with ui.tabs().classes('w-full') as tabs:
tab_tools = ui.tab('Tools')
tab_robots = ui.tab('Robots')
# two = ui.tab('Two')
with ui.tab_panels(tabs, value=tab_tools).classes():
with ui.tab_panel(tab_tools):
# Create and place buttons dynamically based on the dictionary
with ui.grid(columns=3):
for app_name, command in applications.items():
ui.button(app_name, on_click=lambda cmd=command: launch_app(cmd)).style('width: 150px; height: 50px; margin: 5px;')
with ui.tab_panel(tab_robots):
with ui.card().tight():
ui.label(f"Robots SysID")
@ui.page('/')
def main():
with ui.tabs().classes('w-full') as tabs:
tab_tools = ui.tab('Tools')
tab_robots = ui.tab('Robots')
# two = ui.tab('Two')
with ui.tab_panels(tabs, value=tab_tools).classes():
with ui.tab_panel(tab_tools):
# Create and place buttons dynamically based on the dictionary
with ui.grid(columns=3):
for app_name, command in applications.items():
ui.button(app_name, on_click=lambda cmd=command: launch_app(cmd)).style('width: 150px; height: 50px; margin: 5px;')
with ui.tab_panel(tab_robots):
with ui.grid(columns=3):
for robot in robots:
with ui.row():
ui.label(f"Robot {robot.sys_id}")
ui.button(f"Start Robot {robot.sys_id}", on_click=lambda: robot.start()).style('width: 150px; height: 50px; margin: 5px;')
#Add a new robot
with ui.element():
ui.label("Add new robot")
newRobotParams = defaultdict(binding.BindableProperty)
ui.number(value=0, label="SysID").bind_value(newRobotParams, 'sysid')
ui.input(value="./sdk/docker-compose.yml", label="Compose files (comma seperated)").bind_value(newRobotParams, 'compose_files')
def new_robot():
robot = Robot(**newRobotParams)
robots.append(robot)
ui.label(str(robots))
ui.button("Add", on_click=new_robot)
# Start the NiceGUI application
ui.run(title="Spiri SDK Launcher", port=8923, dark=None)

View File

@ -0,0 +1,70 @@
from loguru import logger
from pathlib import Path
import contextlib
from typing import List
@contextlib.contextmanager
def modified_environ(*remove, **update):
"""
Temporarily updates the ``os.environ`` dictionary in-place.
The ``os.environ`` dictionary is updated in-place so that the modification
is sure to work in all situations.
:param remove: Environment variables to remove.
:param update: Dictionary of environment variables and values to add/update.
"""
env = os.environ
update = update or {}
remove = remove or []
# List of environment variables being updated or removed.
stomped = (set(update.keys()) | set(remove)) & set(env.keys())
# Environment variables and values to restore on exit.
update_after = {k: env[k] for k in stomped}
# Environment variables and values to remove on exit.
remove_after = frozenset(k for k in update if k not in env)
try:
env.update(update)
[env.pop(k, None) for k in remove]
yield
finally:
env.update(update_after)
[env.pop(k) for k in remove_after]
class Robot:
def __init__(self, sysid: int, compose_files: List[Path]):
if sysid > 255 or sysid < 0:
raise ValueError("sysid must be between 0 and 255")
self.processes = []
def start(self):
"""Starts the simulated drone with a given sys_id,
each drone must have it's own unique ID.
"""
instance = self.sysid-1
with logger.contextualize(syd_id=sys_id):
env = os.environ
with modified_environ(
SERIAL0_PORT=str(int(env["SERIAL0_PORT"]) + 10 * instance),
MAVROS2_PORT=str(int(env["MAVROS2_PORT"]) + 10 * instance),
MAVROS1_PORT=str(int(env["MAVROS1_PORT"]) + 10 * instance),
FDM_PORT_IN=str(int(env["FDM_PORT_IN"]) + 10 * instance),
SITL_PORT=str(int(env["SITL_PORT"]) + 10 * instance),
INSTANCE=str(instance),
DRONE_SYS_ID=str(self.sys_id),
):
logger.info("Starting drone stack, this may take some time")
docker_stack = sh.docker.compose(
"--profile",
"uav-sim",
"-p",
f"spiri-sdk-{sys_id}",
"up",
_out=outputLogger("docker_stack", sys_id),
_err=sys.stderr,
_bg=True,
)
self.processes.append(docker_stack)