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
119 changes: 109 additions & 10 deletions .ci/gazebo.Dockerfile
Original file line number Diff line number Diff line change
@@ -1,12 +1,93 @@
# .ci/gazebo.Dockerfile — SAKURA-II Gazebo Harmonic headless SITL image.
# .ci/gazebo.Dockerfile — SAKURA-II Gazebo Harmonic SITL image (GUI-capable).
#
# Builds from ubuntu:22.04 + OSRF apt repository because
# ghcr.io/gazebosim/gz-sim:harmonic requires authentication.
# Runs gz sim in headless server mode (-s flag); no display required.
# Simulation world SDF and plugins are mounted read-only from ./simulation.
# Two-stage build:
# Stage 1 (builder): installs Gazebo dev headers, compiles all 4 system
# plugins into shared libraries.
# Stage 2 (runtime): installs Gazebo runtime + X11/Mesa GUI packages, copies
# .so files from builder. Final image is ~400 MB smaller than single-stage.
#
# GUI support: the runtime stage includes Mesa llvmpipe (software renderer) and
# X11 client libraries so gz sim can open a window when $DISPLAY is forwarded
# from the host via /tmp/.X11-unix (see compose.yaml gazebo service).
# LIBGL_ALWAYS_SOFTWARE=1 forces Mesa llvmpipe; no GPU passthrough required.
#
# Plugin discovery: GZ_SIM_SYSTEM_PLUGIN_PATH=/usr/local/lib (Harmonic API).
# World file discovery: GZ_SIM_RESOURCE_PATH=/app/simulation/worlds.
# No HPSC cross-toolchain (Q-H8 deferred to Phase C+).
# No secrets embedded.

# ── Stage 1: builder ──────────────────────────────────────────────────────────
FROM ubuntu:22.04 AS builder

ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update -q && \
apt-get install -y --no-install-recommends \
curl \
gnupg \
lsb-release \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*

# Add OSRF Gazebo Harmonic apt repository.
RUN curl -fsSL 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 -q && \
apt-get install -y --no-install-recommends \
gz-harmonic \
libgz-sim8-dev \
libgz-transport13-dev \
libgz-plugin2-dev \
libgz-math7-dev \
cmake \
g++ \
make \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*

# Copy plugin source trees into the builder.
COPY simulation/gazebo_rover_plugin /src/gazebo_rover_plugin
COPY simulation/gazebo_uav_plugin /src/gazebo_uav_plugin
COPY simulation/gazebo_cryobot_plugin /src/gazebo_cryobot_plugin
COPY simulation/gazebo_world_plugin /src/gazebo_world_plugin

# Build and install each plugin. Unit-test targets (uav_plugin_test, etc.)
# have no Gazebo dependency and are skipped here (BUILD_TESTING=OFF).
RUN cmake -B /build/rover \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=/usr/local \
-DBUILD_TESTING=OFF \
/src/gazebo_rover_plugin && \
cmake --build /build/rover && \
cmake --install /build/rover

RUN cmake -B /build/uav \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=/usr/local \
-DBUILD_TESTING=OFF \
/src/gazebo_uav_plugin && \
cmake --build /build/uav && \
cmake --install /build/uav

RUN cmake -B /build/cryobot \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=/usr/local \
-DBUILD_TESTING=OFF \
/src/gazebo_cryobot_plugin && \
cmake --build /build/cryobot && \
cmake --install /build/cryobot

RUN cmake -B /build/world \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=/usr/local \
-DBUILD_TESTING=OFF \
/src/gazebo_world_plugin && \
cmake --build /build/world && \
cmake --install /build/world

# ── Stage 2: runtime ─────────────────────────────────────────────────────────
FROM ubuntu:22.04

