Compare commits

...

4 Commits

Author SHA1 Message Date
a5207c6b28 Added plugin loader
Some checks failed
Build Docs / build (push) Failing after 31s
2024-11-23 16:57:04 -04:00
3e4aad9094 Fixed ros spin for asyncio, added actions tab 2024-11-23 16:50:09 -04:00
af94edae60 Refactor to make launch part of Robot. Auto detect compose files 2024-11-23 16:33:39 -04:00
ad40afac9b Removed nvidia GPU requirement for gui_tools 2024-11-23 16:05:14 -04:00
3 changed files with 81 additions and 55 deletions

View File

@ -1,5 +1,3 @@
version: "3.8"
services:
gui-tools:
env_file:
@ -9,44 +7,40 @@ services:
environment:
# Display settings
- DISPLAY=${DISPLAY}
- WAYLAND_DISPLAY=${WAYLAND_DISPLAY}
# - QT_QPA_PLATFORM=${QT_QPA_PLATFORM:-xcb} # Default to X11
# If running with Wayland
- XDG_RUNTIME_DIR=${XDG_RUNTIME_DIR}
- ROS_MASTER_URI=http://ros-master:11311
# - NVIDIA_DRIVER_CAPABILITIES=compute,video,utility
# - NVIDIA_VISIBLE_DEVICES=all
DISPLAY: "${DISPLAY}"
WAYLAND_DISPLAY: "${WAYLAND_DISPLAY}"
# Uncomment below if using X11
# QT_QPA_PLATFORM: "${QT_QPA_PLATFORM:-xcb}"
XDG_RUNTIME_DIR: "${XDG_RUNTIME_DIR}"
ROS_MASTER_URI: "http://ros-master:11311"
volumes:
# X11 socket
- /tmp/.X11-unix:/tmp/.X11-unix
- ${XAUTHORITY:-~/.Xauthority}:/root/.Xauthority
# Wayland socket
#- ${XDG_RUNTIME_DIR}/wayland-0:${XDG_RUNTIME_DIR}/wayland-0
# Allow access to the host's GPU
# Access to GPU devices
- /dev/dri:/dev/dri
#Auto reload on code changes
# Code and configuration
- ./guiTools/spiri_sdk_guitools/:/app/spiri_sdk_guitools/
# Enable launching the SDK from the SDK
# - ./:/app/sdk
- ./robots:/robots
- /var/run/docker.sock:/var/run/docker.sock
devices:
# Provide access to GPU devices
# Provide access to GPU devices (supports non-NVIDIA GPUs)
- /dev/dri:/dev/dri
network_mode: host
ports:
- 8923:8923
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
deploy:
resources:
reservations:
devices:
- driver: cdi
device_ids:
- nvidia.com/gpu=all
privileged: true # Required for GPU access
# deploy:
# resources:
# reservations:
# devices:
# - driver: cdi # Optional for advanced resource scheduling
# device_ids:
# - "all" # Use "all" for all available GPUs

View File

@ -27,7 +27,7 @@ def launch_app(command):
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
from spiri_sdk_guitools.sim_drone import robot_types
import aiodocker
import asyncio
@ -64,30 +64,9 @@ async def main():
#Add a new robot
with new_robot_widget:
ui.label("Add new robot").classes("text-3xl")
newRobotParams = defaultdict(binding.BindableProperty)
ui.number(value=1, label="SysID", min=1, max=254,
).bind_value(newRobotParams, 'sysid')
default_robot_compose = (
"/robots/spiri-mu/core/docker-compose.yaml\n"
"#/robots/spiri-mu/virtual_camera/docker-compose.yaml --build"
)
ui.label("Compose files").classes("text-xl")
ui.codemirror(value=default_robot_compose, language="bash", theme="basicDark").bind_value(newRobotParams, 'compose_files')
async def new_robot():
compose_files = []
#Split on comma or newline, and remove comments
for line in newRobotParams['compose_files'].split('\n'):
line = line.split('#')[0].strip()
if line:
compose_files.append(line)
current_robot = newRobotParams.copy()
current_robot['compose_files'] = compose_files
robot = Robot(**current_robot)
asyncio.tasks.create_task(robot.ui(robots_widget))
newRobotParams['sysid'] += 1
ui.button("Add", on_click=new_robot)
for name, robot in robot_types.items():
ui.label(f"Add new {name}").classes("text-3xl")
await robot.launch_widget(robots_widget)
# Start the NiceGUI application
ui.run(title="Spiri SDK Launcher", port=8923, dark=None)

