mirror of
https://gitee.com/mirrors_PX4/PX4-Autopilot.git
synced 2026-10-03 06:18:52 +08:00
build(packaging): add PX4 SITL .deb packages
Add cmake/cpack infrastructure for building .deb packages from px4_sitl_sih and px4_sitl_default targets. Includes install rules, package scripts, Gazebo wrapper, and CI workflow. Signed-off-by: Ramon Roche <mrpollo@gmail.com>
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
# PX4 SITL Gazebo Harmonic runtime image
|
||||
# Runs PX4 SITL with Gazebo Harmonic. Supports X11 forwarding for GUI.
|
||||
#
|
||||
# Build:
|
||||
# make px4_sitl_default && cd build/px4_sitl_default && cpack -G DEB && cd ../..
|
||||
# docker build -f Tools/packaging/Dockerfile.gazebo -t px4io/px4-sitl-gazebo:v1.17.0 build/px4_sitl_default/
|
||||
#
|
||||
# Run (headless):
|
||||
# docker run --rm -it --network host px4io/px4-sitl-gazebo:v1.17.0
|
||||
#
|
||||
# Run (X11 GUI):
|
||||
# xhost +local:docker
|
||||
# docker run --rm -it --network host \
|
||||
# -e DISPLAY=$DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix \
|
||||
# --gpus all px4io/px4-sitl-gazebo:v1.17.0
|
||||
|
||||
FROM ubuntu:24.04 AS extract
|
||||
COPY px4-gazebo_*.deb /tmp/
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends binutils \
|
||||
&& dpkg -x /tmp/px4-gazebo_*.deb /staging \
|
||||
&& strip /staging/opt/px4-gazebo/bin/px4 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
FROM ubuntu:24.04
|
||||
LABEL maintainer="PX4 Development Team"
|
||||
LABEL description="PX4 SITL with Gazebo Harmonic simulation"
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
ENV RUNS_IN_DOCKER=true
|
||||
|
||||
# Install Gazebo Harmonic with buildkit cache mounts for apt
|
||||
# The --mount=type=cache persists /var/cache/apt and /var/lib/apt across builds
|
||||
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,target=/var/lib/apt,sharing=locked \
|
||||
apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
bc \
|
||||
ca-certificates \
|
||||
gnupg \
|
||||
lsb-release \
|
||||
wget \
|
||||
&& wget -q https://packages.osrfoundation.org/gazebo.gpg \
|
||||
-O /usr/share/keyrings/pkgs-osrf-archive-keyring.gpg \
|
||||
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/pkgs-osrf-archive-keyring.gpg] http://packages.osrfoundation.org/gazebo/ubuntu-stable $(lsb_release -cs) main" \
|
||||
> /etc/apt/sources.list.d/gazebo-stable.list \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
gz-harmonic
|
||||
|
||||
# Install PX4 files from .deb
|
||||
COPY --from=extract /staging/opt/px4-gazebo /opt/px4-gazebo
|
||||
RUN ln -sf /opt/px4-gazebo/bin/px4-gazebo /usr/bin/px4-gazebo
|
||||
|
||||
# Create the DART physics engine symlink (avoids needing the -dev package)
|
||||
RUN GZ_PHYSICS_DIR=$(find /usr/lib -maxdepth 3 -type d -name "engine-plugins" -path "*/gz-physics-7/*" 2>/dev/null | head -1) \
|
||||
&& if [ -n "$GZ_PHYSICS_DIR" ] && [ -d "$GZ_PHYSICS_DIR" ]; then \
|
||||
VERSIONED=$(ls "$GZ_PHYSICS_DIR"/libgz-physics*-dartsim-plugin.so.* 2>/dev/null | head -1) \
|
||||
&& [ -n "$VERSIONED" ] \
|
||||
&& ln -sf "$(basename "$VERSIONED")" "$GZ_PHYSICS_DIR/libgz-physics-dartsim-plugin.so"; \
|
||||
fi
|
||||
|
||||
# Gazebo resource paths
|
||||
ENV GZ_SIM_RESOURCE_PATH=/opt/px4-gazebo/share/gz/models:/opt/px4-gazebo/share/gz/worlds
|
||||
ENV GZ_SIM_SYSTEM_PLUGIN_PATH=/opt/px4-gazebo/lib/gz/plugins
|
||||
ENV GZ_SIM_SERVER_CONFIG_PATH=/opt/px4-gazebo/share/gz/server.config
|
||||
ENV PX4_GZ_MODELS=/opt/px4-gazebo/share/gz/models
|
||||
ENV PX4_GZ_WORLDS=/opt/px4-gazebo/share/gz/worlds
|
||||
|
||||
ENV PX4_SIM_MODEL=gz_x500
|
||||
ENV HOME=/root
|
||||
|
||||
# MAVLink, MAVSDK, DDS
|
||||
EXPOSE 14550/udp 14540/udp 8888/udp
|
||||
|
||||
# Platform-adaptive entrypoint: detects Docker Desktop (macOS/Windows) via
|
||||
# host.docker.internal and configures MAVLink + DDS to target the host.
|
||||
COPY px4-entrypoint.sh /opt/px4-gazebo/bin/px4-entrypoint.sh
|
||||
RUN chmod +x /opt/px4-gazebo/bin/px4-entrypoint.sh
|
||||
|
||||
WORKDIR /root
|
||||
|
||||
ENTRYPOINT ["/opt/px4-gazebo/bin/px4-entrypoint.sh"]
|
||||
CMD []
|
||||
@@ -0,0 +1,49 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
# PX4 SITL SIH runtime image
|
||||
# Minimal container that runs PX4 with the SIH physics engine (no Gazebo).
|
||||
#
|
||||
# Build:
|
||||
# make px4_sitl_sih && cd build/px4_sitl_sih && cpack -G DEB && cd ../..
|
||||
# docker build -f Tools/packaging/Dockerfile.sih -t px4io/px4-sitl-sih:v1.17.0 build/px4_sitl_sih/
|
||||
#
|
||||
# Run (Linux):
|
||||
# docker run --rm -it --network host px4io/px4-sitl-sih:v1.17.0
|
||||
#
|
||||
# Run (macOS / Windows):
|
||||
# docker run --rm -it -p 14550:14550/udp -p 14540:14540/udp -p 19410:19410/udp -p 8888:8888/udp px4io/px4-sitl-sih:v1.17.0
|
||||
|
||||
FROM ubuntu:24.04 AS build
|
||||
COPY px4_*.deb /tmp/
|
||||
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,target=/var/lib/apt,sharing=locked \
|
||||
apt-get update \
|
||||
&& apt-get install -y --no-install-recommends binutils \
|
||||
&& dpkg -x /tmp/px4_*.deb /staging \
|
||||
&& strip /staging/opt/px4/bin/px4
|
||||
|
||||
FROM ubuntu:24.04
|
||||
LABEL maintainer="PX4 Development Team"
|
||||
LABEL description="PX4 SITL with SIH physics (no simulator dependencies)"
|
||||
|
||||
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,target=/var/lib/apt,sharing=locked \
|
||||
apt-get update && apt-get install -y --no-install-recommends bc
|
||||
|
||||
COPY --from=build /staging/opt/px4 /opt/px4
|
||||
RUN ln -sf /opt/px4/bin/px4 /usr/bin/px4
|
||||
|
||||
# Platform-adaptive entrypoint: detects Docker Desktop (macOS/Windows) via
|
||||
# host.docker.internal and configures MAVLink + DDS to target the host.
|
||||
COPY px4-entrypoint.sh /opt/px4/bin/px4-entrypoint.sh
|
||||
RUN chmod +x /opt/px4/bin/px4-entrypoint.sh
|
||||
|
||||
ENV PX4_SIM_MODEL=sihsim_quadx
|
||||
ENV HOME=/root
|
||||
|
||||
# MAVLink (QGC, MAVSDK), DDS (ROS 2), jMAVSim/viewer display
|
||||
EXPOSE 14550/udp 14540/udp 19410/udp 8888/udp
|
||||
|
||||
WORKDIR /root
|
||||
|
||||
ENTRYPOINT ["/opt/px4/bin/px4-entrypoint.sh"]
|
||||
CMD []
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
ln -sf /opt/px4-gazebo/bin/px4-gazebo /usr/bin/px4-gazebo
|
||||
exit 0
|
||||
Executable
+6
@@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
if [ "$1" = "remove" ] || [ "$1" = "purge" ]; then
|
||||
rm -f /usr/bin/px4-gazebo
|
||||
fi
|
||||
exit 0
|
||||
@@ -0,0 +1,32 @@
|
||||
#!/bin/sh
|
||||
# Docker entrypoint for PX4 SITL containers.
|
||||
#
|
||||
# On Docker Desktop (macOS/Windows), host.docker.internal resolves to the
|
||||
# host machine. We detect this and configure MAVLink + DDS to send to the
|
||||
# host IP instead of localhost (which stays inside the container VM).
|
||||
#
|
||||
# On Linux with --network host, host.docker.internal does not resolve and
|
||||
# PX4 defaults work without modification.
|
||||
|
||||
set -e
|
||||
|
||||
# Detect install prefix (SIH uses /opt/px4, Gazebo uses /opt/px4-gazebo)
|
||||
if [ -d /opt/px4-gazebo ]; then
|
||||
PX4_PREFIX=/opt/px4-gazebo
|
||||
else
|
||||
PX4_PREFIX=/opt/px4
|
||||
fi
|
||||
|
||||
if getent hosts host.docker.internal >/dev/null 2>&1; then
|
||||
DOCKER_HOST_IP=$(getent hosts host.docker.internal | awk '{print $1}')
|
||||
|
||||
# MAVLink: replace default target (127.0.0.1) with the Docker host IP
|
||||
sed -i "s/mavlink start -x -u/mavlink start -x -t $DOCKER_HOST_IP -u/g" \
|
||||
"$PX4_PREFIX/etc/init.d-posix/px4-rc.mavlink"
|
||||
|
||||
# DDS: point uXRCE-DDS client at the host
|
||||
sed -i "s|uxrce_dds_client start -t udp|uxrce_dds_client start -t udp -h $DOCKER_HOST_IP|" \
|
||||
"$PX4_PREFIX/etc/init.d-posix/rcS"
|
||||
fi
|
||||
|
||||
exec "$PX4_PREFIX/bin/px4" "$@"
|
||||
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env bash
|
||||
# px4-gazebo: Launch PX4 SITL with Gazebo from the installed .deb package
|
||||
set -e
|
||||
|
||||
PX4_GAZEBO_DIR="$(cd "$(dirname "$(readlink -f "$0")")/.." && pwd)"
|
||||
PX4_BINARY="${PX4_GAZEBO_DIR}/bin/px4"
|
||||
|
||||
# Set Gazebo resource paths so gz-sim finds PX4 models, worlds, and plugins.
|
||||
export PX4_GZ_MODELS="${PX4_GAZEBO_DIR}/share/gz/models"
|
||||
export PX4_GZ_WORLDS="${PX4_GAZEBO_DIR}/share/gz/worlds"
|
||||
export PX4_GZ_PLUGINS="${PX4_GAZEBO_DIR}/lib/gz/plugins"
|
||||
export PX4_GZ_SERVER_CONFIG="${PX4_GAZEBO_DIR}/share/gz/server.config"
|
||||
export GZ_SIM_RESOURCE_PATH="${GZ_SIM_RESOURCE_PATH}:${PX4_GZ_MODELS}:${PX4_GZ_WORLDS}"
|
||||
export GZ_SIM_SYSTEM_PLUGIN_PATH="${GZ_SIM_SYSTEM_PLUGIN_PATH}:${PX4_GZ_PLUGINS}"
|
||||
export GZ_SIM_SERVER_CONFIG_PATH="${PX4_GZ_SERVER_CONFIG}"
|
||||
|
||||
# Gazebo's Physics system searches for "gz-physics-dartsim-plugin" which maps
|
||||
# to the unversioned libgz-physics-dartsim-plugin.so. The runtime package only
|
||||
# ships versioned .so files; the unversioned symlink lives in the -dev package.
|
||||
# Create it if missing so Gazebo finds the DART engine without installing -dev.
|
||||
GZ_PHYSICS_ENGINE_DIR=$(find /usr/lib -maxdepth 3 -type d -name "engine-plugins" -path "*/gz-physics-7/*" 2>/dev/null | head -1)
|
||||
if [ -n "$GZ_PHYSICS_ENGINE_DIR" ] && [ -d "$GZ_PHYSICS_ENGINE_DIR" ]; then
|
||||
UNVERSIONED="$GZ_PHYSICS_ENGINE_DIR/libgz-physics-dartsim-plugin.so"
|
||||
if [ ! -e "$UNVERSIONED" ]; then
|
||||
VERSIONED=$(ls "$GZ_PHYSICS_ENGINE_DIR"/libgz-physics*-dartsim-plugin.so.* 2>/dev/null | head -1)
|
||||
if [ -n "$VERSIONED" ]; then
|
||||
ln -sf "$(basename "$VERSIONED")" "$UNVERSIONED" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
exec "${PX4_BINARY}" "$@"
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
ln -sf /opt/px4/bin/px4 /usr/bin/px4
|
||||
exit 0
|
||||
Executable
+6
@@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
if [ "$1" = "remove" ] || [ "$1" = "purge" ]; then
|
||||
rm -f /usr/bin/px4
|
||||
fi
|
||||
exit 0
|
||||
@@ -0,0 +1,144 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
MAVSDK mission test for PX4 SIH SITL in Docker.
|
||||
|
||||
Takes off to 100m, flies a short 4-waypoint box mission, then lands.
|
||||
Validates that the SIH Docker container works end-to-end with MAVSDK.
|
||||
|
||||
Prerequisites:
|
||||
- Docker container running:
|
||||
docker run --rm --network host px4io/px4-sitl-sih:v1.17.0-alpha1
|
||||
- pip install mavsdk
|
||||
- mavsim-viewer running (optional):
|
||||
/path/to/mavsim-viewer -n 1
|
||||
|
||||
Usage:
|
||||
python3 Tools/packaging/test_sih_mission.py
|
||||
python3 Tools/packaging/test_sih_mission.py --speed 10 # faster-than-realtime
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import argparse
|
||||
import sys
|
||||
import time
|
||||
|
||||
from mavsdk import System
|
||||
from mavsdk.mission import MissionItem, MissionPlan
|
||||
|
||||
|
||||
async def run_mission(speed_factor: int = 1):
|
||||
drone = System()
|
||||
print(f"Connecting to drone on udp://:14540 ...")
|
||||
await drone.connect(system_address="udp://:14540")
|
||||
|
||||
print("Waiting for drone to connect...")
|
||||
async for state in drone.core.connection_state():
|
||||
if state.is_connected:
|
||||
print(f"Connected (UUID: {state.uuid if hasattr(state, 'uuid') else 'N/A'})")
|
||||
break
|
||||
|
||||
print("Waiting for global position estimate...")
|
||||
async for health in drone.telemetry.health():
|
||||
if health.is_global_position_ok and health.is_home_position_ok:
|
||||
print("Global position OK")
|
||||
break
|
||||
|
||||
# Get home position for reference
|
||||
async for pos in drone.telemetry.position():
|
||||
home_lat = pos.latitude_deg
|
||||
home_lon = pos.longitude_deg
|
||||
print(f"Home position: {home_lat:.6f}, {home_lon:.6f}")
|
||||
break
|
||||
|
||||
# Build a small box mission at 100m AGL
|
||||
# ~100m offset in each direction
|
||||
offset = 0.001 # roughly 111m at equator
|
||||
mission_items = [
|
||||
MissionItem(
|
||||
home_lat + offset, home_lon,
|
||||
100, 10, True, float('nan'), float('nan'),
|
||||
MissionItem.CameraAction.NONE,
|
||||
float('nan'), float('nan'), float('nan'),
|
||||
float('nan'), float('nan'),
|
||||
MissionItem.VehicleAction.NONE,
|
||||
),
|
||||
MissionItem(
|
||||
home_lat + offset, home_lon + offset,
|
||||
100, 10, True, float('nan'), float('nan'),
|
||||
MissionItem.CameraAction.NONE,
|
||||
float('nan'), float('nan'), float('nan'),
|
||||
float('nan'), float('nan'),
|
||||
MissionItem.VehicleAction.NONE,
|
||||
),
|
||||
MissionItem(
|
||||
home_lat, home_lon + offset,
|
||||
100, 10, True, float('nan'), float('nan'),
|
||||
MissionItem.CameraAction.NONE,
|
||||
float('nan'), float('nan'), float('nan'),
|
||||
float('nan'), float('nan'),
|
||||
MissionItem.VehicleAction.NONE,
|
||||
),
|
||||
MissionItem(
|
||||
home_lat, home_lon,
|
||||
100, 10, True, float('nan'), float('nan'),
|
||||
MissionItem.CameraAction.NONE,
|
||||
float('nan'), float('nan'), float('nan'),
|
||||
float('nan'), float('nan'),
|
||||
MissionItem.VehicleAction.NONE,
|
||||
),
|
||||
]
|
||||
|
||||
mission_plan = MissionPlan(mission_items)
|
||||
|
||||
print(f"Uploading mission ({len(mission_items)} waypoints, 100m AGL)...")
|
||||
await drone.mission.upload_mission(mission_plan)
|
||||
print("Mission uploaded")
|
||||
|
||||
print("Arming...")
|
||||
await drone.action.arm()
|
||||
print("Armed")
|
||||
|
||||
t0 = time.time()
|
||||
print("Starting mission...")
|
||||
await drone.mission.start_mission()
|
||||
|
||||
# Monitor mission progress
|
||||
async for progress in drone.mission.mission_progress():
|
||||
elapsed = time.time() - t0
|
||||
print(f" [{elapsed:6.1f}s] Waypoint {progress.current}/{progress.total}")
|
||||
if progress.current == progress.total:
|
||||
print(f"Mission complete in {elapsed:.1f}s (speed factor: {speed_factor}x)")
|
||||
break
|
||||
|
||||
print("Returning to launch...")
|
||||
await drone.action.return_to_launch()
|
||||
|
||||
# Wait for landing
|
||||
async for in_air in drone.telemetry.in_air():
|
||||
if not in_air:
|
||||
print("Landed")
|
||||
break
|
||||
|
||||
print("Disarming...")
|
||||
await drone.action.disarm()
|
||||
print("Test PASSED")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="PX4 SIH Docker mission test")
|
||||
parser.add_argument("--speed", type=int, default=1,
|
||||
help="PX4_SIM_SPEED_FACTOR (must match container)")
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
asyncio.run(run_mission(args.speed))
|
||||
except KeyboardInterrupt:
|
||||
print("\nInterrupted")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"Test FAILED: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user