Compare commits
4 Commits
9298a57211
...
a5207c6b28
Author | SHA1 | Date | |
---|---|---|---|
a5207c6b28 | |||
3e4aad9094 | |||
af94edae60 | |||
ad40afac9b |
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user