Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,7 @@
"runServices": ["sim"],
"workspaceFolder": "/home/trickfire/gazebo-simulations",
"postCreateCommand": "[ -d .venv ] || (python3 -m venv --system-site-packages .venv && . .venv/bin/activate && pip install -e .)",
// "postStartCommand": "bash ./.devcontainer/x_server.sh",
"mounts": [
"source=${localEnv:HOME}/.ssh,target=/home/trickfire/.ssh,type=bind,consistency=cached",
"source=/tmp/.X11-unix,target=/tmp/.X11-unix,type=bind,consistency=cached"
],
"postStartCommand": "bash ./.devcontainer/x_server.sh || exit 0",
"forwardPorts": [6080, 5900],
"customizations": {
"vscode": {
Expand Down
11 changes: 10 additions & 1 deletion cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@
import argparse
import shutil
import sys
import threading

from .docker import build_and_launch
from .docker import build_and_launch, check_display
from .native import build_and_launch_native
from .create import create as robot_create, update as robot_update, PACKAGE_DIR, REPO_ROOT
from .create.commands import cmd_local, cmd_raw
from .auth import auth as cmd_auth
from .output import die, info
from .paths import WORKSPACE_DIR
from .drpc import rpc_start


def main() -> None:
Expand Down Expand Up @@ -134,8 +136,15 @@ def main() -> None:

try:
if args.command == "docker":
threading.Thread(
target=rpc_start, kwargs={"is_docker": True, "robot_name": args.robot}, daemon=True
).start()
check_display()
build_and_launch(args.robot, build_only=args.build_only, no_build=args.no_build)
elif args.command == "native":
threading.Thread(
target=rpc_start, kwargs={"is_docker": False, "robot_name": args.robot}, daemon=True
).start()
build_and_launch_native(args.robot, build_only=args.build_only, no_build=args.no_build)
elif args.command == "clean":
for name in ("build", "install", "log"):
Expand Down
25 changes: 14 additions & 11 deletions cli/docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,20 @@ def _run_logged_command(
raise subprocess.CalledProcessError(return_code, command)


def check_display() -> None:
"""Check that a display is available and can be connected to"""
info("Checking for display...")
display = os.environ.copy().get("DISPLAY")
if not display:
die("DISPLAY environment variable not set")

display_running = subprocess.call(
["xdpyinfo", "-display", display], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
)
if display_running != 0:
die("Cannot connect to display " + display)


def build_and_launch(robot_name: str, *, build_only: bool = False, no_build: bool = False) -> None:
"""Build the workspace and launch a robot simulation."""
if build_only and no_build:
Expand Down Expand Up @@ -171,17 +185,6 @@ def build_and_launch(robot_name: str, *, build_only: bool = False, no_build: boo
info("Build-only requested; skipping launch")
return

info("Checking for display...")
display = env.get("DISPLAY")
if not display:
die("DISPLAY environment variable not set")

display_running = subprocess.call(["xdpyinfo", "-display", display],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL)
if display_running != 0:
die("Cannot connect to display " + display)

info("Sourcing ROS2 environment and launching simulation...")
launch_script = f"""
set -e
Expand Down
49 changes: 49 additions & 0 deletions cli/drpc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"""Discord Rich Presence (DRPC) integration for TrickFire Simulation"""

import os
import sys
import time

from pypresence import Presence


def _find_discord_socket_dir() -> str | None:
"""Return the directory containing a discord-ipc-N socket, or None if not found."""
candidates = [
os.environ.get("XDG_RUNTIME_DIR"), # standard Linux user session
os.environ.get("TMPDIR"),
"/run/host-runtime", # devcontainer: host XDG_RUNTIME_DIR mounted here
"/run/user/1000", # fallback for some Linux distros
"/tmp",
]
for base in filter(None, candidates):
for i in range(10):
if os.path.exists(os.path.join(base, f"discord-ipc-{i}")):
return base
return None


def rpc_start(is_docker: bool = False, robot_name: str = "Unknown Robot") -> None:
"""Start DRPC in a separate thread"""
try:
if sys.platform == "linux":
socket_dir = _find_discord_socket_dir()
if socket_dir is None:
print("Discord RPC unavailable: no Discord IPC socket found")
return
# pypresence checks TMPDIR early in its path search; point it at the socket dir
# so it works both natively and inside the devcontainer (/run/host-runtime).
os.environ["TMPDIR"] = socket_dir

client_id = "1504990746527404063"
rpc = Presence(client_id)
rpc.connect()
rpc.update(
name="TrickFire Simulation (" + robot_name + ")",
details="Running TrickFire Simulation on " + robot_name,
state="Running in Docker" if is_docker else "Running locally",
)
while True:
time.sleep(15)
except Exception as e: # pylint: disable=broad-except
print(f"Discord RPC unavailable: {e}")
2 changes: 2 additions & 0 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ RUN printf '#include <stdlib.h>\nint drmCloseBufferHandle(int fd, unsigned int h
# --------------------------------------
# Python packages

RUN pip3 install --break-system-packages \
pypresence
RUN rosdep init || true

# --------------------------------------
Expand Down
13 changes: 13 additions & 0 deletions docker/docker-compose-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,16 @@ services:
target: dev
args:
USER_UID: "${UID:-1000}"
volumes:
- ${XDG_RUNTIME_DIR:-/tmp}:/run/host-runtime:ro
- type: bind
source: ${HOME}/.ssh
target: /home/trickfire/.ssh
read_only: true
bind:
create_host_path: true
- type: bind
source: /tmp/.X11-unix
target: /tmp/.X11-unix
bind:
create_host_path: true
5 changes: 5 additions & 0 deletions docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ services:

volumes:
- ..:/home/trickfire/gazebo-simulations
- type: bind
source: ${XDG_RUNTIME_DIR:-/run/user/1000}
target: /run/user/1000
bind:
create_host_path: true

working_dir: /home/trickfire/gazebo-simulations

Expand Down
20 changes: 20 additions & 0 deletions pixi.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pixi.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ scripts = [".devcontainer/pixi.sh"]
[pypi-dependencies]
sim = { path = ".", editable = true }
filelock = "*"
pypresence = "*"
Loading