This repository hosts the core communication, sensing, and control architecture for the Purdue ROV X-19 underwater vehicle. The system uses ZeroMQ (ZMQ) for low-latency inter-process communication and Protocol Buffers (Protobuf) for type-safe, cross-language serialization (C++ and Python).
X19-Core/
βββ config/ # Configuration files (ports, address bindings)
βββ launch.sh # Bare-bones node launcher script (no tmux)
βββ launch.yaml # Declarative node configuration
βββ src/ # Communication wrappers & middleware
β βββ cpp/ # C++ wrapper library
β βββ python/ # Python wrapper library (python.messaging package)
β βββ messaging/
β βββ publisher.py
β βββ subscriber.py
βββ nodes/ # Independent executable nodes
β βββ cpp/ # C++ nodes (thrusters, pid controls, etc.)
β βββ python/ # Python nodes (video, pilot control, etc.)
βββ proto/ # Protobuf message schemas
βββ scripts/ # Utility scripts (setup, compilation, launch runner)
β βββ compile_protos.sh # Protobuf schema compiler
β βββ launch.py # Lightweight YAML process runner
β βββ setup.sh # System dependency provisioner
βββ testing/ # Validation and testing scripts
βββ run.sh # Test runner shortcut script
βββ MVPs.md # Development checklist & milestones
Prerequisites include the Protobuf Compiler (protoc), ZeroMQ development headers, and Python 3.
Run the automated setup script to provision your system:
./scripts/setup.sh(This installs protobuf-compiler and libzmq3-dev via apt-get, and builds the Python dependencies defined in requirements.txt into your virtual environment).
Compile your .proto files in the proto/ directory to generate C++ and Python bindings:
./scripts/compile_protos.shThis outputs compiled bindings directly to src/protocols/cpp and src/protocols/python.
To launch all vehicle nodes on the Raspberry Pi without tmux:
# 1. Launch with default surface address (192.168.1.7:5556)
./launch.sh
# 2. Launch with a specific Surface laptop IP address
./launch.sh -i 192.168.1.100
# 3. View options
./launch.sh --help- Dependency Verification:
launch.shverifies thatpython3andpyyamlare installed. If missing, it alerts you with instructions to install them viapip install -r requirements.txt. - Environment & IP Setup: Formats the Surface IP into
tcp://<IP>:5556and exportsSURFACE_ZMQ_ADDRESS. - Execution: Hands off
launch.yamltoscripts/launch.py, which starts each defined node in the background and tags console logs with node labels (e.g.[videos]). - Graceful Cleanup: Pressing
Ctrl+Ccleanly shuts down all nodes and terminates any spawned child processes (e.g.,ffmpegcamera streams).
To add new nodes (e.g. thrusters, telemetry), simply add their entry to launch.yaml:
nodes:
- name: "videos"
cmd: "python3 src/python/videos/get_ip.py"
# Future nodes:
- name: "thrusters"
cmd: "python3 src/python/thrusters/thruster_node.py"Use the root run.sh script to execute standalone test scripts:
# Run the hello_pub test node
./run.sh
# Recompile protobuf schemas AND run the hello_pub test node
./run.sh -pTo make writing nodes simple and robust, lightweight wrappers wrap ZMQ sockets into easy-to-use classes.
Creates a ZMQ PUB socket that serializes and publishes Protobuf messages on a specific topic.
from src.python.messaging import Publisher
from src.protocols.python import telemetry_pb2
# Initialize publisher (binds to address by default)
publisher = Publisher(address="tcp://127.0.0.1:5555", topic="telemetry")
# Create and publish a Protobuf message
msg = telemetry_pb2.SensorData(depth=1.24)
publisher.publish(msg)
# Clean up
publisher.close()__init__(address: str, topic: str, bind: bool = True)address: ZMQ address endpoint (e.g.,tcp://*:5555).topic: Topic name string.bind: Binds socket to the port ifTrue; connects ifFalse.
publish(proto_message)- Serializes the Protobuf class instance and publishes it.
close()- Closes the underlying ZMQ socket.
Creates a ZMQ SUB socket that connects to a publisher, subscribes to a topic, and parses incoming Protobuf payloads.
from src.python.messaging import Subscriber
from src.protocols.python import telemetry_pb2
def telemetry_callback(data):
print(f"Received depth: {data.depth}")
# Initialize subscriber (connects to address by default)
subscriber = Subscriber(
address="tcp://127.0.0.1:5555",
topic="telemetry",
message_type=telemetry_pb2.SensorData,
callback=telemetry_callback
)
# Process a single incoming message (timeout in milliseconds)
subscriber.spin_once(timeout_ms=100)
# Clean up
subscriber.close()__init__(address: str, topic: str, message_type, callback, bind: bool = False)address: Target publisher endpoint (e.g.,tcp://127.0.0.1:5555).topic: Subscribed topic string.message_type: Protobuf class type used to deserialize payloads.callback: Callback function signaturedef callback(message).bind: Binds socket ifTrue; connects ifFalse.
spin_once(timeout_ms: int)- Polls the socket. If data is available, it deserializes the payload and invokes the callback. Returns
Trueif processed, otherwiseFalse.
- Polls the socket. If data is available, it deserializes the payload and invokes the callback. Returns
close()- Closes the underlying ZMQ socket.
- set python path to dir root
export PYTHONPATH=<project_root>- run publisher using virtual env first
./.venv/bin/python ./testing/hello_pub.py - run subscriber using virtual env next
./.venv/bin/python ./testing/hello_sub.py X19-Core contains the camera acquisition and low-latency H.264 RTSP streaming pipeline for the ROV cameras.
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β X19-Core Video Subsystem β
β β
β 1. get_ip.py (ZMQ Subscriber on port 5556) β
β - Connects to tcp://<SURFACE_IP>:5556 on topic 'surface_ip' β
β - Deserializes Protobuf payload: telemetry_pb2.test(msg=ip) β
β β
β 2. V4L2 Device Auto-Discovery β
β - Runs 'v4l2-ctl --list-devices' β
β - Detects attached exploreHD / Arducam / Intel cameras β
β β
β 3. FFmpeg RTSP Streamers (videos_launch.py) β
β - Spawns background FFmpeg process for each camera β
β - Encodes H.264 / MJPEG video with zerolatency preset β
β - Pushes RTSP stream to rtsp://<SURFACE_IP>:8554/camera<N> β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
get_ip.py: ZMQ subscriber node listening for Surface IP broadcast. Once IP is received, triggers camera discovery and starts streaming processes.videos_launch.py: Low-latency FFmpeg RTSP camera streamer node (--ip,--device,--camera-number).cv_camera_connect.py: DepthAI camera RTSP streamer node for OAK/DepthAI hardware.
When running on the Raspberry Pi connected to the Surface Topside Laptop via Ethernet/tether network:
| Port | Protocol | Target Node | Purpose |
|---|---|---|---|
| 5555 | ZMQ (TCP) | Surface Telemetry | Receives telemetry packets on topic telemetry |
| 5556 | ZMQ (TCP) | Surface IP Publisher | Receives Surface IP broadcast on topic surface_ip |
| 8554 | RTSP (TCP) | Go2RTC Server | Pushes RTSP camera streams to Surface media server |
- On Surface Topside Laptop: Start
go2rtc_node.py(or run./launch --fieldinX19-Surface). Note the Surface laptop IP address (e.g.192.168.1.100). - On Raspberry Pi (
X19-Core): Launch all vehicle nodes pointing to the Surface laptop's IP address:(This automatically forwards the Surface address to./launch.sh -i 192.168.1.100
get_ip.py, discovers local cameras, and streams RTSP video back to the Surface computer).