ENV DEBIAN_FRONTEND=noninteractive
Expand All @@ -20,22 +101,40 @@ RUN apt-get update -q && \
procps \
&& rm -rf /var/lib/apt/lists/*

# Add OSRF Gazebo Harmonic apt repository.
# NOTE: the echo must be a single line — a multi-line echo with indented
# continuation puts leading spaces into the URI, which APT silently ignores,
# making gz-harmonic unlocalizable.
# Add OSRF Gazebo Harmonic apt repository (runtime packages only).
RUN curl -fsSL 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 -q && \
apt-get install -y --no-install-recommends \
gz-harmonic \
# Mesa llvmpipe software renderer — GPU-free OpenGL for Docker containers.
libgl1-mesa-dri \
libgl1 \
libegl1 \
libglu1-mesa \
# X11 client libraries for forwarding the GUI window to the host display.
libx11-6 \
libx11-xcb1 \
libxrender1 \
libxext6 \
libxcb1 \
&& rm -rf /var/lib/apt/lists/*

# Copy compiled plugin .so files from the builder stage.
COPY --from=builder /usr/local/lib/librover_drive_plugin.so /usr/local/lib/
COPY --from=builder /usr/local/lib/libuav_flight_plugin.so /usr/local/lib/
COPY --from=builder /usr/local/lib/libcryobot_physics_plugin.so /usr/local/lib/
COPY --from=builder /usr/local/lib/libworld_environment_plugin.so /usr/local/lib/

RUN ldconfig

RUN mkdir -p /app/simulation

# Plugin and world file discovery path for gz sim.
# GZ_SIM_RESOURCE_PATH: world .sdf and mesh files mounted from ./simulation
# GZ_SIM_SYSTEM_PLUGIN_PATH: where gz-sim scans for compiled system plugins
ENV GZ_SIM_RESOURCE_PATH=/app/simulation/worlds
ENV GZ_SIM_SYSTEM_PLUGIN_PATH=/usr/local/lib

WORKDIR /app
14 changes: 14 additions & 0 deletions .ci/ros2.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,20 @@ FROM osrf/space-ros:latest
# log/ and install/ outputs there without a permission error.
USER root
RUN mkdir -p /workspace && chown spaceros-user:spaceros-user /workspace

# Install ros_gz_bridge and ros_gz_sim for Gazebo Harmonic ↔ ROS 2 topic bridging.
# The || true allows the build to proceed if the Space ROS apt overlay does not
# carry these packages — the colcon build in compose.yaml then builds them from
# source via the ros_gz_src clone below.
RUN apt-get update -q && \
apt-get install -y --no-install-recommends \
ros-${ROS_DISTRO}-ros-gz-bridge \
ros-${ROS_DISTRO}-ros-gz-sim \
&& rm -rf /var/lib/apt/lists/* \
|| (rm -rf /var/lib/apt/lists/* && \
git clone --depth 1 -b harmonic \
https://github.com/gazebosim/ros_gz /workspace/ros_gz_src || true)

USER spaceros-user

WORKDIR /workspace
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ add_subdirectory(apps/orbiter_payload)
add_subdirectory(apps/mcu_payload_gw)
add_subdirectory(apps/mcu_rwa_gw)
add_subdirectory(apps/mcu_eps_gw)
add_subdirectory(apps/sim_adapter)

# ── Simulation Plugins ────────────────────────────────────────────────────────
# Gazebo plugins require Gazebo to be installed. Each subdirectory guards
Expand Down
17 changes: 16 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,31 @@
# make stop stop and remove containers (named volumes are preserved)
# make logs stream combined logs from all running services (Ctrl-C to exit)
# make clean stop containers AND remove named volumes (destructive)
#
# Gazebo GUI: on a desktop with X11, the Gazebo 3D window opens automatically.
# On a headless server (no DISPLAY), Gazebo runs server-only (no window).
# Requires x11-xserver-utils (xhost) on the host: apt install x11-xserver-utils

.PHONY: run stop logs clean

run:
@if [ -n "$$DISPLAY" ]; then \
echo "X11 display detected ($$DISPLAY) — granting Docker X11 access..."; \
xhost +local:docker 2>/dev/null || echo " (xhost not found — install x11-xserver-utils if the Gazebo window does not appear)"; \
else \
echo "No DISPLAY set — Gazebo will run headless (server-only)."; \
fi
docker compose up --build -d
@echo ""
@echo "Stack is starting. Current service status:"
@docker compose ps
@echo ""
@echo " Ground station UI → http://localhost:8080/api/time"
@if [ -n "$$DISPLAY" ]; then \
echo " Gazebo 3D view → opens automatically (X11 forwarded)"; \
else \
echo " Gazebo → headless (no DISPLAY set)"; \
fi
@echo " Ground station API → http://localhost:8080/api/time"
@echo " UDP telemetry in → localhost:10000"
@echo ""
@echo " make logs — stream all service logs"
Expand Down
4 changes: 4 additions & 0 deletions _defs/mids.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@
/* ── Sim injection block anchor (sideband only; never flight-path) ──────────── */
#define SIM_INJECT_HK_MID 0x0D00U /* 0x0800 | 0x500 */

