Work on moving sim-drone to our user interface
This commit is contained in:
parent
15110bd830
commit
ec7a53201f
|
@ -26,7 +26,10 @@ services:
|
||||||
# Allow access to the host's GPU
|
# Allow access to the host's GPU
|
||||||
- /dev/dri:/dev/dri
|
- /dev/dri:/dev/dri
|
||||||
#Auto reload on code changes
|
#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:
|
devices:
|
||||||
# Provide access to GPU devices
|
# Provide access to GPU devices
|
||||||
- /dev/dri:/dev/dri
|
- /dev/dri:/dev/dri
|
||||||
|
|
|
@ -4,15 +4,14 @@ RUN apt-get update
|
||||||
RUN apt-get -y install qterminal mesa-utils \
|
RUN apt-get -y install qterminal mesa-utils \
|
||||||
libgstreamer1.0-dev \
|
libgstreamer1.0-dev \
|
||||||
libgstreamer-plugins-base1.0-dev \
|
libgstreamer-plugins-base1.0-dev \
|
||||||
|
python3.12-venv \
|
||||||
|
python3-pip \
|
||||||
gstreamer1.0-libav \
|
gstreamer1.0-libav \
|
||||||
gstreamer1.0-gl \
|
gstreamer1.0-gl \
|
||||||
gstreamer1.0-plugins-good \
|
gstreamer1.0-plugins-good \
|
||||||
gstreamer1.0-plugins-bad \
|
gstreamer1.0-plugins-bad \
|
||||||
gstreamer1.0-plugins-ugly
|
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 /plugins /ardupilot_gazebo/plugins
|
||||||
COPY --from=git.spirirobotics.com/spiri/gazebo-resources:main /models /ardupilot_gazebo/models
|
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
|
WORKDIR /app
|
||||||
|
|
||||||
COPY ./pyproject.toml /app/pyproject.toml
|
RUN python3 -m venv /opt/venv
|
||||||
COPY ./poetry.lock /app/poetry.lock
|
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
|
CMD poetry run python3 spiri_sdk_guitools/launcher.py
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -666,6 +666,24 @@ MarkupSafe = ">=2.0"
|
||||||
[package.extras]
|
[package.extras]
|
||||||
i18n = ["Babel (>=2.7)"]
|
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]]
|
[[package]]
|
||||||
name = "markdown2"
|
name = "markdown2"
|
||||||
version = "2.5.1"
|
version = "2.5.1"
|
||||||
|
@ -1919,6 +1937,20 @@ files = [
|
||||||
{file = "websockets-13.1.tar.gz", hash = "sha256:a3b3366087c1bc0a2795111edcadddb8b3b59509d5db5d7ea3fdd69f954a8878"},
|
{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]]
|
[[package]]
|
||||||
name = "wsproto"
|
name = "wsproto"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
|
@ -2032,4 +2064,4 @@ propcache = ">=0.2.0"
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.11"
|
python-versions = "^3.11"
|
||||||
content-hash = "cd634ccba7b1059ce9cf2e92503f3ccce24693405aa53f78509482f944c55d5b"
|
content-hash = "941bf4750c3b8a3045cfd01bb1ab4af7c1e8fd2d5fa54cd85d8ac3a0c8837238"
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "spiri-sdk-guitools"
|
name = "spiri_sdk_guitools"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = ""
|
description = ""
|
||||||
authors = ["Alex Davies <traverse.da@gmail.com>"]
|
authors = ["Spiri Robotics"]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.11"
|
python = "^3.11"
|
||||||
nicegui = "^2.5.0"
|
nicegui = "^2.5.0"
|
||||||
pywebview = "^5.3.2"
|
pywebview = "^5.3.2"
|
||||||
|
loguru = "^0.7.2"
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry-core"]
|
requires = ["poetry-core"]
|
||||||
build-backend = "poetry.core.masonry.api"
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from nicegui import ui
|
from nicegui import ui, binding, app
|
||||||
import subprocess
|
import subprocess
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
# Dictionary of applications: key is the button text, value is the command to execute
|
# Dictionary of applications: key is the button text, value is the command to execute
|
||||||
applications = {
|
applications = {
|
||||||
|
@ -21,19 +22,38 @@ def launch_app(command):
|
||||||
# Create the NiceGUI interface
|
# Create the NiceGUI interface
|
||||||
ui.label("Spiri Robotics SDK").style('font-size: 40px; margin-bottom: 10px;').classes('w-full text-center')
|
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:
|
@ui.page('/')
|
||||||
tab_tools = ui.tab('Tools')
|
def main():
|
||||||
tab_robots = ui.tab('Robots')
|
|
||||||
# two = ui.tab('Two')
|
with ui.tabs().classes('w-full') as tabs:
|
||||||
with ui.tab_panels(tabs, value=tab_tools).classes():
|
tab_tools = ui.tab('Tools')
|
||||||
with ui.tab_panel(tab_tools):
|
tab_robots = ui.tab('Robots')
|
||||||
# Create and place buttons dynamically based on the dictionary
|
# two = ui.tab('Two')
|
||||||
with ui.grid(columns=3):
|
with ui.tab_panels(tabs, value=tab_tools).classes():
|
||||||
for app_name, command in applications.items():
|
with ui.tab_panel(tab_tools):
|
||||||
ui.button(app_name, on_click=lambda cmd=command: launch_app(cmd)).style('width: 150px; height: 50px; margin: 5px;')
|
# Create and place buttons dynamically based on the dictionary
|
||||||
with ui.tab_panel(tab_robots):
|
with ui.grid(columns=3):
|
||||||
with ui.card().tight():
|
for app_name, command in applications.items():
|
||||||
ui.label(f"Robots SysID")
|
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
|
# Start the NiceGUI application
|
||||||
ui.run(title="Spiri SDK Launcher", port=8923, dark=None)
|
ui.run(title="Spiri SDK Launcher", port=8923, dark=None)
|
||||||
|
|
|
@ -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)
|
Loading…
Reference in New Issue