View File

@ -5,13 +5,15 @@ from typing import List
import os
import sh
import subprocess
from nicegui import ui, run, app
from nicegui import ui, run, app, binding
import yaml
import docker
import aiodocker
import asyncio
from spiri_sdk_guitools.video_button import EnableStreamingButton
from collections import defaultdict
import importlib.util
docker_client = docker.from_env()
@ -20,12 +22,15 @@ from rclpy.node import Node
import rclpy
import threading
def ros_main() -> None:
async def ros_loop():
rclpy.init()
node = rclpy.create_node('async_subscriber')
while rclpy.ok():
rclpy.spin_once(node, timeout_sec=0)
await asyncio.sleep(0.1)
app.on_startup(lambda: threading.Thread(target=ros_main).start())
app.on_startup(ros_loop())
@contextlib.contextmanager
@ -73,7 +78,22 @@ async def container_logs(container, element):
# ui.html(conv.convert(bytes(log,'utf-8').decode('utf-8', 'xmlcharrefreplace'), full=False))
robot_types = {}
class Robot:
def __init_subclass__(self):
#Register sub-classes as plugins
robot_types[self.robot_type] = self
for file in Path("/robots").glob("**/robot_plugins.py"):
logger.info(f"Loading plugin {file}")
spec = importlib.util.spec_from_file_location("robot_plugins", file)
plugin = importlib.util.module_from_spec(spec)
spec.loader.exec_module(plugin)
logger.info(f"Loaded plugin {file}")
class Spirimu(Robot):
robot_type = "spiri_mu"
def __init__(self, sysid: int, compose_files: List[Path] | str):
@ -92,6 +112,33 @@ class Robot:
self.robot_name = f"{self.robot_type}_{self.sysid}".replace("-","_")
self.world_name = "citadel_hill"
@classmethod
async def launch_widget(cls, robots_widget):
newRobotParams = defaultdict(binding.BindableProperty)
ui.number(value=1, label="SysID", min=1, max=254,
).bind_value(newRobotParams, 'sysid')
default_robot_compose = ""
for compose_file in Path("/robots").glob("**/docker-compose.yaml"):
default_robot_compose += f"{compose_file} --build \n"
ui.label("Compose files").classes("text-xl")
ui.codemirror(value=default_robot_compose, language="bash", theme="basicDark").bind_value(newRobotParams, 'compose_files')
async def new_robot():
compose_files = []
#Split on comma or newline, and remove comments
for line in newRobotParams['compose_files'].split('\n'):
line = line.split('#')[0].strip()
if line:
compose_files.append(line)
current_robot = newRobotParams.copy()
current_robot['compose_files'] = compose_files
robot = cls(**current_robot)
asyncio.tasks.create_task(robot.ui(robots_widget))
newRobotParams['sysid'] += 1
ui.button("Add", on_click=new_robot)
async def ui_containers(self, element):
docker_elements = {}
container_status = {}
@ -152,6 +199,9 @@ class Robot:
ui.label(topic[0])
await asyncio.sleep(10)
async def ui_actions(self, element):
pass
async def ui(self, element):
adocker = aiodocker.Docker()
@ -175,11 +225,14 @@ class Robot:
with ui.tabs() as tabs:
tab_containers = ui.tab("Containers")
tab_ros = ui.tab("ROS Topics")
tab_actions = ui.tab("Actions")
with ui.tab_panels(tabs, value=tab_containers):
tab = ui.tab_panel(tab_containers).classes("w-full")
asyncio.create_task(self.ui_containers(tab))
tab = ui.tab_panel(tab_ros).classes("w-full")
asyncio.create_task(self.ui_ros(tab))
tab = ui.tab_panel(tab_actions).classes("w-full")
asyncio.create_task(self.ui_actions(tab))
async def async_stop(self):
return await run.io_bound(self.stop)