/* ── sim_adapter MIDs (SITL only; CFS_FLIGHT_BUILD excluded) ────────────────── */
#define SIM_ADAPTER_CMD_MID 0x1D00U /* 0x1800 | 0x500 */
#define SIM_ADAPTER_HK_MID 0x0D01U /* 0x0800 | 0x501 */

/* ── Fault-injection SPP APIDs (sideband only; ICD-sim-fsw.md §2) ───────────
* These are raw APID values (not cFE MIDs) used by simulation/fault_injector.
* They are never placed on the cFE Software Bus (CFS_FLIGHT_BUILD guard
Expand Down
1 change: 1 addition & 0 deletions _defs/targets.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ set(MISSION_APPS
mcu_payload_gw
mcu_rwa_gw
mcu_eps_gw
sim_adapter
)

message(STATUS "Mission: ${MISSION_NAME} SCID: ${SPACECRAFT_ID} Apps: ${MISSION_APPS}")
11 changes: 11 additions & 0 deletions apps/sim_adapter/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# apps/sim_adapter/CMakeLists.txt
#
# Build contract provided by sakura_add_cfs_app() (see _defs/cfs_app_template.cmake):
# sim_adapter OBJECT library (FSW source)
# sim_adapter_test CMocka unit-test executable
# sim_adapter_unit_tests CTest entry
# sim_adapter_cppcheck cppcheck development target
#
# CFS_FLIGHT_BUILD guard: the entire sim_adapter implementation is excluded from
# flight builds by #ifndef CFS_FLIGHT_BUILD in sim_adapter.c / sim_adapter.h.
sakura_add_cfs_app(sim_adapter)
102 changes: 102 additions & 0 deletions apps/sim_adapter/fsw/src/cfe.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
#ifndef CFE_H
#define CFE_H

/*
* cfe.h — Minimal cFE / OSAL type and constant stubs for sim_adapter standalone builds.
*
* Extends orbiter_comm's stub pattern with:
* - CFE_EVS_BinFilter_t (event filter struct for EVS_Register)
* - CFE_MSG_SetMsgId / CFE_MSG_SetSize (message header setters)
* - OS_SocketBind / OS_SocketRecvFrom (OSAL receive-side socket API)
* - OS_ERR_TIMEOUT constant
*
* MISRA C:2012 Rule 20.5 deviation: conditionally compiled; superseded by the
* real cfe.h when cFS is present on the include path ahead of this stub.
*/

#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdarg.h>

/* ── Primitive typedefs (mirror cFE common_types.h) ─────────────────────── */
typedef int32_t int32;
typedef uint16_t uint16;
typedef uint32_t uint32;
typedef uint8_t uint8;

/* ── Software Bus types ──────────────────────────────────────────────────── */
typedef uintptr_t CFE_SB_PipeId_t;
typedef uint32_t CFE_SB_MsgId_Atom_t;
typedef CFE_SB_MsgId_Atom_t CFE_SB_MsgId_t;

/* CCSDS primary-header minimum (6 bytes) plus secondary-header stub (10 bytes) */
typedef struct { uint8 Byte[16]; } CFE_MSG_Message_t;
typedef struct { CFE_MSG_Message_t Msg; } CFE_SB_Buffer_t;
typedef uint16_t CFE_MSG_FcnCode_t;

/* ── Event Services types ────────────────────────────────────────────────── */
typedef struct {
uint16 EventID;
uint16 Mask;
} CFE_EVS_BinFilter_t;

