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
47 changes: 47 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Docs

on:
push:
branches: [main]
pull_request:

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.10"
- run: sudo apt-get install -y doxygen
- run: |
pip install \
properdocs \
mkdocs-material \
mkdocstrings[python] \
mkdocs-gen-files \
mkdocs-literate-nav \
mkdocs-section-index \
mkdoxy
- run: properdocs build

deploy:
if: github.ref == 'refs/heads/main'
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.10"
- run: sudo apt-get install -y doxygen
- run: |
pip install \
properdocs \
mkdocs-material \
mkdocstrings[python] \
mkdocs-gen-files \
mkdocs-literate-nav \
mkdocs-section-index \
mkdoxy
- run: properdocs gh-deploy --force
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,5 @@ crash course/data/MNIST/raw/*

node_modules
*venv
.claude
site/
2 changes: 1 addition & 1 deletion controls/sae_2025_ws/scripts/ci/validate_ros_workspace.sh
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ rosdep install -r -i -y --rosdistro "$ROS_DISTRO" \

ci_log "Building shared hardware dependencies"
colcon build \
--packages-select payload_interfaces px4_msgs uav_interfaces actuator_msgs
--packages-select payload_interfaces px4_msgs uav_interfaces actuator_msgs udp_bridge

ci_log "Building hardware payload package"
ci_source_workspace "$WORKSPACE_ROOT"
Expand Down
2 changes: 1 addition & 1 deletion controls/sae_2025_ws/src/custom_gz_msgs/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# custom_gz_msgs

Custom Gazebo Harmonic protobuf message definitions for the SAE 2025 simulation environment.
Custom Gazebo Harmonic protobuf message definitions for the simulation environment.

For general information on creating Gazebo messages, see the [official guide](https://gazebosim.org/api/msgs/10/index.html).

Expand Down
2 changes: 1 addition & 1 deletion controls/sae_2025_ws/src/custom_gz_plugins/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# custom_gz_plugins

Custom Gazebo Harmonic system plugins for the SAE 2025 simulation environment.
Custom Gazebo Harmonic system plugins for simulation.

For general information on creating Gazebo system plugins, see the [official guide](https://gazebosim.org/api/sim/8/createsystemplugins.html).

Expand Down
2 changes: 1 addition & 1 deletion controls/sae_2025_ws/src/custom_sim_bridge/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# custom_sim_bridge

Pluginlib-based ROS 2 bridge plugins that translate between ROS 2 services and Gazebo Harmonic transport for the SAE 2025 simulation environment.
Pluginlib-based ROS 2 bridge plugins that translate between ROS 2 services and Gazebo Harmonic transport for simulation.

For general information on pluginlib, see the [official guide](https://docs.ros.org/en/rolling/Tutorials/Beginner-Client-Libraries/Pluginlib.html).

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ def load_mission_spec_compat(path: str | Path | dict[str, Any]) -> MissionSpecCo
unknown_targets = sorted(
next_mode
for next_mode in mode_spec.transitions.values()
if next_mode not in modes
if next_mode not in modes and next_mode != "terminate"
)
if unknown_targets:
raise ValueError(
Expand Down
17 changes: 15 additions & 2 deletions controls/sae_2025_ws/src/payload/include/payload/motor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,36 @@
#include <algorithm>
#include "payload/gpio.hpp"

/// Which side of the vehicle this motor drives.
enum class MotorType {
LEFT,
RIGHT
LEFT, ///< Left-side motor
RIGHT ///< Right-side motor
};

/// Abstract base class for all motor drivers.
class Motor {
public:
virtual ~Motor() = default;
/// Set motor speed in [-1, 1] (negative = reverse).
virtual void set_speed(float speed) = 0;
/// Drive forward at the given PWM duty cycle [0, 1].
virtual void forward(float duty) = 0;
/// Drive in reverse at the given PWM duty cycle [0, 1].
virtual void reverse(float duty) = 0;
/// Let the motor coast (no braking force).
virtual void coast() = 0;
/// Apply electronic braking immediately.
virtual void hard_brake() = 0;
};

/// Motor driver for the DRV8833 H-bridge IC.
class DRVMotor : public Motor {
public:
/// @param pi pigpio instance handle
/// @param in1 GPIO pin for IN1
/// @param in2 GPIO pin for IN2
/// @param frequency PWM frequency in Hz
/// @param motor_type Which side this motor drives
DRVMotor(int pi, int in1, int in2, int frequency, MotorType motor_type);
void set_speed(float speed) override;
void forward(float duty) override;
Expand Down
2 changes: 2 additions & 0 deletions controls/sae_2025_ws/src/sim/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# `sim` Package

Gazebo Harmonic simulation backend — world generation, multi-vehicle spawning, hoop-course scoring, and stage configuration.

## Extra Dependency
```bash
sudo apt install ros-humble-tf-transformations
Expand Down
2 changes: 1 addition & 1 deletion controls/sae_2025_ws/src/tools/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# tools

Developer tools for the SAE 2025 workspace.
Various developer tools

---

Expand Down
1 change: 1 addition & 0 deletions controls/sae_2025_ws/src/uav/test/test_auto_launch.py
Original file line number Diff line number Diff line change
Expand Up @@ -700,6 +700,7 @@ def test_payload_bootstrap_forwards_auto_launch(monkeypatch, auto_launch):
"vehicle_name": "payload_0",
"peer_heartbeat_hz": 12.0,
"peer_stale_timeout_s": 0.75,
"vision_debug": False,
}
bootstrap.get_parameter = lambda name: _FakeParameter(parameters[name])

Expand Down
5 changes: 5 additions & 0 deletions controls/sae_2025_ws/src/uav/test/test_launch_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,7 @@ def test_build_runtime_parameters_for_uav(stack_launch_module):
vehicle_name="uav_3",
auto_launch=True,
debug=1,
vision_debug=False,
servo_only=0,
vehicle_class_name="MULTICOPTER",
camera_mount_offsets=[0.1, 0.2, 0.3],
Expand All @@ -687,6 +688,7 @@ def test_build_runtime_parameters_for_payload(stack_launch_module):
vehicle_name="payload_0",
auto_launch=False,
debug=True,
vision_debug=False,
servo_only=True,
vehicle_class_name=None,
camera_mount_offsets=[],
Expand All @@ -696,6 +698,7 @@ def test_build_runtime_parameters_for_payload(stack_launch_module):
"mode_map": "/tmp/payload.yaml",
"auto_launch": False,
"vehicle_name": "payload_0",
"vision_debug": False,
}


Expand All @@ -707,6 +710,7 @@ def test_build_runtime_parameters_rejects_missing_vehicle_class(stack_launch_mod
vehicle_name="uav",
auto_launch=True,
debug=False,
vision_debug=False,
servo_only=False,
vehicle_class_name=None,
camera_mount_offsets=[0.0, 0.0, 0.0],
Expand All @@ -723,6 +727,7 @@ def test_build_runtime_parameters_rejects_bad_camera_offsets(stack_launch_module
vehicle_name="uav",
auto_launch=True,
debug=False,
vision_debug=False,
servo_only=False,
vehicle_class_name="MULTICOPTER",
camera_mount_offsets=[0.0, 0.0],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,17 +210,23 @@ def _publish_debug(

now = self._now()
if orange_found:
elapsed = (now - self._clear_since) if self._clear_since is not None else 0.0
elapsed = (
(now - self._clear_since) if self._clear_since is not None else 0.0
)
state_label = f"DLZ {elapsed:.1f}/{self.dlz_color_wait_seconds:.1f}s"
label_color = (0, 255, 0)
elif is_still:
state_label = f"OBSTRUCTED {self._obstruction_count}/{self.obstruction_frames}"
state_label = (
f"OBSTRUCTED {self._obstruction_count}/{self.obstruction_frames}"
)
label_color = (0, 140, 255)
else:
state_label = "IN-FLIGHT"
label_color = (128, 128, 128)

cv2.putText(vis, state_label, (8, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, label_color, 2)
cv2.putText(
vis, state_label, (8, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, label_color, 2
)
cv2.putText(
vis,
f"dlz={coverage:.2f} thresh={self.dlz_color_pixel_threshold:.2f}",
Expand Down Expand Up @@ -279,17 +285,23 @@ def on_update(self, time_delta: float) -> None:
self._obstruction_count = 0
if self._clear_since is None:
self._clear_since = now
self.log(f"DLZ in view — starting {self.dlz_color_wait_seconds}s timer")
self.log(
f"DLZ in view — starting {self.dlz_color_wait_seconds}s timer"
)
elapsed = now - self._clear_since
self.log(f"DLZ confirmed for {elapsed:.1f}/{self.dlz_color_wait_seconds:.1f}s")
self.log(
f"DLZ confirmed for {elapsed:.1f}/{self.dlz_color_wait_seconds:.1f}s"
)
self._publish_debug(bgr, True, coverage, orange_mask, None)
if elapsed >= self.dlz_color_wait_seconds:
self.log("landing confirmed — moving to reverse")
self.state = DriveOutState.REVERSING
else:
# Orange absent — reset DLZ timer, reset optical flow so readings are fresh
if self._clear_since is not None:
self.log("orange lost — resetting DLZ timer, resetting optical flow")
self.log(
"orange lost — resetting DLZ timer, resetting optical flow"
)
self._prev_gray = None
self._flow_deltas.clear()
self._clear_since = None
Expand Down
Binary file added docs/assets/pennair-preview-150x150.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions docs/concepts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Concepts


TODO
76 changes: 76 additions & 0 deletions docs/doc_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"""Shared utilities for mkdocs-gen-files scripts."""

from pathlib import Path
import xml.etree.ElementTree as ET

REPO_ROOT = Path(__file__).resolve().parent.parent
GITMODULES = REPO_ROOT / ".gitmodules"


def submodule_dirs() -> set[Path]:
"""Return absolute paths of all submodules declared in the repo's .gitmodules."""
if not GITMODULES.exists():
return set()
paths: set[Path] = set()
for line in GITMODULES.read_text().splitlines():
line = line.strip()
if line.startswith("path ="):
rel = line.split("=", 1)[1].strip()
paths.add(REPO_ROOT / rel)
return paths


def find_ros_packages(
src: Path, ament_type="all", exclude_submodules=True
) -> set[Path]:
"""Return the set of ROS package directories under *src*.

A ROS package is any directory that contains a ``package.xml`` file.
Only direct children of *src* are searched so that vendored/nested
package trees (e.g. ``ros_gz/ros_gz_sim/``) are excluded.

Args:
src: The source directory to search in.
ament_type: Filter by build type — ``"all"``, ``"python"``
(``ament_python``), or ``"cmake"`` (``ament_cmake``).
exclude_submodules: When ``True`` (default), directories that are
git submodules are excluded.
"""
if ament_type not in ("all", "python", "cmake"):
raise ValueError("ament_type must be 'all', 'python', or 'cmake'")

src = src.resolve()
pkg_dirs = {pkg_xml.parent for pkg_xml in src.glob("*/package.xml")}

if exclude_submodules:
pkg_dirs -= submodule_dirs()

if ament_type == "all":
return pkg_dirs

target_build_type = "ament_python" if ament_type == "python" else "ament_cmake"

return {
pkg_dir
for pkg_dir in pkg_dirs
if ET.parse(pkg_dir / "package.xml").getroot().findtext("export/build_type")
== target_build_type
}


if __name__ == "__main__":
print("submodules:")
for smd in submodule_dirs():
print(smd)

print("\nALL:")
for pkg in find_ros_packages(Path("controls/sae_2025_ws/src")):
print(pkg)

print("\nPYTHON:")
for pkg in find_ros_packages(Path("controls/sae_2025_ws/src"), ament_type="python"):
print(pkg)

print("\nCMAKE:")
for pkg in find_ros_packages(Path("controls/sae_2025_ws/src"), ament_type="cmake"):
print(pkg)
Loading
Loading