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: services:
gui-tools: gui-tools:
env_file: env_file:
@ -9,44 +7,40 @@ services:
environment: environment:
# Display settings # Display settings
- DISPLAY=${DISPLAY} DISPLAY: "${DISPLAY}"
- WAYLAND_DISPLAY=${WAYLAND_DISPLAY} WAYLAND_DISPLAY: "${WAYLAND_DISPLAY}"
# - QT_QPA_PLATFORM=${QT_QPA_PLATFORM:-xcb} # Default to X11 # Uncomment below if using X11
# If running with Wayland # QT_QPA_PLATFORM: "${QT_QPA_PLATFORM:-xcb}"
- XDG_RUNTIME_DIR=${XDG_RUNTIME_DIR} XDG_RUNTIME_DIR: "${XDG_RUNTIME_DIR}"
- ROS_MASTER_URI=http://ros-master:11311 ROS_MASTER_URI: "http://ros-master:11311"
# - NVIDIA_DRIVER_CAPABILITIES=compute,video,utility
# - NVIDIA_VISIBLE_DEVICES=all
volumes: volumes:
# X11 socket # X11 socket
- /tmp/.X11-unix:/tmp/.X11-unix - /tmp/.X11-unix:/tmp/.X11-unix
- ${XAUTHORITY:-~/.Xauthority}:/root/.Xauthority - ${XAUTHORITY:-~/.Xauthority}:/root/.Xauthority
# Wayland socket # Wayland socket
#- ${XDG_RUNTIME_DIR}/wayland-0:${XDG_RUNTIME_DIR}/wayland-0 #- ${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 - /dev/dri:/dev/dri
#Auto reload on code changes # Code and configuration
- ./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
- ./robots:/robots - ./robots:/robots
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
devices: devices:
# Provide access to GPU devices # Provide access to GPU devices (supports non-NVIDIA GPUs)
- /dev/dri:/dev/dri - /dev/dri:/dev/dri
network_mode: host network_mode: host
ports: ports:
- 8923:8923 - 8923:8923
ipc: host ipc: host
#user: "${UID}:${GID}" privileged: true # Required for GPU access
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
# 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') ui.label("Spiri Robotics SDK").style('font-size: 40px; margin-bottom: 10px;').classes('w-full text-center')
robots = [] robots = []
from spiri_sdk_guitools.sim_drone import Robot from spiri_sdk_guitools.sim_drone import robot_types
import aiodocker import aiodocker
import asyncio import asyncio
@ -64,30 +64,9 @@ async def main():
#Add a new robot #Add a new robot
with new_robot_widget: with new_robot_widget:
ui.label("Add new robot").classes("text-3xl") for name, robot in robot_types.items():
newRobotParams = defaultdict(binding.BindableProperty) ui.label(f"Add new {name}").classes("text-3xl")
ui.number(value=1, label="SysID", min=1, max=254, await robot.launch_widget(robots_widget)
).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)
# 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)

View File

@ -5,13 +5,15 @@ from typing import List
import os import os
import sh import sh
import subprocess import subprocess
from nicegui import ui, run, app from nicegui import ui, run, app, binding
import yaml import yaml
import docker import docker
import aiodocker import aiodocker
import asyncio import asyncio
from spiri_sdk_guitools.video_button import EnableStreamingButton from spiri_sdk_guitools.video_button import EnableStreamingButton
from collections import defaultdict
import importlib.util
docker_client = docker.from_env() docker_client = docker.from_env()
@ -20,12 +22,15 @@ from rclpy.node import Node
import rclpy import rclpy
import threading import threading
async def ros_loop():
def ros_main() -> None:
rclpy.init() 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 @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)) # ui.html(conv.convert(bytes(log,'utf-8').decode('utf-8', 'xmlcharrefreplace'), full=False))
robot_types = {}
class Robot: 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" robot_type = "spiri_mu"
def __init__(self, sysid: int, compose_files: List[Path] | str): 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.robot_name = f"{self.robot_type}_{self.sysid}".replace("-","_")
self.world_name = "citadel_hill" 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): async def ui_containers(self, element):
docker_elements = {} docker_elements = {}
container_status = {} container_status = {}
@ -152,6 +199,9 @@ class Robot:
ui.label(topic[0]) ui.label(topic[0])
await asyncio.sleep(10) await asyncio.sleep(10)
async def ui_actions(self, element):
pass
async def ui(self, element): async def ui(self, element):
adocker = aiodocker.Docker() adocker = aiodocker.Docker()
@ -175,11 +225,14 @@ class Robot:
with ui.tabs() as tabs: with ui.tabs() as tabs:
tab_containers = ui.tab("Containers") tab_containers = ui.tab("Containers")
tab_ros = ui.tab("ROS Topics") tab_ros = ui.tab("ROS Topics")
tab_actions = ui.tab("Actions")
with ui.tab_panels(tabs, value=tab_containers): with ui.tab_panels(tabs, value=tab_containers):
tab = ui.tab_panel(tab_containers).classes("w-full") tab = ui.tab_panel(tab_containers).classes("w-full")
asyncio.create_task(self.ui_containers(tab)) asyncio.create_task(self.ui_containers(tab))
tab = ui.tab_panel(tab_ros).classes("w-full") tab = ui.tab_panel(tab_ros).classes("w-full")
asyncio.create_task(self.ui_ros(tab)) 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): async def async_stop(self):
return await run.io_bound(self.stop) return await run.io_bound(self.stop)