/* ── Status codes ────────────────────────────────────────────────────────── */
#define CFE_SUCCESS ((int32) 0)
#define CFE_SB_BAD_ARGUMENT ((int32)-1)
#define CFE_SB_PIPE_RD_ERR ((int32)-2)
#define CFE_SB_MAX_MSGS_MET ((int32)-3)
#define CFE_ES_ERR_APP_REGISTER ((int32)-4)
#define CFE_EVS_APP_FILTER_OVERLOAD ((int32)-5)

/* ── Executive Services constants ───────────────────────────────────────── */
#define CFE_ES_RunStatus_APP_RUN 1U
#define CFE_ES_RunStatus_APP_ERROR 2U
#define CFE_ES_RunStatus_APP_EXIT 3U

/* ── Software Bus constants ─────────────────────────────────────────────── */
#define CFE_SB_PEND_FOREVER ((int32)-1)
#define CFE_SB_INVALID_MSG_ID ((CFE_SB_MsgId_t)0xFFFFU)

/* ── Event Services constants ────────────────────────────────────────────── */
#define CFE_EVS_EventFilter_BINARY 1U
#define CFE_EVS_EventType_INFORMATION 1U
#define CFE_EVS_EventType_ERROR 4U

/* ── API declarations (implemented by cFE or by UNIT_TEST stubs) ─────────── */
int32 CFE_ES_RegisterApp(void);
bool CFE_ES_RunLoop(uint32 *RunStatus);
void CFE_ES_ExitApp(uint32 ExitStatus);

int32 CFE_EVS_Register(const void *Filters, uint16 NumFilters, uint16 FilterScheme);
void CFE_EVS_SendEvent(uint16 EventID, uint16 EventType, const char *Spec, ...);

int32 CFE_SB_CreatePipe(CFE_SB_PipeId_t *PipeIdPtr, uint16 Depth, const char *PipeName);
int32 CFE_SB_Subscribe(CFE_SB_MsgId_t MsgId, CFE_SB_PipeId_t PipeId);
int32 CFE_SB_ReceiveBuffer(CFE_SB_Buffer_t **BufPtr, CFE_SB_PipeId_t PipeId, int32 TimeOut);
int32 CFE_SB_TransmitMsg(CFE_MSG_Message_t *MsgPtr, bool IncrementSequenceCount);
CFE_SB_MsgId_Atom_t CFE_SB_MsgIdToValue(CFE_SB_MsgId_t MsgId);
CFE_SB_MsgId_t CFE_SB_ValueToMsgId(CFE_SB_MsgId_Atom_t MsgIdValue);

int32 CFE_MSG_GetMsgId(const CFE_MSG_Message_t *MsgPtr, CFE_SB_MsgId_t *MsgId);
int32 CFE_MSG_GetFcnCode(const CFE_MSG_Message_t *MsgPtr, CFE_MSG_FcnCode_t *FcnCode);
int32 CFE_MSG_SetMsgId(CFE_MSG_Message_t *MsgPtr, CFE_SB_MsgId_t MsgId);
int32 CFE_MSG_SetSize(CFE_MSG_Message_t *MsgPtr, uint32 TotalMsgSize);

/* ── OSAL network types ──────────────────────────────────────────────────── */
typedef uint32_t osal_id_t;
typedef struct { uint8_t AddrData[28]; } OS_SockAddr_t;
typedef enum { OS_SocketDomain_INET = 2 } OS_SocketDomain_t;
typedef enum { OS_SocketType_DATAGRAM = 2 } OS_SocketType_t;

#define OS_SUCCESS ((int32) 0)
#define OS_ERR_TIMEOUT ((int32)-10)

int32 OS_SocketOpen(osal_id_t *sock_id, OS_SocketDomain_t Domain, OS_SocketType_t Type);
int32 OS_SocketAddrInit(OS_SockAddr_t *Addr, OS_SocketDomain_t Domain);
int32 OS_SocketAddrSetPort(OS_SockAddr_t *Addr, uint16 PortNum);
int32 OS_SocketBind(osal_id_t sock_id, const OS_SockAddr_t *Addr);
int32 OS_SocketRecvFrom(osal_id_t sock_id, void *buffer, uint32 buflen,
OS_SockAddr_t *RemoteAddr, int32 timeout_ms);

#endif /* CFE_H */
Loading
Loading