Repository files navigation

Nereo PoliTOcean

Code for the Nereo ROV by PoliTOcean.

The project is split into two ROS 2 (Humble) workspaces:

WorkspaceSidePurpose
gui_wsControl station (PC)GUI, joystick, web controller, safety arbitration
rpi_wsRaspberry Pi (on the ROV)IMU / barometer / sonar acquisition

The PID/state-space controller lives in a separate repository, ros2_controller_tuning_aid, and is wired in by the workstation.launch.py of gui_pkg.


Table of contents

  1. Installation
  2. Build
  3. Run
  4. Workspace structure
  5. ROS 2 node and topic map
  6. GUI features
  7. Web controller (phone / tablet)
  8. Local testing (no ROV hardware)
  9. Troubleshooting

Installation

0. Clone the repos

nereo_interfaces is a git submodule of this repo. Clone with --recurse-submodules:

git clone --recurse-submodules https://github.com/PoliTOcean/nereo_ros2_code.git

If already cloned without it:

git submodule update --init

Clone the controller repo next to this one (paths in workstation.launch.py assume they are siblings under ~/Documents/PoliTOcean/RD/):

git clone https://github.com/PoliTOcean/ros2_controller_tuning_aid.git

1. System dependencies

sudo apt update
sudo apt install \
ros-humble-rosbridge-suite \
ros-humble-tf-transformations \
gstreamer1.0-plugins-good gstreamer1.0-plugins-base gstreamer1.0-tools \
python3-qrcode python3-pil
PackageUsed byWhy it's needed
ros-humble-rosbridge-suiteweb_pkgWebSocket bridge for the web controller (port 9090)
ros-humble-tf-transformationsgui_pkgQuaternion → RPY conversion in the GUI
gstreamer1.0-plugins-*gui_pkgLive H.264/UDP video from the ROV cameras
python3-qrcode, python3-pilgui_pkgGenerates the QR code shown on Ctrl+Q for the web controller URL

2. PyQt6 with QML bindings (pip)

The apt python3-pyqt6 package ships without QtQml/QtQuick. Install via pip:

pip install PyQt6

3. Register the custom rosdep sources

A few dependencies (PyQt6, bluerobotics-ping) are not in the default rosdep index. The local override sits at rosdep.yaml in this repo:

echo"yaml file://$(pwd)/rosdep.yaml"| sudo tee /etc/ros/rosdep/sources.list.d/nereo.list
rosdep update

4. Install per-package dependencies

# Control station (PC)cd gui_ws
rosdep install --from-paths src --ignore-src -r -y
# Raspberry Picd ../rpi_ws
rosdep install --from-paths src --ignore-src -r -y

Build

# Controller (must be built first — gui_ws launch file pulls it in via AMENT_PREFIX_PATH)cd ../ros2_controller_tuning_aid && colcon build &&source install/setup.zsh
# Control stationcd ../nereo_ros2_code/gui_ws && colcon build &&source install/setup.zsh
# Raspberry Picd ../rpi_ws && colcon build &&source install/setup.zsh

Run

A single launch file starts everything on the workstation: joystick driver, command translator, GUI, safety arbiter, rosbridge WebSocket (port 9090), web controller server (port 8080), and the controller node.

ros2 launch gui_pkg workstation.launch.py

The launch file auto-prepends the ros2_controller_tuning_aid install dir to AMENT_PREFIX_PATH, so you do not need to source that overlay manually.

Launch arguments

ArgumentDefaultDescription
device/dev/input/js0Joystick device path
deadzone0.05Joystick deadzone
max_steps10Quantization steps for axes
btn_arm8Arm/disarm button index
btn_mode6Direct ↔ Controller mode toggle button index
control_mode0Initial controller mode (0=passthrough, 1=PID, 2=PID-AW, 3=CS)

Physical controller mapping

InputActionXbox One S (default)DS5
Left stick Y/XSurge / Sway
Right stick Y/XHeave / Yaw
D-pad up/downPitch trim
D-pad left/rightRoll trim
Arm/DisarmToggle armXbox (btn 8)PS (btn 10)
Mode toggleDirect ↔ ControllerView (btn 6)Share (btn 8)

Examples:

# DS5 button mapping
ros2 launch gui_pkg workstation.launch.py btn_arm:=10 btn_mode:=8
# Different joystick device
ros2 launch gui_pkg workstation.launch.py device:=/dev/input/js1

Direct mode (red lock icon in GUI): commands go to /nereo_cmd_vel_joysafety_node → ROV. Controller mode (blue lock icon in GUI): commands go to /nereo_cmd_vel_no_fb → controller node → safety_node → ROV.


Workspace structure

nereo_ros2_code/
├── gui_ws/ # Workstation workspace
│ └── src/
│ ├── gui_pkg/ # QML/PyQt6 dashboard + joystick + rov simulator
│ │ ├── gui_pkg/
│ │ │ ├── gui_node.py # Main node + ROSQmlBridge / SonarBridge / ControllerBridge
│ │ │ ├── rov_sim_node.py # Full ROV simulator (IMU, barometer, cameras, joy)
│ │ │ └── qml/ # QML UI
│ │ │ ├── main.qml
│ │ │ └── components/
│ │ │ ├── ControllerTunerWindow.qml # PID/CS tuning UI (deg/m setpoints)
│ │ │ ├── ControlPanelWindow.qml
│ │ │ ├── SonarWindow.qml
│ │ │ ├── Orientation2D.qml
│ │ │ └── VideoBox.qml
│ │ └── launch/
│ │ └── workstation.launch.py
│ ├── joystick_pkg/ # joy → CommandVelocity
│ ├── web_pkg/
│ │ ├── web_server_node.py # HTTP server on :8080
│ │ ├── safety_node.py # Arbitrates physical vs. web commands
│ │ └── static/ # Web controller UI assets
│ └── nereo_interfaces/ # CommandVelocity, ThrusterStatuses (submodule)
└── rpi_ws/ # Raspberry Pi workspace
└── src/
├── nereo_sensors_pkg/ # C++ — IMU (WT61P) + barometer (MS5837)
│ ├── src/imuPub.cpp # publishes /imu_data, service imu_reset_reference
│ ├── src/barPub.cpp # publishes /barometer_*, service barometer_reset_reference
│ └── include/nereo_sensors_pkg/
└── sonar_pkg/ # Python — Blue Robotics Ping1D

ROS 2 node and topic map

Nodes

NodePackageSideRole
imu_publishernereo_sensors_pkgRPiWT61P over I2C → /imu_data; service imu_reset_reference
bar_publishernereo_sensors_pkgRPiMS5837 over I2C → /barometer_*; service barometer_reset_reference
sonar_nodesonar_pkgRPiPing1D over serial → /sonar/*
gui_nodegui_pkgPCQML dashboard, telemetry fusion, QR code, tuner
joy_to_cmd_veljoystick_pkgPCJoystick → CommandVelocity
safety_nodeweb_pkgPCArbitrates controller + web commands → /nereo_cmd_vel
web_server_nodeweb_pkgPCServes the web controller on :8080
rosbridge_websocketrosbridge_serverPCWebSocket bridge for the web client (:9090)
nereo_controller_nodenereo_controller_nodePCPID / state-space controller (separate repo)
rov_sim_nodegui_pkgPCLocal ROV simulator (no hardware needed)
sonar_sim_nodesonar_pkgPCSynthetic sonar data

Topics

PublisherTopicTypeConsumers
imu_publisherimu_datasensor_msgs/Imugui_node, nereo_controller_node
imu_publisherimu_diagnosticdiagnostic_msgs/DiagnosticArray
bar_publisherbarometer_pressuresensor_msgs/FluidPressuregui_node, nereo_controller_node
bar_publisherbarometer_depth_saltstd_msgs/Float32gui_node (depth widget)
bar_publisherbarometer_depth_freshstd_msgs/Float32
bar_publisherbarometer_temperaturesensor_msgs/Temperaturegui_node
bar_publisherbarometer_diagnosticdiagnostic_msgs/DiagnosticArray
sonar_node / sonar_sim_nodesonar/distancestd_msgs/Float32gui_node
sonar_node / sonar_sim_nodesonar/confidencestd_msgs/Int32gui_node
sonar_node / sonar_sim_nodesonar/profilestd_msgs/Float32MultiArraygui_node
joy_to_cmd_vel/nereo_cmd_vel_joynereo_interfaces/CommandVelocitysafety_node
joy_to_cmd_vel/nereo_cmd_vel_no_fbnereo_interfaces/CommandVelocitynereo_controller_node
joy_to_cmd_vel/joy_control_activestd_msgs/Boolgui_node, web
joy_to_cmd_vel / web/set_arm_modestd_msgs/BoolROV firmware
nereo_controller_node/nereo_cmd_vel (controller mode)nereo_interfaces/CommandVelocitysafety_node
nereo_controller_node/controller/setpointsstd_msgs/Float64MultiArraytuner GUI
nereo_controller_node/controller/errorsstd_msgs/Float64MultiArraytuner GUI
nereo_controller_node/controller/pid_termsstd_msgs/Float64MultiArraytuner GUI
web client/web_cmd_velnereo_interfaces/CommandVelocitysafety_node
safety_node/nereo_cmd_vel (direct mode)nereo_interfaces/CommandVelocityROV firmware
ROV firmware/rov_armedstd_msgs/Boolgui_node, joy_to_cmd_vel, web
ROV firmware/thruster_statusnereo_interfaces/ThrusterStatuses

Services

ServiceTypeProviderUsed byEffect
barometer_reset_referencestd_srvs/Triggerbar_publisherGUI "ZERO" buttonRe-zeroes the surface pressure → depth = 0 at current spot
imu_reset_referencestd_srvs/Triggerimu_publisherGUI "ZERO RPY" buttonStores current orientation as offset → roll/pitch/yaw = 0 at current attitude

Safety arbitration (safety_node)

ConditionOutput
Controller active (last msg < 0.5 s)Controller command forwarded, web ignored
Only web activeWeb command forwarded
Web silent 0–1 sLast web command held
Web silent 1–1.5 sCommand ramped to zero
Web silent > 1.5 sZero command sent
No source activeZero command sent

Architecture diagram

graph LR
subgraph RPI[rpi_ws — Raspberry Pi]
IMU[imu_publisher\nimu_reset_reference svc]
BAR[bar_publisher\nbarometer_reset_reference svc]
SONAR[sonar_node]
CAM[GStreamer cameras\nH264 UDP]
end
subgraph CTRL[gui_ws — Control Station]
GUI[gui_node\nROSQmlBridge / SonarBridge / ControllerBridge]
JOY[joy_to_cmd_vel]
SAFETY[safety_node]
WEB[web_server_node\nport 8080]
RB[rosbridge_websocket\nport 9090]
end
subgraph TUNE[ros2_controller_tuning_aid]
NCN[nereo_controller_node]
end
subgraph SIM[Simulators — local testing]
ROVSIM[rov_sim_node]
SONARSIM[sonar_sim_node]
end
PHONE[Phone / tablet\nbrowser]
IMU -.->|imu_data| GUI
IMU -.->|imu_data| NCN
BAR -.->|barometer_pressure| GUI
BAR -.->|barometer_pressure| NCN
BAR -.->|barometer_depth_salt| GUI
SONAR -.->|sonar/*| GUI
CAM -.->|RTP/UDP 5001-5003| GUI
JOY -->|nereo_cmd_vel_joy| SAFETY
JOY -->|nereo_cmd_vel_no_fb| NCN
NCN -->|nereo_cmd_vel| SAFETY
PHONE -->|web_cmd_vel via :9090| RB
RB --> SAFETY
WEB -->|UI :8080| PHONE
SAFETY -->|nereo_cmd_vel| ROV[(ROV firmware\nmicroROS)]
JOY -->|set_arm_mode| ROV
ROV -->|rov_armed| GUI
ROV -->|rov_armed| JOY
GUI -.->|set_parameters| NCN
NCN -.->|/controller/*| GUI
Loading

GUI features

Main dashboard

  • 3× live H.264/UDP camera streams (main + 2 secondary)
  • IMU orientation widget (yaw / pitch / roll)
  • Depth, temperature, ROV arm/connection status
  • ZERO button → re-zeroes depth at the surface (calls barometer_reset_reference)
  • ZERO RPY button → re-zeroes attitude when the ROV is sitting level (calls imu_reset_reference)
  • Ctrl+Q → opens a popup with a QR code + URL (http://<ip>:8080) for the web controller. The IP is recomputed every time the shortcut is pressed, so it works even if you connect to Wi-Fi after launching the GUI.

Controller Tuner window (TUNER button)

  • Selects control mode (0 passthrough / 1 PID / 2 PID anti-windup / 3 CS)
  • Edits kp/ki/kd for depth/roll/pitch/yaw
  • Manual setpoint toggles per axis
  • Setpoint inputs in display units: depth in metres, roll/pitch/yaw in degrees. Conversion to controller units (Pa, rad) is done inside QML (density ρ = 1025 kg/m³, salt water — matches barometer_depth_salt). The controller-side parameters stay in their native units.
  • Full CS controller section (kx, ki, heave/angle limits)
  • Live telemetry: /controller/setpoints, /controller/errors, /controller/pid_terms

Sonar Viewer (SONAR button)

Waterfall + A-scan rendered into a QQuickImageProvider, confidence threshold filter.

Control Panel (CONTROL PANEL button)

Manual arming / mode toggles independent from the joystick.


Web controller (phone / tablet)

Open from any device on the same network:

  • Controller: http://<workstation-ip>:8080
  • ROV simulator: http://<workstation-ip>:8080/sim.html

The fastest way to get the URL on a phone: focus the GUI and press Ctrl+Q — a QR code with the URL appears.

The web client publishes on /web_cmd_vel. safety_node gives priority to the physical controller when both are active.

The web_server_node is robust to GUI shutdown: it installs a SIGTERM handler, closes the HTTP socket, and joins its serving thread on destroy_node(), so port 8080 is released cleanly and the launch file can be restarted without pkill.


Local testing (no ROV hardware)

# Terminal 1 — full ROV simulator (IMU, barometer, joystick, arm, 3 GStreamer test streams)
ros2 run gui_pkg rov_sim_node
# (without GStreamer: --ros-args -p simulate_cameras:=false)# Terminal 2 — sonar simulator
ros2 run sonar_pkg sonar_sim_node
# Terminal 3 — workstation stack
ros2 launch gui_pkg workstation.launch.py

Open http://localhost:8080 for the web controller and http://localhost:8080/sim.html for the top-down ROV simulator.

Manual test helpers

  • gui_ws/src/gui_pkg/test/cam_test.sh — three independent GStreamer test streams
  • ros2 run joystick_pkg rov_cmd_monitor — terminal dashboard of the live 6-DOF command vector

Unit tests

Inside unit_tests/ each subfolder is a CMake project for stdout-level debugging:

cd unit_tests/<name>
cmake .&& make
./<name>

Troubleshooting

SymptomCauseFix
Address already in use :8080 at relaunchOld web_server_node still runningShould not happen anymore (the node releases the socket on SIGTERM); if it does, pkill -f web_server_node
GUI starts but no nereo_controller_noderos2_controller_tuning_aid not built or not at sibling pathCheck ~/Documents/PoliTOcean/RD/ros2_controller_tuning_aid/install/ exists
imu_reset_reference service not availablerpi_ws not rebuilt after the new service was addedOn the Pi: colcon build --packages-select nereo_sensors_pkg
Ctrl+Q shows "Nessuna rete"No default-route interface upConnect to Wi-Fi / Ethernet, press Ctrl+Q again (re-evaluated each time)
Ctrl+Q shows "Pacchetto python3-qrcode mancante"Missing system packagesudo apt install python3-qrcode python3-pil
Tuner setpoint value looks wrong after RELOADController still has old unit valueThe GUI now converts deg↔rad and m↔Pa; if you set parameters from CLI in raw units, RELOAD will display them converted

About

No description, website, or topics provided.

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

Nereo PoliTOcean

Code for the Nereo ROV by PoliTOcean.

The project is split into two ROS 2 (Humble) workspaces:

WorkspaceSidePurpose
gui_wsControl station (PC)GUI, joystick, web controller, safety arbitration
rpi_wsRaspberry Pi (on the ROV)IMU / barometer / sonar acquisition

The PID/state-space controller lives in a separate repository, ros2_controller_tuning_aid, and is wired in by the workstation.launch.py of gui_pkg.


Table of contents

  1. Installation
  2. Build
  3. Run
  4. Workspace structure
  5. ROS 2 node and topic map
  6. GUI features
  7. Web controller (phone / tablet)
  8. Local testing (no ROV hardware)
  9. Troubleshooting

Installation

0. Clone the repos

nereo_interfaces is a git submodule of this repo. Clone with --recurse-submodules:

git clone --recurse-submodules https://github.com/PoliTOcean/nereo_ros2_code.git

If already cloned without it:

git submodule update --init

Clone the controller repo next to this one (paths in workstation.launch.py assume they are siblings under ~/Documents/PoliTOcean/RD/):

git clone https://github.com/PoliTOcean/ros2_controller_tuning_aid.git

1. System dependencies

sudo apt update
sudo apt install \
ros-humble-rosbridge-suite \
ros-humble-tf-transformations \
gstreamer1.0-plugins-good gstreamer1.0-plugins-base gstreamer1.0-tools \
python3-qrcode python3-pil
PackageUsed byWhy it's needed
ros-humble-rosbridge-suiteweb_pkgWebSocket bridge for the web controller (port 9090)
ros-humble-tf-transformationsgui_pkgQuaternion → RPY conversion in the GUI
gstreamer1.0-plugins-*gui_pkgLive H.264/UDP video from the ROV cameras
python3-qrcode, python3-pilgui_pkgGenerates the QR code shown on Ctrl+Q for the web controller URL

2. PyQt6 with QML bindings (pip)

The apt python3-pyqt6 package ships without QtQml/QtQuick. Install via pip:

pip install PyQt6

3. Register the custom rosdep sources

A few dependencies (PyQt6, bluerobotics-ping) are not in the default rosdep index. The local override sits at rosdep.yaml in this repo:

echo"yaml file://$(pwd)/rosdep.yaml"| sudo tee /etc/ros/rosdep/sources.list.d/nereo.list
rosdep update

4. Install per-package dependencies

# Control station (PC)cd gui_ws
rosdep install --from-paths src --ignore-src -r -y
# Raspberry Picd ../rpi_ws
rosdep install --from-paths src --ignore-src -r -y

Build

# Controller (must be built first — gui_ws launch file pulls it in via AMENT_PREFIX_PATH)cd ../ros2_controller_tuning_aid && colcon build &&source install/setup.zsh
# Control stationcd ../nereo_ros2_code/gui_ws && colcon build &&source install/setup.zsh
# Raspberry Picd ../rpi_ws && colcon build &&source install/setup.zsh

Run

A single launch file starts everything on the workstation: joystick driver, command translator, GUI, safety arbiter, rosbridge WebSocket (port 9090), web controller server (port 8080), and the controller node.

ros2 launch gui_pkg workstation.launch.py

The launch file auto-prepends the ros2_controller_tuning_aid install dir to AMENT_PREFIX_PATH, so you do not need to source that overlay manually.

Launch arguments

ArgumentDefaultDescription
device/dev/input/js0Joystick device path
deadzone0.05Joystick deadzone
max_steps10Quantization steps for axes
btn_arm8Arm/disarm button index
btn_mode6Direct ↔ Controller mode toggle button index
control_mode0Initial controller mode (0=passthrough, 1=PID, 2=PID-AW, 3=CS)

Physical controller mapping

InputActionXbox One S (default)DS5
Left stick Y/XSurge / Sway
Right stick Y/XHeave / Yaw
D-pad up/downPitch trim
D-pad left/rightRoll trim
Arm/DisarmToggle armXbox (btn 8)PS (btn 10)
Mode toggleDirect ↔ ControllerView (btn 6)Share (btn 8)

Examples:

# DS5 button mapping
ros2 launch gui_pkg workstation.launch.py btn_arm:=10 btn_mode:=8
# Different joystick device
ros2 launch gui_pkg workstation.launch.py device:=/dev/input/js1

Direct mode (red lock icon in GUI): commands go to /nereo_cmd_vel_joysafety_node → ROV. Controller mode (blue lock icon in GUI): commands go to /nereo_cmd_vel_no_fb → controller node → safety_node → ROV.


Workspace structure

nereo_ros2_code/
├── gui_ws/ # Workstation workspace
│ └── src/
│ ├── gui_pkg/ # QML/PyQt6 dashboard + joystick + rov simulator
│ │ ├── gui_pkg/
│ │ │ ├── gui_node.py # Main node + ROSQmlBridge / SonarBridge / ControllerBridge
│ │ │ ├── rov_sim_node.py # Full ROV simulator (IMU, barometer, cameras, joy)
│ │ │ └── qml/ # QML UI
│ │ │ ├── main.qml
│ │ │ └── components/
│ │ │ ├── ControllerTunerWindow.qml # PID/CS tuning UI (deg/m setpoints)
│ │ │ ├── ControlPanelWindow.qml
│ │ │ ├── SonarWindow.qml
│ │ │ ├── Orientation2D.qml
│ │ │ └── VideoBox.qml
│ │ └── launch/
│ │ └── workstation.launch.py
│ ├── joystick_pkg/ # joy → CommandVelocity
│ ├── web_pkg/
│ │ ├── web_server_node.py # HTTP server on :8080
│ │ ├── safety_node.py # Arbitrates physical vs. web commands
│ │ └── static/ # Web controller UI assets
│ └── nereo_interfaces/ # CommandVelocity, ThrusterStatuses (submodule)
└── rpi_ws/ # Raspberry Pi workspace
└── src/
├── nereo_sensors_pkg/ # C++ — IMU (WT61P) + barometer (MS5837)
│ ├── src/imuPub.cpp # publishes /imu_data, service imu_reset_reference
│ ├── src/barPub.cpp # publishes /barometer_*, service barometer_reset_reference
│ └── include/nereo_sensors_pkg/
└── sonar_pkg/ # Python — Blue Robotics Ping1D

ROS 2 node and topic map

Nodes

NodePackageSideRole
imu_publishernereo_sensors_pkgRPiWT61P over I2C → /imu_data; service imu_reset_reference
bar_publishernereo_sensors_pkgRPiMS5837 over I2C → /barometer_*; service barometer_reset_reference
sonar_nodesonar_pkgRPiPing1D over serial → /sonar/*
gui_nodegui_pkgPCQML dashboard, telemetry fusion, QR code, tuner
joy_to_cmd_veljoystick_pkgPCJoystick → CommandVelocity
safety_nodeweb_pkgPCArbitrates controller + web commands → /nereo_cmd_vel
web_server_nodeweb_pkgPCServes the web controller on :8080
rosbridge_websocketrosbridge_serverPCWebSocket bridge for the web client (:9090)
nereo_controller_nodenereo_controller_nodePCPID / state-space controller (separate repo)
rov_sim_nodegui_pkgPCLocal ROV simulator (no hardware needed)
sonar_sim_nodesonar_pkgPCSynthetic sonar data

Topics

PublisherTopicTypeConsumers
imu_publisherimu_datasensor_msgs/Imugui_node, nereo_controller_node
imu_publisherimu_diagnosticdiagnostic_msgs/DiagnosticArray
bar_publisherbarometer_pressuresensor_msgs/FluidPressuregui_node, nereo_controller_node
bar_publisherbarometer_depth_saltstd_msgs/Float32gui_node (depth widget)
bar_publisherbarometer_depth_freshstd_msgs/Float32
bar_publisherbarometer_temperaturesensor_msgs/Temperaturegui_node
bar_publisherbarometer_diagnosticdiagnostic_msgs/DiagnosticArray
sonar_node / sonar_sim_nodesonar/distancestd_msgs/Float32gui_node
sonar_node / sonar_sim_nodesonar/confidencestd_msgs/Int32gui_node
sonar_node / sonar_sim_nodesonar/profilestd_msgs/Float32MultiArraygui_node
joy_to_cmd_vel/nereo_cmd_vel_joynereo_interfaces/CommandVelocitysafety_node
joy_to_cmd_vel/nereo_cmd_vel_no_fbnereo_interfaces/CommandVelocitynereo_controller_node
joy_to_cmd_vel/joy_control_activestd_msgs/Boolgui_node, web
joy_to_cmd_vel / web/set_arm_modestd_msgs/BoolROV firmware
nereo_controller_node/nereo_cmd_vel (controller mode)nereo_interfaces/CommandVelocitysafety_node
nereo_controller_node/controller/setpointsstd_msgs/Float64MultiArraytuner GUI
nereo_controller_node/controller/errorsstd_msgs/Float64MultiArraytuner GUI
nereo_controller_node/controller/pid_termsstd_msgs/Float64MultiArraytuner GUI
web client/web_cmd_velnereo_interfaces/CommandVelocitysafety_node
safety_node/nereo_cmd_vel (direct mode)nereo_interfaces/CommandVelocityROV firmware
ROV firmware/rov_armedstd_msgs/Boolgui_node, joy_to_cmd_vel, web
ROV firmware/thruster_statusnereo_interfaces/ThrusterStatuses

Services

ServiceTypeProviderUsed byEffect
barometer_reset_referencestd_srvs/Triggerbar_publisherGUI "ZERO" buttonRe-zeroes the surface pressure → depth = 0 at current spot
imu_reset_referencestd_srvs/Triggerimu_publisherGUI "ZERO RPY" buttonStores current orientation as offset → roll/pitch/yaw = 0 at current attitude

Safety arbitration (safety_node)

ConditionOutput
Controller active (last msg < 0.5 s)Controller command forwarded, web ignored
Only web activeWeb command forwarded
Web silent 0–1 sLast web command held
Web silent 1–1.5 sCommand ramped to zero
Web silent > 1.5 sZero command sent
No source activeZero command sent

Architecture diagram

graph LR
subgraph RPI[rpi_ws — Raspberry Pi]
IMU[imu_publisher\nimu_reset_reference svc]
BAR[bar_publisher\nbarometer_reset_reference svc]
SONAR[sonar_node]
CAM[GStreamer cameras\nH264 UDP]
end
subgraph CTRL[gui_ws — Control Station]
GUI[gui_node\nROSQmlBridge / SonarBridge / ControllerBridge]
JOY[joy_to_cmd_vel]
SAFETY[safety_node]
WEB[web_server_node\nport 8080]
RB[rosbridge_websocket\nport 9090]
end
subgraph TUNE[ros2_controller_tuning_aid]
NCN[nereo_controller_node]
end
subgraph SIM[Simulators — local testing]
ROVSIM[rov_sim_node]
SONARSIM[sonar_sim_node]
end
PHONE[Phone / tablet\nbrowser]
IMU -.->|imu_data| GUI
IMU -.->|imu_data| NCN
BAR -.->|barometer_pressure| GUI
BAR -.->|barometer_pressure| NCN
BAR -.->|barometer_depth_salt| GUI
SONAR -.->|sonar/*| GUI
CAM -.->|RTP/UDP 5001-5003| GUI
JOY -->|nereo_cmd_vel_joy| SAFETY
JOY -->|nereo_cmd_vel_no_fb| NCN
NCN -->|nereo_cmd_vel| SAFETY
PHONE -->|web_cmd_vel via :9090| RB
RB --> SAFETY
WEB -->|UI :8080| PHONE
SAFETY -->|nereo_cmd_vel| ROV[(ROV firmware\nmicroROS)]
JOY -->|set_arm_mode| ROV
ROV -->|rov_armed| GUI
ROV -->|rov_armed| JOY
GUI -.->|set_parameters| NCN
NCN -.->|/controller/*| GUI
Loading

GUI features

Main dashboard

  • 3× live H.264/UDP camera streams (main + 2 secondary)
  • IMU orientation widget (yaw / pitch / roll)
  • Depth, temperature, ROV arm/connection status
  • ZERO button → re-zeroes depth at the surface (calls barometer_reset_reference)
  • ZERO RPY button → re-zeroes attitude when the ROV is sitting level (calls imu_reset_reference)
  • Ctrl+Q → opens a popup with a QR code + URL (http://<ip>:8080) for the web controller. The IP is recomputed every time the shortcut is pressed, so it works even if you connect to Wi-Fi after launching the GUI.

Controller Tuner window (TUNER button)

  • Selects control mode (0 passthrough / 1 PID / 2 PID anti-windup / 3 CS)
  • Edits kp/ki/kd for depth/roll/pitch/yaw
  • Manual setpoint toggles per axis
  • Setpoint inputs in display units: depth in metres, roll/pitch/yaw in degrees. Conversion to controller units (Pa, rad) is done inside QML (density ρ = 1025 kg/m³, salt water — matches barometer_depth_salt). The controller-side parameters stay in their native units.
  • Full CS controller section (kx, ki, heave/angle limits)
  • Live telemetry: /controller/setpoints, /controller/errors, /controller/pid_terms

Sonar Viewer (SONAR button)

Waterfall + A-scan rendered into a QQuickImageProvider, confidence threshold filter.

Control Panel (CONTROL PANEL button)

Manual arming / mode toggles independent from the joystick.


Web controller (phone / tablet)

Open from any device on the same network:

  • Controller: http://<workstation-ip>:8080
  • ROV simulator: http://<workstation-ip>:8080/sim.html

The fastest way to get the URL on a phone: focus the GUI and press Ctrl+Q — a QR code with the URL appears.

The web client publishes on /web_cmd_vel. safety_node gives priority to the physical controller when both are active.

The web_server_node is robust to GUI shutdown: it installs a SIGTERM handler, closes the HTTP socket, and joins its serving thread on destroy_node(), so port 8080 is released cleanly and the launch file can be restarted without pkill.


Local testing (no ROV hardware)

# Terminal 1 — full ROV simulator (IMU, barometer, joystick, arm, 3 GStreamer test streams)
ros2 run gui_pkg rov_sim_node
# (without GStreamer: --ros-args -p simulate_cameras:=false)# Terminal 2 — sonar simulator
ros2 run sonar_pkg sonar_sim_node
# Terminal 3 — workstation stack
ros2 launch gui_pkg workstation.launch.py

Open http://localhost:8080 for the web controller and http://localhost:8080/sim.html for the top-down ROV simulator.

Manual test helpers

  • gui_ws/src/gui_pkg/test/cam_test.sh — three independent GStreamer test streams
  • ros2 run joystick_pkg rov_cmd_monitor — terminal dashboard of the live 6-DOF command vector

Unit tests

Inside unit_tests/ each subfolder is a CMake project for stdout-level debugging:

cd unit_tests/<name>
cmake .&& make
./<name>

Troubleshooting

SymptomCauseFix
Address already in use :8080 at relaunchOld web_server_node still runningShould not happen anymore (the node releases the socket on SIGTERM); if it does, pkill -f web_server_node
GUI starts but no nereo_controller_noderos2_controller_tuning_aid not built or not at sibling pathCheck ~/Documents/PoliTOcean/RD/ros2_controller_tuning_aid/install/ exists
imu_reset_reference service not availablerpi_ws not rebuilt after the new service was addedOn the Pi: colcon build --packages-select nereo_sensors_pkg
Ctrl+Q shows "Nessuna rete"No default-route interface upConnect to Wi-Fi / Ethernet, press Ctrl+Q again (re-evaluated each time)
Ctrl+Q shows "Pacchetto python3-qrcode mancante"Missing system packagesudo apt install python3-qrcode python3-pil
Tuner setpoint value looks wrong after RELOADController still has old unit valueThe GUI now converts deg↔rad and m↔Pa; if you set parameters from CLI in raw units, RELOAD will display them converted

About

No description, website, or topics provided.

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Nereo PoliTOcean

Code for the Nereo ROV by PoliTOcean.

The project is split into two ROS 2 (Humble) workspaces:

WorkspaceSidePurpose
gui_wsControl station (PC)GUI, joystick, web controller, safety arbitration
rpi_wsRaspberry Pi (on the ROV)IMU / barometer / sonar acquisition

The PID/state-space controller lives in a separate repository, ros2_controller_tuning_aid, and is wired in by the workstation.launch.py of gui_pkg.


Table of contents

  1. Installation
  2. Build
  3. Run
  4. Workspace structure
  5. ROS 2 node and topic map
  6. GUI features
  7. Web controller (phone / tablet)
  8. Local testing (no ROV hardware)
  9. Troubleshooting

Installation

0. Clone the repos

nereo_interfaces is a git submodule of this repo. Clone with --recurse-submodules:

git clone --recurse-submodules https://github.com/PoliTOcean/nereo_ros2_code.git

If already cloned without it:

git submodule update --init

Clone the controller repo next to this one (paths in workstation.launch.py assume they are siblings under ~/Documents/PoliTOcean/RD/):

git clone https://github.com/PoliTOcean/ros2_controller_tuning_aid.git

1. System dependencies

sudo apt update
sudo apt install \
ros-humble-rosbridge-suite \
ros-humble-tf-transformations \
gstreamer1.0-plugins-good gstreamer1.0-plugins-base gstreamer1.0-tools \
python3-qrcode python3-pil
PackageUsed byWhy it's needed
ros-humble-rosbridge-suiteweb_pkgWebSocket bridge for the web controller (port 9090)
ros-humble-tf-transformationsgui_pkgQuaternion → RPY conversion in the GUI
gstreamer1.0-plugins-*gui_pkgLive H.264/UDP video from the ROV cameras
python3-qrcode, python3-pilgui_pkgGenerates the QR code shown on Ctrl+Q for the web controller URL

2. PyQt6 with QML bindings (pip)

The apt python3-pyqt6 package ships without QtQml/QtQuick. Install via pip:

pip install PyQt6

3. Register the custom rosdep sources

A few dependencies (PyQt6, bluerobotics-ping) are not in the default rosdep index. The local override sits at rosdep.yaml in this repo:

echo"yaml file://$(pwd)/rosdep.yaml"| sudo tee /etc/ros/rosdep/sources.list.d/nereo.list
rosdep update

4. Install per-package dependencies

# Control station (PC)cd gui_ws
rosdep install --from-paths src --ignore-src -r -y
# Raspberry Picd ../rpi_ws
rosdep install --from-paths src --ignore-src -r -y

Build

# Controller (must be built first — gui_ws launch file pulls it in via AMENT_PREFIX_PATH)cd ../ros2_controller_tuning_aid && colcon build &&source install/setup.zsh
# Control stationcd ../nereo_ros2_code/gui_ws && colcon build &&source install/setup.zsh
# Raspberry Picd ../rpi_ws && colcon build &&source install/setup.zsh

Run

A single launch file starts everything on the workstation: joystick driver, command translator, GUI, safety arbiter, rosbridge WebSocket (port 9090), web controller server (port 8080), and the controller node.

ros2 launch gui_pkg workstation.launch.py

The launch file auto-prepends the ros2_controller_tuning_aid install dir to AMENT_PREFIX_PATH, so you do not need to source that overlay manually.

Launch arguments

ArgumentDefaultDescription
device/dev/input/js0Joystick device path
deadzone0.05Joystick deadzone
max_steps10Quantization steps for axes
btn_arm8Arm/disarm button index
btn_mode6Direct ↔ Controller mode toggle button index
control_mode0Initial controller mode (0=passthrough, 1=PID, 2=PID-AW, 3=CS)

Physical controller mapping

InputActionXbox One S (default)DS5
Left stick Y/XSurge / Sway
Right stick Y/XHeave / Yaw
D-pad up/downPitch trim
D-pad left/rightRoll trim
Arm/DisarmToggle armXbox (btn 8)PS (btn 10)
Mode toggleDirect ↔ ControllerView (btn 6)Share (btn 8)

Examples:

# DS5 button mapping
ros2 launch gui_pkg workstation.launch.py btn_arm:=10 btn_mode:=8
# Different joystick device
ros2 launch gui_pkg workstation.launch.py device:=/dev/input/js1

Direct mode (red lock icon in GUI): commands go to /nereo_cmd_vel_joysafety_node → ROV. Controller mode (blue lock icon in GUI): commands go to /nereo_cmd_vel_no_fb → controller node → safety_node → ROV.


Workspace structure

nereo_ros2_code/
├── gui_ws/ # Workstation workspace
│ └── src/
│ ├── gui_pkg/ # QML/PyQt6 dashboard + joystick + rov simulator
│ │ ├── gui_pkg/
│ │ │ ├── gui_node.py # Main node + ROSQmlBridge / SonarBridge / ControllerBridge
│ │ │ ├── rov_sim_node.py # Full ROV simulator (IMU, barometer, cameras, joy)
│ │ │ └── qml/ # QML UI
│ │ │ ├── main.qml
│ │ │ └── components/
│ │ │ ├── ControllerTunerWindow.qml # PID/CS tuning UI (deg/m setpoints)
│ │ │ ├── ControlPanelWindow.qml
│ │ │ ├── SonarWindow.qml
│ │ │ ├── Orientation2D.qml
│ │ │ └── VideoBox.qml
│ │ └── launch/
│ │ └── workstation.launch.py
│ ├── joystick_pkg/ # joy → CommandVelocity
│ ├── web_pkg/
│ │ ├── web_server_node.py # HTTP server on :8080
│ │ ├── safety_node.py # Arbitrates physical vs. web commands
│ │ └── static/ # Web controller UI assets
│ └── nereo_interfaces/ # CommandVelocity, ThrusterStatuses (submodule)
└── rpi_ws/ # Raspberry Pi workspace
└── src/
├── nereo_sensors_pkg/ # C++ — IMU (WT61P) + barometer (MS5837)
│ ├── src/imuPub.cpp # publishes /imu_data, service imu_reset_reference
│ ├── src/barPub.cpp # publishes /barometer_*, service barometer_reset_reference
│ └── include/nereo_sensors_pkg/
└── sonar_pkg/ # Python — Blue Robotics Ping1D

ROS 2 node and topic map

Nodes

NodePackageSideRole
imu_publishernereo_sensors_pkgRPiWT61P over I2C → /imu_data; service imu_reset_reference
bar_publishernereo_sensors_pkgRPiMS5837 over I2C → /barometer_*; service barometer_reset_reference
sonar_nodesonar_pkgRPiPing1D over serial → /sonar/*
gui_nodegui_pkgPCQML dashboard, telemetry fusion, QR code, tuner
joy_to_cmd_veljoystick_pkgPCJoystick → CommandVelocity
safety_nodeweb_pkgPCArbitrates controller + web commands → /nereo_cmd_vel
web_server_nodeweb_pkgPCServes the web controller on :8080
rosbridge_websocketrosbridge_serverPCWebSocket bridge for the web client (:9090)
nereo_controller_nodenereo_controller_nodePCPID / state-space controller (separate repo)
rov_sim_nodegui_pkgPCLocal ROV simulator (no hardware needed)
sonar_sim_nodesonar_pkgPCSynthetic sonar data

Topics

PublisherTopicTypeConsumers
imu_publisherimu_datasensor_msgs/Imugui_node, nereo_controller_node
imu_publisherimu_diagnosticdiagnostic_msgs/DiagnosticArray
bar_publisherbarometer_pressuresensor_msgs/FluidPressuregui_node, nereo_controller_node
bar_publisherbarometer_depth_saltstd_msgs/Float32gui_node (depth widget)
bar_publisherbarometer_depth_freshstd_msgs/Float32
bar_publisherbarometer_temperaturesensor_msgs/Temperaturegui_node
bar_publisherbarometer_diagnosticdiagnostic_msgs/DiagnosticArray
sonar_node / sonar_sim_nodesonar/distancestd_msgs/Float32gui_node
sonar_node / sonar_sim_nodesonar/confidencestd_msgs/Int32gui_node
sonar_node / sonar_sim_nodesonar/profilestd_msgs/Float32MultiArraygui_node
joy_to_cmd_vel/nereo_cmd_vel_joynereo_interfaces/CommandVelocitysafety_node
joy_to_cmd_vel/nereo_cmd_vel_no_fbnereo_interfaces/CommandVelocitynereo_controller_node
joy_to_cmd_vel/joy_control_activestd_msgs/Boolgui_node, web
joy_to_cmd_vel / web/set_arm_modestd_msgs/BoolROV firmware
nereo_controller_node/nereo_cmd_vel (controller mode)nereo_interfaces/CommandVelocitysafety_node
nereo_controller_node/controller/setpointsstd_msgs/Float64MultiArraytuner GUI
nereo_controller_node/controller/errorsstd_msgs/Float64MultiArraytuner GUI
nereo_controller_node/controller/pid_termsstd_msgs/Float64MultiArraytuner GUI
web client/web_cmd_velnereo_interfaces/CommandVelocitysafety_node
safety_node/nereo_cmd_vel (direct mode)nereo_interfaces/CommandVelocityROV firmware
ROV firmware/rov_armedstd_msgs/Boolgui_node, joy_to_cmd_vel, web
ROV firmware/thruster_statusnereo_interfaces/ThrusterStatuses

Services

ServiceTypeProviderUsed byEffect
barometer_reset_referencestd_srvs/Triggerbar_publisherGUI "ZERO" buttonRe-zeroes the surface pressure → depth = 0 at current spot
imu_reset_referencestd_srvs/Triggerimu_publisherGUI "ZERO RPY" buttonStores current orientation as offset → roll/pitch/yaw = 0 at current attitude

Safety arbitration (safety_node)

ConditionOutput
Controller active (last msg < 0.5 s)Controller command forwarded, web ignored
Only web activeWeb command forwarded
Web silent 0–1 sLast web command held
Web silent 1–1.5 sCommand ramped to zero
Web silent > 1.5 sZero command sent
No source activeZero command sent

Architecture diagram

graph LR
subgraph RPI[rpi_ws — Raspberry Pi]
IMU[imu_publisher\nimu_reset_reference svc]
BAR[bar_publisher\nbarometer_reset_reference svc]
SONAR[sonar_node]
CAM[GStreamer cameras\nH264 UDP]
end
subgraph CTRL[gui_ws — Control Station]
GUI[gui_node\nROSQmlBridge / SonarBridge / ControllerBridge]
JOY[joy_to_cmd_vel]
SAFETY[safety_node]
WEB[web_server_node\nport 8080]
RB[rosbridge_websocket\nport 9090]
end
subgraph TUNE[ros2_controller_tuning_aid]
NCN[nereo_controller_node]
end
subgraph SIM[Simulators — local testing]
ROVSIM[rov_sim_node]
SONARSIM[sonar_sim_node]
end
PHONE[Phone / tablet\nbrowser]
IMU -.->|imu_data| GUI
IMU -.->|imu_data| NCN
BAR -.->|barometer_pressure| GUI
BAR -.->|barometer_pressure| NCN
BAR -.->|barometer_depth_salt| GUI
SONAR -.->|sonar/*| GUI
CAM -.->|RTP/UDP 5001-5003| GUI
JOY -->|nereo_cmd_vel_joy| SAFETY
JOY -->|nereo_cmd_vel_no_fb| NCN
NCN -->|nereo_cmd_vel| SAFETY
PHONE -->|web_cmd_vel via :9090| RB
RB --> SAFETY
WEB -->|UI :8080| PHONE
SAFETY -->|nereo_cmd_vel| ROV[(ROV firmware\nmicroROS)]
JOY -->|set_arm_mode| ROV
ROV -->|rov_armed| GUI
ROV -->|rov_armed| JOY
GUI -.->|set_parameters| NCN
NCN -.->|/controller/*| GUI
Loading

GUI features

Main dashboard

  • 3× live H.264/UDP camera streams (main + 2 secondary)
  • IMU orientation widget (yaw / pitch / roll)
  • Depth, temperature, ROV arm/connection status
  • ZERO button → re-zeroes depth at the surface (calls barometer_reset_reference)
  • ZERO RPY button → re-zeroes attitude when the ROV is sitting level (calls imu_reset_reference)
  • Ctrl+Q → opens a popup with a QR code + URL (http://<ip>:8080) for the web controller. The IP is recomputed every time the shortcut is pressed, so it works even if you connect to Wi-Fi after launching the GUI.

Controller Tuner window (TUNER button)

  • Selects control mode (0 passthrough / 1 PID / 2 PID anti-windup / 3 CS)
  • Edits kp/ki/kd for depth/roll/pitch/yaw
  • Manual setpoint toggles per axis
  • Setpoint inputs in display units: depth in metres, roll/pitch/yaw in degrees. Conversion to controller units (Pa, rad) is done inside QML (density ρ = 1025 kg/m³, salt water — matches barometer_depth_salt). The controller-side parameters stay in their native units.
  • Full CS controller section (kx, ki, heave/angle limits)
  • Live telemetry: /controller/setpoints, /controller/errors, /controller/pid_terms

Sonar Viewer (SONAR button)

Waterfall + A-scan rendered into a QQuickImageProvider, confidence threshold filter.

Control Panel (CONTROL PANEL button)

Manual arming / mode toggles independent from the joystick.


Web controller (phone / tablet)

Open from any device on the same network:

  • Controller: http://<workstation-ip>:8080
  • ROV simulator: http://<workstation-ip>:8080/sim.html

The fastest way to get the URL on a phone: focus the GUI and press Ctrl+Q — a QR code with the URL appears.

The web client publishes on /web_cmd_vel. safety_node gives priority to the physical controller when both are active.

The web_server_node is robust to GUI shutdown: it installs a SIGTERM handler, closes the HTTP socket, and joins its serving thread on destroy_node(), so port 8080 is released cleanly and the launch file can be restarted without pkill.


Local testing (no ROV hardware)

# Terminal 1 — full ROV simulator (IMU, barometer, joystick, arm, 3 GStreamer test streams)
ros2 run gui_pkg rov_sim_node
# (without GStreamer: --ros-args -p simulate_cameras:=false)# Terminal 2 — sonar simulator
ros2 run sonar_pkg sonar_sim_node
# Terminal 3 — workstation stack
ros2 launch gui_pkg workstation.launch.py

Open http://localhost:8080 for the web controller and http://localhost:8080/sim.html for the top-down ROV simulator.

Manual test helpers

  • gui_ws/src/gui_pkg/test/cam_test.sh — three independent GStreamer test streams
  • ros2 run joystick_pkg rov_cmd_monitor — terminal dashboard of the live 6-DOF command vector

Unit tests

Inside unit_tests/ each subfolder is a CMake project for stdout-level debugging:

cd unit_tests/<name>
cmake .&& make
./<name>

Troubleshooting

SymptomCauseFix
Address already in use :8080 at relaunchOld web_server_node still runningShould not happen anymore (the node releases the socket on SIGTERM); if it does, pkill -f web_server_node
GUI starts but no nereo_controller_noderos2_controller_tuning_aid not built or not at sibling pathCheck ~/Documents/PoliTOcean/RD/ros2_controller_tuning_aid/install/ exists
imu_reset_reference service not availablerpi_ws not rebuilt after the new service was addedOn the Pi: colcon build --packages-select nereo_sensors_pkg
Ctrl+Q shows "Nessuna rete"No default-route interface upConnect to Wi-Fi / Ethernet, press Ctrl+Q again (re-evaluated each time)
Ctrl+Q shows "Pacchetto python3-qrcode mancante"Missing system packagesudo apt install python3-qrcode python3-pil
Tuner setpoint value looks wrong after RELOADController still has old unit valueThe GUI now converts deg↔rad and m↔Pa; if you set parameters from CLI in raw units, RELOAD will display them converted

About

No description, website, or topics provided.

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Nereo PoliTOcean

Code for the Nereo ROV by PoliTOcean.

The project is split into two ROS 2 (Humble) workspaces:

WorkspaceSidePurpose
gui_wsControl station (PC)GUI, joystick, web controller, safety arbitration
rpi_wsRaspberry Pi (on the ROV)IMU / barometer / sonar acquisition

The PID/state-space controller lives in a separate repository, ros2_controller_tuning_aid, and is wired in by the workstation.launch.py of gui_pkg.


Table of contents

  1. Installation
  2. Build
  3. Run
  4. Workspace structure
  5. ROS 2 node and topic map
  6. GUI features
  7. Web controller (phone / tablet)
  8. Local testing (no ROV hardware)
  9. Troubleshooting

Installation

0. Clone the repos

nereo_interfaces is a git submodule of this repo. Clone with --recurse-submodules:

git clone --recurse-submodules https://github.com/PoliTOcean/nereo_ros2_code.git

If already cloned without it:

git submodule update --init

Clone the controller repo next to this one (paths in workstation.launch.py assume they are siblings under ~/Documents/PoliTOcean/RD/):

git clone https://github.com/PoliTOcean/ros2_controller_tuning_aid.git

1. System dependencies

sudo apt update
sudo apt install \
ros-humble-rosbridge-suite \
ros-humble-tf-transformations \
gstreamer1.0-plugins-good gstreamer1.0-plugins-base gstreamer1.0-tools \
python3-qrcode python3-pil
PackageUsed byWhy it's needed
ros-humble-rosbridge-suiteweb_pkgWebSocket bridge for the web controller (port 9090)
ros-humble-tf-transformationsgui_pkgQuaternion → RPY conversion in the GUI
gstreamer1.0-plugins-*gui_pkgLive H.264/UDP video from the ROV cameras
python3-qrcode, python3-pilgui_pkgGenerates the QR code shown on Ctrl+Q for the web controller URL

2. PyQt6 with QML bindings (pip)

The apt python3-pyqt6 package ships without QtQml/QtQuick. Install via pip:

pip install PyQt6

3. Register the custom rosdep sources

A few dependencies (PyQt6, bluerobotics-ping) are not in the default rosdep index. The local override sits at rosdep.yaml in this repo:

echo"yaml file://$(pwd)/rosdep.yaml"| sudo tee /etc/ros/rosdep/sources.list.d/nereo.list
rosdep update

4. Install per-package dependencies

# Control station (PC)cd gui_ws
rosdep install --from-paths src --ignore-src -r -y
# Raspberry Picd ../rpi_ws
rosdep install --from-paths src --ignore-src -r -y

Build

# Controller (must be built first — gui_ws launch file pulls it in via AMENT_PREFIX_PATH)cd ../ros2_controller_tuning_aid && colcon build &&source install/setup.zsh
# Control stationcd ../nereo_ros2_code/gui_ws && colcon build &&source install/setup.zsh
# Raspberry Picd ../rpi_ws && colcon build &&source install/setup.zsh

Run

A single launch file starts everything on the workstation: joystick driver, command translator, GUI, safety arbiter, rosbridge WebSocket (port 9090), web controller server (port 8080), and the controller node.

ros2 launch gui_pkg workstation.launch.py

The launch file auto-prepends the ros2_controller_tuning_aid install dir to AMENT_PREFIX_PATH, so you do not need to source that overlay manually.

Launch arguments

ArgumentDefaultDescription
device/dev/input/js0Joystick device path
deadzone0.05Joystick deadzone
max_steps10Quantization steps for axes
btn_arm8Arm/disarm button index
btn_mode6Direct ↔ Controller mode toggle button index
control_mode0Initial controller mode (0=passthrough, 1=PID, 2=PID-AW, 3=CS)

Physical controller mapping

InputActionXbox One S (default)DS5
Left stick Y/XSurge / Sway
Right stick Y/XHeave / Yaw
D-pad up/downPitch trim
D-pad left/rightRoll trim
Arm/DisarmToggle armXbox (btn 8)PS (btn 10)
Mode toggleDirect ↔ ControllerView (btn 6)Share (btn 8)

Examples:

# DS5 button mapping
ros2 launch gui_pkg workstation.launch.py btn_arm:=10 btn_mode:=8
# Different joystick device
ros2 launch gui_pkg workstation.launch.py device:=/dev/input/js1

Direct mode (red lock icon in GUI): commands go to /nereo_cmd_vel_joysafety_node → ROV. Controller mode (blue lock icon in GUI): commands go to /nereo_cmd_vel_no_fb → controller node → safety_node → ROV.


Workspace structure

nereo_ros2_code/
├── gui_ws/ # Workstation workspace
│ └── src/
│ ├── gui_pkg/ # QML/PyQt6 dashboard + joystick + rov simulator
│ │ ├── gui_pkg/
│ │ │ ├── gui_node.py # Main node + ROSQmlBridge / SonarBridge / ControllerBridge
│ │ │ ├── rov_sim_node.py # Full ROV simulator (IMU, barometer, cameras, joy)
│ │ │ └── qml/ # QML UI
│ │ │ ├── main.qml
│ │ │ └── components/
│ │ │ ├── ControllerTunerWindow.qml # PID/CS tuning UI (deg/m setpoints)
│ │ │ ├── ControlPanelWindow.qml
│ │ │ ├── SonarWindow.qml
│ │ │ ├── Orientation2D.qml
│ │ │ └── VideoBox.qml
│ │ └── launch/
│ │ └── workstation.launch.py
│ ├── joystick_pkg/ # joy → CommandVelocity
│ ├── web_pkg/
│ │ ├── web_server_node.py # HTTP server on :8080
│ │ ├── safety_node.py # Arbitrates physical vs. web commands
│ │ └── static/ # Web controller UI assets
│ └── nereo_interfaces/ # CommandVelocity, ThrusterStatuses (submodule)
└── rpi_ws/ # Raspberry Pi workspace
└── src/
├── nereo_sensors_pkg/ # C++ — IMU (WT61P) + barometer (MS5837)
│ ├── src/imuPub.cpp # publishes /imu_data, service imu_reset_reference
│ ├── src/barPub.cpp # publishes /barometer_*, service barometer_reset_reference
│ └── include/nereo_sensors_pkg/
└── sonar_pkg/ # Python — Blue Robotics Ping1D

ROS 2 node and topic map

Nodes

NodePackageSideRole
imu_publishernereo_sensors_pkgRPiWT61P over I2C → /imu_data; service imu_reset_reference
bar_publishernereo_sensors_pkgRPiMS5837 over I2C → /barometer_*; service barometer_reset_reference
sonar_nodesonar_pkgRPiPing1D over serial → /sonar/*
gui_nodegui_pkgPCQML dashboard, telemetry fusion, QR code, tuner
joy_to_cmd_veljoystick_pkgPCJoystick → CommandVelocity
safety_nodeweb_pkgPCArbitrates controller + web commands → /nereo_cmd_vel
web_server_nodeweb_pkgPCServes the web controller on :8080
rosbridge_websocketrosbridge_serverPCWebSocket bridge for the web client (:9090)
nereo_controller_nodenereo_controller_nodePCPID / state-space controller (separate repo)
rov_sim_nodegui_pkgPCLocal ROV simulator (no hardware needed)
sonar_sim_nodesonar_pkgPCSynthetic sonar data

Topics

PublisherTopicTypeConsumers
imu_publisherimu_datasensor_msgs/Imugui_node, nereo_controller_node
imu_publisherimu_diagnosticdiagnostic_msgs/DiagnosticArray
bar_publisherbarometer_pressuresensor_msgs/FluidPressuregui_node, nereo_controller_node
bar_publisherbarometer_depth_saltstd_msgs/Float32gui_node (depth widget)
bar_publisherbarometer_depth_freshstd_msgs/Float32
bar_publisherbarometer_temperaturesensor_msgs/Temperaturegui_node
bar_publisherbarometer_diagnosticdiagnostic_msgs/DiagnosticArray
sonar_node / sonar_sim_nodesonar/distancestd_msgs/Float32gui_node
sonar_node / sonar_sim_nodesonar/confidencestd_msgs/Int32gui_node
sonar_node / sonar_sim_nodesonar/profilestd_msgs/Float32MultiArraygui_node
joy_to_cmd_vel/nereo_cmd_vel_joynereo_interfaces/CommandVelocitysafety_node
joy_to_cmd_vel/nereo_cmd_vel_no_fbnereo_interfaces/CommandVelocitynereo_controller_node
joy_to_cmd_vel/joy_control_activestd_msgs/Boolgui_node, web
joy_to_cmd_vel / web/set_arm_modestd_msgs/BoolROV firmware
nereo_controller_node/nereo_cmd_vel (controller mode)nereo_interfaces/CommandVelocitysafety_node
nereo_controller_node/controller/setpointsstd_msgs/Float64MultiArraytuner GUI
nereo_controller_node/controller/errorsstd_msgs/Float64MultiArraytuner GUI
nereo_controller_node/controller/pid_termsstd_msgs/Float64MultiArraytuner GUI
web client/web_cmd_velnereo_interfaces/CommandVelocitysafety_node
safety_node/nereo_cmd_vel (direct mode)nereo_interfaces/CommandVelocityROV firmware
ROV firmware/rov_armedstd_msgs/Boolgui_node, joy_to_cmd_vel, web
ROV firmware/thruster_statusnereo_interfaces/ThrusterStatuses

Services

ServiceTypeProviderUsed byEffect
barometer_reset_referencestd_srvs/Triggerbar_publisherGUI "ZERO" buttonRe-zeroes the surface pressure → depth = 0 at current spot
imu_reset_referencestd_srvs/Triggerimu_publisherGUI "ZERO RPY" buttonStores current orientation as offset → roll/pitch/yaw = 0 at current attitude

Safety arbitration (safety_node)

ConditionOutput
Controller active (last msg < 0.5 s)Controller command forwarded, web ignored
Only web activeWeb command forwarded
Web silent 0–1 sLast web command held
Web silent 1–1.5 sCommand ramped to zero
Web silent > 1.5 sZero command sent
No source activeZero command sent

Architecture diagram

graph LR
subgraph RPI[rpi_ws — Raspberry Pi]
IMU[imu_publisher\nimu_reset_reference svc]
BAR[bar_publisher\nbarometer_reset_reference svc]
SONAR[sonar_node]
CAM[GStreamer cameras\nH264 UDP]
end
subgraph CTRL[gui_ws — Control Station]
GUI[gui_node\nROSQmlBridge / SonarBridge / ControllerBridge]
JOY[joy_to_cmd_vel]
SAFETY[safety_node]
WEB[web_server_node\nport 8080]
RB[rosbridge_websocket\nport 9090]
end
subgraph TUNE[ros2_controller_tuning_aid]
NCN[nereo_controller_node]
end
subgraph SIM[Simulators — local testing]
ROVSIM[rov_sim_node]
SONARSIM[sonar_sim_node]
end
PHONE[Phone / tablet\nbrowser]
IMU -.->|imu_data| GUI
IMU -.->|imu_data| NCN
BAR -.->|barometer_pressure| GUI
BAR -.->|barometer_pressure| NCN
BAR -.->|barometer_depth_salt| GUI
SONAR -.->|sonar/*| GUI
CAM -.->|RTP/UDP 5001-5003| GUI
JOY -->|nereo_cmd_vel_joy| SAFETY
JOY -->|nereo_cmd_vel_no_fb| NCN
NCN -->|nereo_cmd_vel| SAFETY
PHONE -->|web_cmd_vel via :9090| RB
RB --> SAFETY
WEB -->|UI :8080| PHONE
SAFETY -->|nereo_cmd_vel| ROV[(ROV firmware\nmicroROS)]
JOY -->|set_arm_mode| ROV
ROV -->|rov_armed| GUI
ROV -->|rov_armed| JOY
GUI -.->|set_parameters| NCN
NCN -.->|/controller/*| GUI
Loading

GUI features

Main dashboard

  • 3× live H.264/UDP camera streams (main + 2 secondary)
  • IMU orientation widget (yaw / pitch / roll)
  • Depth, temperature, ROV arm/connection status
  • ZERO button → re-zeroes depth at the surface (calls barometer_reset_reference)
  • ZERO RPY button → re-zeroes attitude when the ROV is sitting level (calls imu_reset_reference)
  • Ctrl+Q → opens a popup with a QR code + URL (http://<ip>:8080) for the web controller. The IP is recomputed every time the shortcut is pressed, so it works even if you connect to Wi-Fi after launching the GUI.

Controller Tuner window (TUNER button)

  • Selects control mode (0 passthrough / 1 PID / 2 PID anti-windup / 3 CS)
  • Edits kp/ki/kd for depth/roll/pitch/yaw
  • Manual setpoint toggles per axis
  • Setpoint inputs in display units: depth in metres, roll/pitch/yaw in degrees. Conversion to controller units (Pa, rad) is done inside QML (density ρ = 1025 kg/m³, salt water — matches barometer_depth_salt). The controller-side parameters stay in their native units.
  • Full CS controller section (kx, ki, heave/angle limits)
  • Live telemetry: /controller/setpoints, /controller/errors, /controller/pid_terms

Sonar Viewer (SONAR button)

Waterfall + A-scan rendered into a QQuickImageProvider, confidence threshold filter.

Control Panel (CONTROL PANEL button)

Manual arming / mode toggles independent from the joystick.


Web controller (phone / tablet)

Open from any device on the same network:

  • Controller: http://<workstation-ip>:8080
  • ROV simulator: http://<workstation-ip>:8080/sim.html

The fastest way to get the URL on a phone: focus the GUI and press Ctrl+Q — a QR code with the URL appears.

The web client publishes on /web_cmd_vel. safety_node gives priority to the physical controller when both are active.

The web_server_node is robust to GUI shutdown: it installs a SIGTERM handler, closes the HTTP socket, and joins its serving thread on destroy_node(), so port 8080 is released cleanly and the launch file can be restarted without pkill.


Local testing (no ROV hardware)

# Terminal 1 — full ROV simulator (IMU, barometer, joystick, arm, 3 GStreamer test streams)
ros2 run gui_pkg rov_sim_node
# (without GStreamer: --ros-args -p simulate_cameras:=false)# Terminal 2 — sonar simulator
ros2 run sonar_pkg sonar_sim_node
# Terminal 3 — workstation stack
ros2 launch gui_pkg workstation.launch.py

Open http://localhost:8080 for the web controller and http://localhost:8080/sim.html for the top-down ROV simulator.

Manual test helpers

  • gui_ws/src/gui_pkg/test/cam_test.sh — three independent GStreamer test streams
  • ros2 run joystick_pkg rov_cmd_monitor — terminal dashboard of the live 6-DOF command vector

Unit tests

Inside unit_tests/ each subfolder is a CMake project for stdout-level debugging:

cd unit_tests/<name>
cmake .&& make
./<name>

Troubleshooting

SymptomCauseFix
Address already in use :8080 at relaunchOld web_server_node still runningShould not happen anymore (the node releases the socket on SIGTERM); if it does, pkill -f web_server_node
GUI starts but no nereo_controller_noderos2_controller_tuning_aid not built or not at sibling pathCheck ~/Documents/PoliTOcean/RD/ros2_controller_tuning_aid/install/ exists
imu_reset_reference service not availablerpi_ws not rebuilt after the new service was addedOn the Pi: colcon build --packages-select nereo_sensors_pkg
Ctrl+Q shows "Nessuna rete"No default-route interface upConnect to Wi-Fi / Ethernet, press Ctrl+Q again (re-evaluated each time)
Ctrl+Q shows "Pacchetto python3-qrcode mancante"Missing system packagesudo apt install python3-qrcode python3-pil
Tuner setpoint value looks wrong after RELOADController still has old unit valueThe GUI now converts deg↔rad and m↔Pa; if you set parameters from CLI in raw units, RELOAD will display them converted

About

No description, website, or topics provided.

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

Nereo PoliTOcean

Code for the Nereo ROV by PoliTOcean.

The project is split into two ROS 2 (Humble) workspaces:

WorkspaceSidePurpose
gui_wsControl station (PC)GUI, joystick, web controller, safety arbitration
rpi_wsRaspberry Pi (on the ROV)IMU / barometer / sonar acquisition

The PID/state-space controller lives in a separate repository, ros2_controller_tuning_aid, and is wired in by the workstation.launch.py of gui_pkg.


Table of contents

  1. Installation
  2. Build
  3. Run
  4. Workspace structure
  5. ROS 2 node and topic map
  6. GUI features
  7. Web controller (phone / tablet)
  8. Local testing (no ROV hardware)
  9. Troubleshooting

Installation

0. Clone the repos

nereo_interfaces is a git submodule of this repo. Clone with --recurse-submodules:

git clone --recurse-submodules https://github.com/PoliTOcean/nereo_ros2_code.git

If already cloned without it:

git submodule update --init

Clone the controller repo next to this one (paths in workstation.launch.py assume they are siblings under ~/Documents/PoliTOcean/RD/):

git clone https://github.com/PoliTOcean/ros2_controller_tuning_aid.git

1. System dependencies

sudo apt update
sudo apt install \
ros-humble-rosbridge-suite \
ros-humble-tf-transformations \
gstreamer1.0-plugins-good gstreamer1.0-plugins-base gstreamer1.0-tools \
python3-qrcode python3-pil
PackageUsed byWhy it's needed
ros-humble-rosbridge-suiteweb_pkgWebSocket bridge for the web controller (port 9090)
ros-humble-tf-transformationsgui_pkgQuaternion → RPY conversion in the GUI
gstreamer1.0-plugins-*gui_pkgLive H.264/UDP video from the ROV cameras
python3-qrcode, python3-pilgui_pkgGenerates the QR code shown on Ctrl+Q for the web controller URL

2. PyQt6 with QML bindings (pip)

The apt python3-pyqt6 package ships without QtQml/QtQuick. Install via pip:

pip install PyQt6

3. Register the custom rosdep sources

A few dependencies (PyQt6, bluerobotics-ping) are not in the default rosdep index. The local override sits at rosdep.yaml in this repo:

echo"yaml file://$(pwd)/rosdep.yaml"| sudo tee /etc/ros/rosdep/sources.list.d/nereo.list
rosdep update

4. Install per-package dependencies

# Control station (PC)cd gui_ws
rosdep install --from-paths src --ignore-src -r -y
# Raspberry Picd ../rpi_ws
rosdep install --from-paths src --ignore-src -r -y

Build

# Controller (must be built first — gui_ws launch file pulls it in via AMENT_PREFIX_PATH)cd ../ros2_controller_tuning_aid && colcon build &&source install/setup.zsh
# Control stationcd ../nereo_ros2_code/gui_ws && colcon build &&source install/setup.zsh
# Raspberry Picd ../rpi_ws && colcon build &&source install/setup.zsh

Run

A single launch file starts everything on the workstation: joystick driver, command translator, GUI, safety arbiter, rosbridge WebSocket (port 9090), web controller server (port 8080), and the controller node.

ros2 launch gui_pkg workstation.launch.py

The launch file auto-prepends the ros2_controller_tuning_aid install dir to AMENT_PREFIX_PATH, so you do not need to source that overlay manually.

Launch arguments

ArgumentDefaultDescription
device/dev/input/js0Joystick device path
deadzone0.05Joystick deadzone
max_steps10Quantization steps for axes
btn_arm8Arm/disarm button index
btn_mode6Direct ↔ Controller mode toggle button index
control_mode0Initial controller mode (0=passthrough, 1=PID, 2=PID-AW, 3=CS)

Physical controller mapping

InputActionXbox One S (default)DS5
Left stick Y/XSurge / Sway
Right stick Y/XHeave / Yaw
D-pad up/downPitch trim
D-pad left/rightRoll trim
Arm/DisarmToggle armXbox (btn 8)PS (btn 10)
Mode toggleDirect ↔ ControllerView (btn 6)Share (btn 8)

Examples:

# DS5 button mapping
ros2 launch gui_pkg workstation.launch.py btn_arm:=10 btn_mode:=8
# Different joystick device
ros2 launch gui_pkg workstation.launch.py device:=/dev/input/js1

Direct mode (red lock icon in GUI): commands go to /nereo_cmd_vel_joysafety_node → ROV. Controller mode (blue lock icon in GUI): commands go to /nereo_cmd_vel_no_fb → controller node → safety_node → ROV.


Workspace structure

nereo_ros2_code/
├── gui_ws/ # Workstation workspace
│ └── src/
│ ├── gui_pkg/ # QML/PyQt6 dashboard + joystick + rov simulator
│ │ ├── gui_pkg/
│ │ │ ├── gui_node.py # Main node + ROSQmlBridge / SonarBridge / ControllerBridge
│ │ │ ├── rov_sim_node.py # Full ROV simulator (IMU, barometer, cameras, joy)
│ │ │ └── qml/ # QML UI
│ │ │ ├── main.qml
│ │ │ └── components/
│ │ │ ├── ControllerTunerWindow.qml # PID/CS tuning UI (deg/m setpoints)
│ │ │ ├── ControlPanelWindow.qml
│ │ │ ├── SonarWindow.qml
│ │ │ ├── Orientation2D.qml
│ │ │ └── VideoBox.qml
│ │ └── launch/
│ │ └── workstation.launch.py
│ ├── joystick_pkg/ # joy → CommandVelocity
│ ├── web_pkg/
│ │ ├── web_server_node.py # HTTP server on :8080
│ │ ├── safety_node.py # Arbitrates physical vs. web commands
│ │ └── static/ # Web controller UI assets
│ └── nereo_interfaces/ # CommandVelocity, ThrusterStatuses (submodule)
└── rpi_ws/ # Raspberry Pi workspace
└── src/
├── nereo_sensors_pkg/ # C++ — IMU (WT61P) + barometer (MS5837)
│ ├── src/imuPub.cpp # publishes /imu_data, service imu_reset_reference
│ ├── src/barPub.cpp # publishes /barometer_*, service barometer_reset_reference
│ └── include/nereo_sensors_pkg/
└── sonar_pkg/ # Python — Blue Robotics Ping1D

ROS 2 node and topic map

Nodes

NodePackageSideRole
imu_publishernereo_sensors_pkgRPiWT61P over I2C → /imu_data; service imu_reset_reference
bar_publishernereo_sensors_pkgRPiMS5837 over I2C → /barometer_*; service barometer_reset_reference
sonar_nodesonar_pkgRPiPing1D over serial → /sonar/*
gui_nodegui_pkgPCQML dashboard, telemetry fusion, QR code, tuner
joy_to_cmd_veljoystick_pkgPCJoystick → CommandVelocity
safety_nodeweb_pkgPCArbitrates controller + web commands → /nereo_cmd_vel
web_server_nodeweb_pkgPCServes the web controller on :8080
rosbridge_websocketrosbridge_serverPCWebSocket bridge for the web client (:9090)
nereo_controller_nodenereo_controller_nodePCPID / state-space controller (separate repo)
rov_sim_nodegui_pkgPCLocal ROV simulator (no hardware needed)
sonar_sim_nodesonar_pkgPCSynthetic sonar data

Topics

PublisherTopicTypeConsumers
imu_publisherimu_datasensor_msgs/Imugui_node, nereo_controller_node
imu_publisherimu_diagnosticdiagnostic_msgs/DiagnosticArray
bar_publisherbarometer_pressuresensor_msgs/FluidPressuregui_node, nereo_controller_node
bar_publisherbarometer_depth_saltstd_msgs/Float32gui_node (depth widget)
bar_publisherbarometer_depth_freshstd_msgs/Float32
bar_publisherbarometer_temperaturesensor_msgs/Temperaturegui_node
bar_publisherbarometer_diagnosticdiagnostic_msgs/DiagnosticArray
sonar_node / sonar_sim_nodesonar/distancestd_msgs/Float32gui_node
sonar_node / sonar_sim_nodesonar/confidencestd_msgs/Int32gui_node
sonar_node / sonar_sim_nodesonar/profilestd_msgs/Float32MultiArraygui_node
joy_to_cmd_vel/nereo_cmd_vel_joynereo_interfaces/CommandVelocitysafety_node
joy_to_cmd_vel/nereo_cmd_vel_no_fbnereo_interfaces/CommandVelocitynereo_controller_node
joy_to_cmd_vel/joy_control_activestd_msgs/Boolgui_node, web
joy_to_cmd_vel / web/set_arm_modestd_msgs/BoolROV firmware
nereo_controller_node/nereo_cmd_vel (controller mode)nereo_interfaces/CommandVelocitysafety_node
nereo_controller_node/controller/setpointsstd_msgs/Float64MultiArraytuner GUI
nereo_controller_node/controller/errorsstd_msgs/Float64MultiArraytuner GUI
nereo_controller_node/controller/pid_termsstd_msgs/Float64MultiArraytuner GUI
web client/web_cmd_velnereo_interfaces/CommandVelocitysafety_node
safety_node/nereo_cmd_vel (direct mode)nereo_interfaces/CommandVelocityROV firmware
ROV firmware/rov_armedstd_msgs/Boolgui_node, joy_to_cmd_vel, web
ROV firmware/thruster_statusnereo_interfaces/ThrusterStatuses

Services

ServiceTypeProviderUsed byEffect
barometer_reset_referencestd_srvs/Triggerbar_publisherGUI "ZERO" buttonRe-zeroes the surface pressure → depth = 0 at current spot
imu_reset_referencestd_srvs/Triggerimu_publisherGUI "ZERO RPY" buttonStores current orientation as offset → roll/pitch/yaw = 0 at current attitude

Safety arbitration (safety_node)

ConditionOutput
Controller active (last msg < 0.5 s)Controller command forwarded, web ignored
Only web activeWeb command forwarded
Web silent 0–1 sLast web command held
Web silent 1–1.5 sCommand ramped to zero
Web silent > 1.5 sZero command sent
No source activeZero command sent

Architecture diagram

graph LR
subgraph RPI[rpi_ws — Raspberry Pi]
IMU[imu_publisher\nimu_reset_reference svc]
BAR[bar_publisher\nbarometer_reset_reference svc]
SONAR[sonar_node]
CAM[GStreamer cameras\nH264 UDP]
end
subgraph CTRL[gui_ws — Control Station]
GUI[gui_node\nROSQmlBridge / SonarBridge / ControllerBridge]
JOY[joy_to_cmd_vel]
SAFETY[safety_node]
WEB[web_server_node\nport 8080]
RB[rosbridge_websocket\nport 9090]
end
subgraph TUNE[ros2_controller_tuning_aid]
NCN[nereo_controller_node]
end
subgraph SIM[Simulators — local testing]
ROVSIM[rov_sim_node]
SONARSIM[sonar_sim_node]
end
PHONE[Phone / tablet\nbrowser]
IMU -.->|imu_data| GUI
IMU -.->|imu_data| NCN
BAR -.->|barometer_pressure| GUI
BAR -.->|barometer_pressure| NCN
BAR -.->|barometer_depth_salt| GUI
SONAR -.->|sonar/*| GUI
CAM -.->|RTP/UDP 5001-5003| GUI
JOY -->|nereo_cmd_vel_joy| SAFETY
JOY -->|nereo_cmd_vel_no_fb| NCN
NCN -->|nereo_cmd_vel| SAFETY
PHONE -->|web_cmd_vel via :9090| RB
RB --> SAFETY
WEB -->|UI :8080| PHONE
SAFETY -->|nereo_cmd_vel| ROV[(ROV firmware\nmicroROS)]
JOY -->|set_arm_mode| ROV
ROV -->|rov_armed| GUI
ROV -->|rov_armed| JOY
GUI -.->|set_parameters| NCN
NCN -.->|/controller/*| GUI
Loading

GUI features

Main dashboard

  • 3× live H.264/UDP camera streams (main + 2 secondary)
  • IMU orientation widget (yaw / pitch / roll)
  • Depth, temperature, ROV arm/connection status
  • ZERO button → re-zeroes depth at the surface (calls barometer_reset_reference)
  • ZERO RPY button → re-zeroes attitude when the ROV is sitting level (calls imu_reset_reference)
  • Ctrl+Q → opens a popup with a QR code + URL (http://<ip>:8080) for the web controller. The IP is recomputed every time the shortcut is pressed, so it works even if you connect to Wi-Fi after launching the GUI.

Controller Tuner window (TUNER button)

  • Selects control mode (0 passthrough / 1 PID / 2 PID anti-windup / 3 CS)
  • Edits kp/ki/kd for depth/roll/pitch/yaw
  • Manual setpoint toggles per axis
  • Setpoint inputs in display units: depth in metres, roll/pitch/yaw in degrees. Conversion to controller units (Pa, rad) is done inside QML (density ρ = 1025 kg/m³, salt water — matches barometer_depth_salt). The controller-side parameters stay in their native units.
  • Full CS controller section (kx, ki, heave/angle limits)
  • Live telemetry: /controller/setpoints, /controller/errors, /controller/pid_terms

Sonar Viewer (SONAR button)

Waterfall + A-scan rendered into a QQuickImageProvider, confidence threshold filter.

Control Panel (CONTROL PANEL button)

Manual arming / mode toggles independent from the joystick.


Web controller (phone / tablet)

Open from any device on the same network:

  • Controller: http://<workstation-ip>:8080
  • ROV simulator: http://<workstation-ip>:8080/sim.html

The fastest way to get the URL on a phone: focus the GUI and press Ctrl+Q — a QR code with the URL appears.

The web client publishes on /web_cmd_vel. safety_node gives priority to the physical controller when both are active.

The web_server_node is robust to GUI shutdown: it installs a SIGTERM handler, closes the HTTP socket, and joins its serving thread on destroy_node(), so port 8080 is released cleanly and the launch file can be restarted without pkill.


Local testing (no ROV hardware)

# Terminal 1 — full ROV simulator (IMU, barometer, joystick, arm, 3 GStreamer test streams)
ros2 run gui_pkg rov_sim_node
# (without GStreamer: --ros-args -p simulate_cameras:=false)# Terminal 2 — sonar simulator
ros2 run sonar_pkg sonar_sim_node
# Terminal 3 — workstation stack
ros2 launch gui_pkg workstation.launch.py

Open http://localhost:8080 for the web controller and http://localhost:8080/sim.html for the top-down ROV simulator.

Manual test helpers

  • gui_ws/src/gui_pkg/test/cam_test.sh — three independent GStreamer test streams
  • ros2 run joystick_pkg rov_cmd_monitor — terminal dashboard of the live 6-DOF command vector

Unit tests

Inside unit_tests/ each subfolder is a CMake project for stdout-level debugging:

cd unit_tests/<name>
cmake .&& make
./<name>

Troubleshooting

SymptomCauseFix
Address already in use :8080 at relaunchOld web_server_node still runningShould not happen anymore (the node releases the socket on SIGTERM); if it does, pkill -f web_server_node
GUI starts but no nereo_controller_noderos2_controller_tuning_aid not built or not at sibling pathCheck ~/Documents/PoliTOcean/RD/ros2_controller_tuning_aid/install/ exists
imu_reset_reference service not availablerpi_ws not rebuilt after the new service was addedOn the Pi: colcon build --packages-select nereo_sensors_pkg
Ctrl+Q shows "Nessuna rete"No default-route interface upConnect to Wi-Fi / Ethernet, press Ctrl+Q again (re-evaluated each time)
Ctrl+Q shows "Pacchetto python3-qrcode mancante"Missing system packagesudo apt install python3-qrcode python3-pil
Tuner setpoint value looks wrong after RELOADController still has old unit valueThe GUI now converts deg↔rad and m↔Pa; if you set parameters from CLI in raw units, RELOAD will display them converted

About

No description, website, or topics provided.

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Nereo PoliTOcean

Code for the Nereo ROV by PoliTOcean.

The project is split into two ROS 2 (Humble) workspaces:

WorkspaceSidePurpose
gui_wsControl station (PC)GUI, joystick, web controller, safety arbitration
rpi_wsRaspberry Pi (on the ROV)IMU / barometer / sonar acquisition

The PID/state-space controller lives in a separate repository, ros2_controller_tuning_aid, and is wired in by the workstation.launch.py of gui_pkg.


Table of contents

  1. Installation
  2. Build
  3. Run
  4. Workspace structure
  5. ROS 2 node and topic map
  6. GUI features
  7. Web controller (phone / tablet)
  8. Local testing (no ROV hardware)
  9. Troubleshooting

Installation

0. Clone the repos

nereo_interfaces is a git submodule of this repo. Clone with --recurse-submodules:

git clone --recurse-submodules https://github.com/PoliTOcean/nereo_ros2_code.git

If already cloned without it:

git submodule update --init

Clone the controller repo next to this one (paths in workstation.launch.py assume they are siblings under ~/Documents/PoliTOcean/RD/):

git clone https://github.com/PoliTOcean/ros2_controller_tuning_aid.git

1. System dependencies

sudo apt update
sudo apt install \
ros-humble-rosbridge-suite \
ros-humble-tf-transformations \
gstreamer1.0-plugins-good gstreamer1.0-plugins-base gstreamer1.0-tools \
python3-qrcode python3-pil
PackageUsed byWhy it's needed
ros-humble-rosbridge-suiteweb_pkgWebSocket bridge for the web controller (port 9090)
ros-humble-tf-transformationsgui_pkgQuaternion → RPY conversion in the GUI
gstreamer1.0-plugins-*gui_pkgLive H.264/UDP video from the ROV cameras
python3-qrcode, python3-pilgui_pkgGenerates the QR code shown on Ctrl+Q for the web controller URL

2. PyQt6 with QML bindings (pip)

The apt python3-pyqt6 package ships without QtQml/QtQuick. Install via pip:

pip install PyQt6

3. Register the custom rosdep sources

A few dependencies (PyQt6, bluerobotics-ping) are not in the default rosdep index. The local override sits at rosdep.yaml in this repo:

echo"yaml file://$(pwd)/rosdep.yaml"| sudo tee /etc/ros/rosdep/sources.list.d/nereo.list
rosdep update

4. Install per-package dependencies

# Control station (PC)cd gui_ws
rosdep install --from-paths src --ignore-src -r -y
# Raspberry Picd ../rpi_ws
rosdep install --from-paths src --ignore-src -r -y

Build

# Controller (must be built first — gui_ws launch file pulls it in via AMENT_PREFIX_PATH)cd ../ros2_controller_tuning_aid && colcon build &&source install/setup.zsh
# Control stationcd ../nereo_ros2_code/gui_ws && colcon build &&source install/setup.zsh
# Raspberry Picd ../rpi_ws && colcon build &&source install/setup.zsh

Run

A single launch file starts everything on the workstation: joystick driver, command translator, GUI, safety arbiter, rosbridge WebSocket (port 9090), web controller server (port 8080), and the controller node.

ros2 launch gui_pkg workstation.launch.py

The launch file auto-prepends the ros2_controller_tuning_aid install dir to AMENT_PREFIX_PATH, so you do not need to source that overlay manually.

Launch arguments

ArgumentDefaultDescription
device/dev/input/js0Joystick device path
deadzone0.05Joystick deadzone
max_steps10Quantization steps for axes
btn_arm8Arm/disarm button index
btn_mode6Direct ↔ Controller mode toggle button index
control_mode0Initial controller mode (0=passthrough, 1=PID, 2=PID-AW, 3=CS)

Physical controller mapping

InputActionXbox One S (default)DS5
Left stick Y/XSurge / Sway
Right stick Y/XHeave / Yaw
D-pad up/downPitch trim
D-pad left/rightRoll trim
Arm/DisarmToggle armXbox (btn 8)PS (btn 10)
Mode toggleDirect ↔ ControllerView (btn 6)Share (btn 8)

Examples:

# DS5 button mapping
ros2 launch gui_pkg workstation.launch.py btn_arm:=10 btn_mode:=8
# Different joystick device
ros2 launch gui_pkg workstation.launch.py device:=/dev/input/js1

Direct mode (red lock icon in GUI): commands go to /nereo_cmd_vel_joysafety_node → ROV. Controller mode (blue lock icon in GUI): commands go to /nereo_cmd_vel_no_fb → controller node → safety_node → ROV.


Workspace structure

nereo_ros2_code/
├── gui_ws/ # Workstation workspace
│ └── src/
│ ├── gui_pkg/ # QML/PyQt6 dashboard + joystick + rov simulator
│ │ ├── gui_pkg/
│ │ │ ├── gui_node.py # Main node + ROSQmlBridge / SonarBridge / ControllerBridge
│ │ │ ├── rov_sim_node.py # Full ROV simulator (IMU, barometer, cameras, joy)
│ │ │ └── qml/ # QML UI
│ │ │ ├── main.qml
│ │ │ └── components/
│ │ │ ├── ControllerTunerWindow.qml # PID/CS tuning UI (deg/m setpoints)
│ │ │ ├── ControlPanelWindow.qml
│ │ │ ├── SonarWindow.qml
│ │ │ ├── Orientation2D.qml
│ │ │ └── VideoBox.qml
│ │ └── launch/
│ │ └── workstation.launch.py
│ ├── joystick_pkg/ # joy → CommandVelocity
│ ├── web_pkg/
│ │ ├── web_server_node.py # HTTP server on :8080
│ │ ├── safety_node.py # Arbitrates physical vs. web commands
│ │ └── static/ # Web controller UI assets
│ └── nereo_interfaces/ # CommandVelocity, ThrusterStatuses (submodule)
└── rpi_ws/ # Raspberry Pi workspace
└── src/
├── nereo_sensors_pkg/ # C++ — IMU (WT61P) + barometer (MS5837)
│ ├── src/imuPub.cpp # publishes /imu_data, service imu_reset_reference
│ ├── src/barPub.cpp # publishes /barometer_*, service barometer_reset_reference
│ └── include/nereo_sensors_pkg/
└── sonar_pkg/ # Python — Blue Robotics Ping1D

ROS 2 node and topic map

Nodes

NodePackageSideRole
imu_publishernereo_sensors_pkgRPiWT61P over I2C → /imu_data; service imu_reset_reference
bar_publishernereo_sensors_pkgRPiMS5837 over I2C → /barometer_*; service barometer_reset_reference
sonar_nodesonar_pkgRPiPing1D over serial → /sonar/*
gui_nodegui_pkgPCQML dashboard, telemetry fusion, QR code, tuner
joy_to_cmd_veljoystick_pkgPCJoystick → CommandVelocity
safety_nodeweb_pkgPCArbitrates controller + web commands → /nereo_cmd_vel
web_server_nodeweb_pkgPCServes the web controller on :8080
rosbridge_websocketrosbridge_serverPCWebSocket bridge for the web client (:9090)
nereo_controller_nodenereo_controller_nodePCPID / state-space controller (separate repo)
rov_sim_nodegui_pkgPCLocal ROV simulator (no hardware needed)
sonar_sim_nodesonar_pkgPCSynthetic sonar data

Topics

PublisherTopicTypeConsumers
imu_publisherimu_datasensor_msgs/Imugui_node, nereo_controller_node
imu_publisherimu_diagnosticdiagnostic_msgs/DiagnosticArray
bar_publisherbarometer_pressuresensor_msgs/FluidPressuregui_node, nereo_controller_node
bar_publisherbarometer_depth_saltstd_msgs/Float32gui_node (depth widget)
bar_publisherbarometer_depth_freshstd_msgs/Float32
bar_publisherbarometer_temperaturesensor_msgs/Temperaturegui_node
bar_publisherbarometer_diagnosticdiagnostic_msgs/DiagnosticArray
sonar_node / sonar_sim_nodesonar/distancestd_msgs/Float32gui_node
sonar_node / sonar_sim_nodesonar/confidencestd_msgs/Int32gui_node
sonar_node / sonar_sim_nodesonar/profilestd_msgs/Float32MultiArraygui_node
joy_to_cmd_vel/nereo_cmd_vel_joynereo_interfaces/CommandVelocitysafety_node
joy_to_cmd_vel/nereo_cmd_vel_no_fbnereo_interfaces/CommandVelocitynereo_controller_node
joy_to_cmd_vel/joy_control_activestd_msgs/Boolgui_node, web
joy_to_cmd_vel / web/set_arm_modestd_msgs/BoolROV firmware
nereo_controller_node/nereo_cmd_vel (controller mode)nereo_interfaces/CommandVelocitysafety_node
nereo_controller_node/controller/setpointsstd_msgs/Float64MultiArraytuner GUI
nereo_controller_node/controller/errorsstd_msgs/Float64MultiArraytuner GUI
nereo_controller_node/controller/pid_termsstd_msgs/Float64MultiArraytuner GUI
web client/web_cmd_velnereo_interfaces/CommandVelocitysafety_node
safety_node/nereo_cmd_vel (direct mode)nereo_interfaces/CommandVelocityROV firmware
ROV firmware/rov_armedstd_msgs/Boolgui_node, joy_to_cmd_vel, web
ROV firmware/thruster_statusnereo_interfaces/ThrusterStatuses

Services

ServiceTypeProviderUsed byEffect
barometer_reset_referencestd_srvs/Triggerbar_publisherGUI "ZERO" buttonRe-zeroes the surface pressure → depth = 0 at current spot
imu_reset_referencestd_srvs/Triggerimu_publisherGUI "ZERO RPY" buttonStores current orientation as offset → roll/pitch/yaw = 0 at current attitude

Safety arbitration (safety_node)

ConditionOutput
Controller active (last msg < 0.5 s)Controller command forwarded, web ignored
Only web activeWeb command forwarded
Web silent 0–1 sLast web command held
Web silent 1–1.5 sCommand ramped to zero
Web silent > 1.5 sZero command sent
No source activeZero command sent

Architecture diagram

graph LR
subgraph RPI[rpi_ws — Raspberry Pi]
IMU[imu_publisher\nimu_reset_reference svc]
BAR[bar_publisher\nbarometer_reset_reference svc]
SONAR[sonar_node]
CAM[GStreamer cameras\nH264 UDP]
end
subgraph CTRL[gui_ws — Control Station]
GUI[gui_node\nROSQmlBridge / SonarBridge / ControllerBridge]
JOY[joy_to_cmd_vel]
SAFETY[safety_node]
WEB[web_server_node\nport 8080]
RB[rosbridge_websocket\nport 9090]
end
subgraph TUNE[ros2_controller_tuning_aid]
NCN[nereo_controller_node]
end
subgraph SIM[Simulators — local testing]
ROVSIM[rov_sim_node]
SONARSIM[sonar_sim_node]
end
PHONE[Phone / tablet\nbrowser]
IMU -.->|imu_data| GUI
IMU -.->|imu_data| NCN
BAR -.->|barometer_pressure| GUI
BAR -.->|barometer_pressure| NCN
BAR -.->|barometer_depth_salt| GUI
SONAR -.->|sonar/*| GUI
CAM -.->|RTP/UDP 5001-5003| GUI
JOY -->|nereo_cmd_vel_joy| SAFETY
JOY -->|nereo_cmd_vel_no_fb| NCN
NCN -->|nereo_cmd_vel| SAFETY
PHONE -->|web_cmd_vel via :9090| RB
RB --> SAFETY
WEB -->|UI :8080| PHONE
SAFETY -->|nereo_cmd_vel| ROV[(ROV firmware\nmicroROS)]
JOY -->|set_arm_mode| ROV
ROV -->|rov_armed| GUI
ROV -->|rov_armed| JOY
GUI -.->|set_parameters| NCN
NCN -.->|/controller/*| GUI
Loading

GUI features

Main dashboard

  • 3× live H.264/UDP camera streams (main + 2 secondary)
  • IMU orientation widget (yaw / pitch / roll)
  • Depth, temperature, ROV arm/connection status
  • ZERO button → re-zeroes depth at the surface (calls barometer_reset_reference)
  • ZERO RPY button → re-zeroes attitude when the ROV is sitting level (calls imu_reset_reference)
  • Ctrl+Q → opens a popup with a QR code + URL (http://<ip>:8080) for the web controller. The IP is recomputed every time the shortcut is pressed, so it works even if you connect to Wi-Fi after launching the GUI.

Controller Tuner window (TUNER button)

  • Selects control mode (0 passthrough / 1 PID / 2 PID anti-windup / 3 CS)
  • Edits kp/ki/kd for depth/roll/pitch/yaw
  • Manual setpoint toggles per axis
  • Setpoint inputs in display units: depth in metres, roll/pitch/yaw in degrees. Conversion to controller units (Pa, rad) is done inside QML (density ρ = 1025 kg/m³, salt water — matches barometer_depth_salt). The controller-side parameters stay in their native units.
  • Full CS controller section (kx, ki, heave/angle limits)
  • Live telemetry: /controller/setpoints, /controller/errors, /controller/pid_terms

Sonar Viewer (SONAR button)

Waterfall + A-scan rendered into a QQuickImageProvider, confidence threshold filter.

Control Panel (CONTROL PANEL button)

Manual arming / mode toggles independent from the joystick.


Web controller (phone / tablet)

Open from any device on the same network:

  • Controller: http://<workstation-ip>:8080
  • ROV simulator: http://<workstation-ip>:8080/sim.html

The fastest way to get the URL on a phone: focus the GUI and press Ctrl+Q — a QR code with the URL appears.

The web client publishes on /web_cmd_vel. safety_node gives priority to the physical controller when both are active.

The web_server_node is robust to GUI shutdown: it installs a SIGTERM handler, closes the HTTP socket, and joins its serving thread on destroy_node(), so port 8080 is released cleanly and the launch file can be restarted without pkill.


Local testing (no ROV hardware)

# Terminal 1 — full ROV simulator (IMU, barometer, joystick, arm, 3 GStreamer test streams)
ros2 run gui_pkg rov_sim_node
# (without GStreamer: --ros-args -p simulate_cameras:=false)# Terminal 2 — sonar simulator
ros2 run sonar_pkg sonar_sim_node
# Terminal 3 — workstation stack
ros2 launch gui_pkg workstation.launch.py

Open http://localhost:8080 for the web controller and http://localhost:8080/sim.html for the top-down ROV simulator.

Manual test helpers

  • gui_ws/src/gui_pkg/test/cam_test.sh — three independent GStreamer test streams
  • ros2 run joystick_pkg rov_cmd_monitor — terminal dashboard of the live 6-DOF command vector

Unit tests

Inside unit_tests/ each subfolder is a CMake project for stdout-level debugging:

cd unit_tests/<name>
cmake .&& make
./<name>

Troubleshooting

SymptomCauseFix
Address already in use :8080 at relaunchOld web_server_node still runningShould not happen anymore (the node releases the socket on SIGTERM); if it does, pkill -f web_server_node
GUI starts but no nereo_controller_noderos2_controller_tuning_aid not built or not at sibling pathCheck ~/Documents/PoliTOcean/RD/ros2_controller_tuning_aid/install/ exists
imu_reset_reference service not availablerpi_ws not rebuilt after the new service was addedOn the Pi: colcon build --packages-select nereo_sensors_pkg
Ctrl+Q shows "Nessuna rete"No default-route interface upConnect to Wi-Fi / Ethernet, press Ctrl+Q again (re-evaluated each time)
Ctrl+Q shows "Pacchetto python3-qrcode mancante"Missing system packagesudo apt install python3-qrcode python3-pil
Tuner setpoint value looks wrong after RELOADController still has old unit valueThe GUI now converts deg↔rad and m↔Pa; if you set parameters from CLI in raw units, RELOAD will display them converted

About

No description, website, or topics provided.

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Nereo PoliTOcean

Code for the Nereo ROV by PoliTOcean.

The project is split into two ROS 2 (Humble) workspaces:

WorkspaceSidePurpose
gui_wsControl station (PC)GUI, joystick, web controller, safety arbitration
rpi_wsRaspberry Pi (on the ROV)IMU / barometer / sonar acquisition

The PID/state-space controller lives in a separate repository, ros2_controller_tuning_aid, and is wired in by the workstation.launch.py of gui_pkg.


Table of contents

  1. Installation
  2. Build
  3. Run
  4. Workspace structure
  5. ROS 2 node and topic map
  6. GUI features
  7. Web controller (phone / tablet)
  8. Local testing (no ROV hardware)
  9. Troubleshooting

Installation

0. Clone the repos

nereo_interfaces is a git submodule of this repo. Clone with --recurse-submodules:

git clone --recurse-submodules https://github.com/PoliTOcean/nereo_ros2_code.git

If already cloned without it:

git submodule update --init

Clone the controller repo next to this one (paths in workstation.launch.py assume they are siblings under ~/Documents/PoliTOcean/RD/):

git clone https://github.com/PoliTOcean/ros2_controller_tuning_aid.git

1. System dependencies

sudo apt update
sudo apt install \
ros-humble-rosbridge-suite \
ros-humble-tf-transformations \
gstreamer1.0-plugins-good gstreamer1.0-plugins-base gstreamer1.0-tools \
python3-qrcode python3-pil
PackageUsed byWhy it's needed
ros-humble-rosbridge-suiteweb_pkgWebSocket bridge for the web controller (port 9090)
ros-humble-tf-transformationsgui_pkgQuaternion → RPY conversion in the GUI
gstreamer1.0-plugins-*gui_pkgLive H.264/UDP video from the ROV cameras
python3-qrcode, python3-pilgui_pkgGenerates the QR code shown on Ctrl+Q for the web controller URL

2. PyQt6 with QML bindings (pip)

The apt python3-pyqt6 package ships without QtQml/QtQuick. Install via pip:

pip install PyQt6

3. Register the custom rosdep sources

A few dependencies (PyQt6, bluerobotics-ping) are not in the default rosdep index. The local override sits at rosdep.yaml in this repo:

echo"yaml file://$(pwd)/rosdep.yaml"| sudo tee /etc/ros/rosdep/sources.list.d/nereo.list
rosdep update

4. Install per-package dependencies

# Control station (PC)cd gui_ws
rosdep install --from-paths src --ignore-src -r -y
# Raspberry Picd ../rpi_ws
rosdep install --from-paths src --ignore-src -r -y

Build

# Controller (must be built first — gui_ws launch file pulls it in via AMENT_PREFIX_PATH)cd ../ros2_controller_tuning_aid && colcon build &&source install/setup.zsh
# Control stationcd ../nereo_ros2_code/gui_ws && colcon build &&source install/setup.zsh
# Raspberry Picd ../rpi_ws && colcon build &&source install/setup.zsh

Run

A single launch file starts everything on the workstation: joystick driver, command translator, GUI, safety arbiter, rosbridge WebSocket (port 9090), web controller server (port 8080), and the controller node.

ros2 launch gui_pkg workstation.launch.py

The launch file auto-prepends the ros2_controller_tuning_aid install dir to AMENT_PREFIX_PATH, so you do not need to source that overlay manually.

Launch arguments

ArgumentDefaultDescription
device/dev/input/js0Joystick device path
deadzone0.05Joystick deadzone
max_steps10Quantization steps for axes
btn_arm8Arm/disarm button index
btn_mode6Direct ↔ Controller mode toggle button index
control_mode0Initial controller mode (0=passthrough, 1=PID, 2=PID-AW, 3=CS)

Physical controller mapping

InputActionXbox One S (default)DS5
Left stick Y/XSurge / Sway
Right stick Y/XHeave / Yaw
D-pad up/downPitch trim
D-pad left/rightRoll trim
Arm/DisarmToggle armXbox (btn 8)PS (btn 10)
Mode toggleDirect ↔ ControllerView (btn 6)Share (btn 8)

Examples:

# DS5 button mapping
ros2 launch gui_pkg workstation.launch.py btn_arm:=10 btn_mode:=8
# Different joystick device
ros2 launch gui_pkg workstation.launch.py device:=/dev/input/js1

Direct mode (red lock icon in GUI): commands go to /nereo_cmd_vel_joysafety_node → ROV. Controller mode (blue lock icon in GUI): commands go to /nereo_cmd_vel_no_fb → controller node → safety_node → ROV.


Workspace structure

nereo_ros2_code/
├── gui_ws/ # Workstation workspace
│ └── src/
│ ├── gui_pkg/ # QML/PyQt6 dashboard + joystick + rov simulator
│ │ ├── gui_pkg/
│ │ │ ├── gui_node.py # Main node + ROSQmlBridge / SonarBridge / ControllerBridge
│ │ │ ├── rov_sim_node.py # Full ROV simulator (IMU, barometer, cameras, joy)
│ │ │ └── qml/ # QML UI
│ │ │ ├── main.qml
│ │ │ └── components/
│ │ │ ├── ControllerTunerWindow.qml # PID/CS tuning UI (deg/m setpoints)
│ │ │ ├── ControlPanelWindow.qml
│ │ │ ├── SonarWindow.qml
│ │ │ ├── Orientation2D.qml
│ │ │ └── VideoBox.qml
│ │ └── launch/
│ │ └── workstation.launch.py
│ ├── joystick_pkg/ # joy → CommandVelocity
│ ├── web_pkg/
│ │ ├── web_server_node.py # HTTP server on :8080
│ │ ├── safety_node.py # Arbitrates physical vs. web commands
│ │ └── static/ # Web controller UI assets
│ └── nereo_interfaces/ # CommandVelocity, ThrusterStatuses (submodule)
└── rpi_ws/ # Raspberry Pi workspace
└── src/
├── nereo_sensors_pkg/ # C++ — IMU (WT61P) + barometer (MS5837)
│ ├── src/imuPub.cpp # publishes /imu_data, service imu_reset_reference
│ ├── src/barPub.cpp # publishes /barometer_*, service barometer_reset_reference
│ └── include/nereo_sensors_pkg/
└── sonar_pkg/ # Python — Blue Robotics Ping1D

ROS 2 node and topic map

Nodes

NodePackageSideRole
imu_publishernereo_sensors_pkgRPiWT61P over I2C → /imu_data; service imu_reset_reference
bar_publishernereo_sensors_pkgRPiMS5837 over I2C → /barometer_*; service barometer_reset_reference
sonar_nodesonar_pkgRPiPing1D over serial → /sonar/*
gui_nodegui_pkgPCQML dashboard, telemetry fusion, QR code, tuner
joy_to_cmd_veljoystick_pkgPCJoystick → CommandVelocity
safety_nodeweb_pkgPCArbitrates controller + web commands → /nereo_cmd_vel
web_server_nodeweb_pkgPCServes the web controller on :8080
rosbridge_websocketrosbridge_serverPCWebSocket bridge for the web client (:9090)
nereo_controller_nodenereo_controller_nodePCPID / state-space controller (separate repo)
rov_sim_nodegui_pkgPCLocal ROV simulator (no hardware needed)
sonar_sim_nodesonar_pkgPCSynthetic sonar data

Topics

PublisherTopicTypeConsumers
imu_publisherimu_datasensor_msgs/Imugui_node, nereo_controller_node
imu_publisherimu_diagnosticdiagnostic_msgs/DiagnosticArray
bar_publisherbarometer_pressuresensor_msgs/FluidPressuregui_node, nereo_controller_node
bar_publisherbarometer_depth_saltstd_msgs/Float32gui_node (depth widget)
bar_publisherbarometer_depth_freshstd_msgs/Float32
bar_publisherbarometer_temperaturesensor_msgs/Temperaturegui_node
bar_publisherbarometer_diagnosticdiagnostic_msgs/DiagnosticArray
sonar_node / sonar_sim_nodesonar/distancestd_msgs/Float32gui_node
sonar_node / sonar_sim_nodesonar/confidencestd_msgs/Int32gui_node
sonar_node / sonar_sim_nodesonar/profilestd_msgs/Float32MultiArraygui_node
joy_to_cmd_vel/nereo_cmd_vel_joynereo_interfaces/CommandVelocitysafety_node
joy_to_cmd_vel/nereo_cmd_vel_no_fbnereo_interfaces/CommandVelocitynereo_controller_node
joy_to_cmd_vel/joy_control_activestd_msgs/Boolgui_node, web
joy_to_cmd_vel / web/set_arm_modestd_msgs/BoolROV firmware
nereo_controller_node/nereo_cmd_vel (controller mode)nereo_interfaces/CommandVelocitysafety_node
nereo_controller_node/controller/setpointsstd_msgs/Float64MultiArraytuner GUI
nereo_controller_node/controller/errorsstd_msgs/Float64MultiArraytuner GUI
nereo_controller_node/controller/pid_termsstd_msgs/Float64MultiArraytuner GUI
web client/web_cmd_velnereo_interfaces/CommandVelocitysafety_node
safety_node/nereo_cmd_vel (direct mode)nereo_interfaces/CommandVelocityROV firmware
ROV firmware/rov_armedstd_msgs/Boolgui_node, joy_to_cmd_vel, web
ROV firmware/thruster_statusnereo_interfaces/ThrusterStatuses

Services

ServiceTypeProviderUsed byEffect
barometer_reset_referencestd_srvs/Triggerbar_publisherGUI "ZERO" buttonRe-zeroes the surface pressure → depth = 0 at current spot
imu_reset_referencestd_srvs/Triggerimu_publisherGUI "ZERO RPY" buttonStores current orientation as offset → roll/pitch/yaw = 0 at current attitude

Safety arbitration (safety_node)

ConditionOutput
Controller active (last msg < 0.5 s)Controller command forwarded, web ignored
Only web activeWeb command forwarded
Web silent 0–1 sLast web command held
Web silent 1–1.5 sCommand ramped to zero
Web silent > 1.5 sZero command sent
No source activeZero command sent

Architecture diagram

graph LR
subgraph RPI[rpi_ws — Raspberry Pi]
IMU[imu_publisher\nimu_reset_reference svc]
BAR[bar_publisher\nbarometer_reset_reference svc]
SONAR[sonar_node]
CAM[GStreamer cameras\nH264 UDP]
end
subgraph CTRL[gui_ws — Control Station]
GUI[gui_node\nROSQmlBridge / SonarBridge / ControllerBridge]
JOY[joy_to_cmd_vel]
SAFETY[safety_node]
WEB[web_server_node\nport 8080]
RB[rosbridge_websocket\nport 9090]
end
subgraph TUNE[ros2_controller_tuning_aid]
NCN[nereo_controller_node]
end
subgraph SIM[Simulators — local testing]
ROVSIM[rov_sim_node]
SONARSIM[sonar_sim_node]
end
PHONE[Phone / tablet\nbrowser]
IMU -.->|imu_data| GUI
IMU -.->|imu_data| NCN
BAR -.->|barometer_pressure| GUI
BAR -.->|barometer_pressure| NCN
BAR -.->|barometer_depth_salt| GUI
SONAR -.->|sonar/*| GUI
CAM -.->|RTP/UDP 5001-5003| GUI
JOY -->|nereo_cmd_vel_joy| SAFETY
JOY -->|nereo_cmd_vel_no_fb| NCN
NCN -->|nereo_cmd_vel| SAFETY
PHONE -->|web_cmd_vel via :9090| RB
RB --> SAFETY
WEB -->|UI :8080| PHONE
SAFETY -->|nereo_cmd_vel| ROV[(ROV firmware\nmicroROS)]
JOY -->|set_arm_mode| ROV
ROV -->|rov_armed| GUI
ROV -->|rov_armed| JOY
GUI -.->|set_parameters| NCN
NCN -.->|/controller/*| GUI
Loading

GUI features

Main dashboard

  • 3× live H.264/UDP camera streams (main + 2 secondary)
  • IMU orientation widget (yaw / pitch / roll)
  • Depth, temperature, ROV arm/connection status
  • ZERO button → re-zeroes depth at the surface (calls barometer_reset_reference)
  • ZERO RPY button → re-zeroes attitude when the ROV is sitting level (calls imu_reset_reference)
  • Ctrl+Q → opens a popup with a QR code + URL (http://<ip>:8080) for the web controller. The IP is recomputed every time the shortcut is pressed, so it works even if you connect to Wi-Fi after launching the GUI.

Controller Tuner window (TUNER button)

  • Selects control mode (0 passthrough / 1 PID / 2 PID anti-windup / 3 CS)
  • Edits kp/ki/kd for depth/roll/pitch/yaw
  • Manual setpoint toggles per axis
  • Setpoint inputs in display units: depth in metres, roll/pitch/yaw in degrees. Conversion to controller units (Pa, rad) is done inside QML (density ρ = 1025 kg/m³, salt water — matches barometer_depth_salt). The controller-side parameters stay in their native units.
  • Full CS controller section (kx, ki, heave/angle limits)
  • Live telemetry: /controller/setpoints, /controller/errors, /controller/pid_terms

Sonar Viewer (SONAR button)

Waterfall + A-scan rendered into a QQuickImageProvider, confidence threshold filter.

Control Panel (CONTROL PANEL button)

Manual arming / mode toggles independent from the joystick.


Web controller (phone / tablet)

Open from any device on the same network:

  • Controller: http://<workstation-ip>:8080
  • ROV simulator: http://<workstation-ip>:8080/sim.html

The fastest way to get the URL on a phone: focus the GUI and press Ctrl+Q — a QR code with the URL appears.

The web client publishes on /web_cmd_vel. safety_node gives priority to the physical controller when both are active.

The web_server_node is robust to GUI shutdown: it installs a SIGTERM handler, closes the HTTP socket, and joins its serving thread on destroy_node(), so port 8080 is released cleanly and the launch file can be restarted without pkill.


Local testing (no ROV hardware)

# Terminal 1 — full ROV simulator (IMU, barometer, joystick, arm, 3 GStreamer test streams)
ros2 run gui_pkg rov_sim_node
# (without GStreamer: --ros-args -p simulate_cameras:=false)# Terminal 2 — sonar simulator
ros2 run sonar_pkg sonar_sim_node
# Terminal 3 — workstation stack
ros2 launch gui_pkg workstation.launch.py

Open http://localhost:8080 for the web controller and http://localhost:8080/sim.html for the top-down ROV simulator.

Manual test helpers

  • gui_ws/src/gui_pkg/test/cam_test.sh — three independent GStreamer test streams
  • ros2 run joystick_pkg rov_cmd_monitor — terminal dashboard of the live 6-DOF command vector

Unit tests

Inside unit_tests/ each subfolder is a CMake project for stdout-level debugging:

cd unit_tests/<name>
cmake .&& make
./<name>

Troubleshooting

SymptomCauseFix
Address already in use :8080 at relaunchOld web_server_node still runningShould not happen anymore (the node releases the socket on SIGTERM); if it does, pkill -f web_server_node
GUI starts but no nereo_controller_noderos2_controller_tuning_aid not built or not at sibling pathCheck ~/Documents/PoliTOcean/RD/ros2_controller_tuning_aid/install/ exists
imu_reset_reference service not availablerpi_ws not rebuilt after the new service was addedOn the Pi: colcon build --packages-select nereo_sensors_pkg
Ctrl+Q shows "Nessuna rete"No default-route interface upConnect to Wi-Fi / Ethernet, press Ctrl+Q again (re-evaluated each time)
Ctrl+Q shows "Pacchetto python3-qrcode mancante"Missing system packagesudo apt install python3-qrcode python3-pil
Tuner setpoint value looks wrong after RELOADController still has old unit valueThe GUI now converts deg↔rad and m↔Pa; if you set parameters from CLI in raw units, RELOAD will display them converted

About

No description, website, or topics provided.

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

Nereo PoliTOcean

Code for the Nereo ROV by PoliTOcean.

The project is split into two ROS 2 (Humble) workspaces:

WorkspaceSidePurpose
gui_wsControl station (PC)GUI, joystick, web controller, safety arbitration
rpi_wsRaspberry Pi (on the ROV)IMU / barometer / sonar acquisition

The PID/state-space controller lives in a separate repository, ros2_controller_tuning_aid, and is wired in by the workstation.launch.py of gui_pkg.


Table of contents

  1. Installation
  2. Build
  3. Run
  4. Workspace structure
  5. ROS 2 node and topic map
  6. GUI features
  7. Web controller (phone / tablet)
  8. Local testing (no ROV hardware)
  9. Troubleshooting

Installation

0. Clone the repos

nereo_interfaces is a git submodule of this repo. Clone with --recurse-submodules:

git clone --recurse-submodules https://github.com/PoliTOcean/nereo_ros2_code.git

If already cloned without it:

git submodule update --init

Clone the controller repo next to this one (paths in workstation.launch.py assume they are siblings under ~/Documents/PoliTOcean/RD/):

git clone https://github.com/PoliTOcean/ros2_controller_tuning_aid.git

1. System dependencies

sudo apt update
sudo apt install \
ros-humble-rosbridge-suite \
ros-humble-tf-transformations \
gstreamer1.0-plugins-good gstreamer1.0-plugins-base gstreamer1.0-tools \
python3-qrcode python3-pil
PackageUsed byWhy it's needed
ros-humble-rosbridge-suiteweb_pkgWebSocket bridge for the web controller (port 9090)
ros-humble-tf-transformationsgui_pkgQuaternion → RPY conversion in the GUI
gstreamer1.0-plugins-*gui_pkgLive H.264/UDP video from the ROV cameras
python3-qrcode, python3-pilgui_pkgGenerates the QR code shown on Ctrl+Q for the web controller URL

2. PyQt6 with QML bindings (pip)

The apt python3-pyqt6 package ships without QtQml/QtQuick. Install via pip:

pip install PyQt6

3. Register the custom rosdep sources

A few dependencies (PyQt6, bluerobotics-ping) are not in the default rosdep index. The local override sits at rosdep.yaml in this repo:

echo"yaml file://$(pwd)/rosdep.yaml"| sudo tee /etc/ros/rosdep/sources.list.d/nereo.list
rosdep update

4. Install per-package dependencies

# Control station (PC)cd gui_ws
rosdep install --from-paths src --ignore-src -r -y
# Raspberry Picd ../rpi_ws
rosdep install --from-paths src --ignore-src -r -y

Build

# Controller (must be built first — gui_ws launch file pulls it in via AMENT_PREFIX_PATH)cd ../ros2_controller_tuning_aid && colcon build &&source install/setup.zsh
# Control stationcd ../nereo_ros2_code/gui_ws && colcon build &&source install/setup.zsh
# Raspberry Picd ../rpi_ws && colcon build &&source install/setup.zsh

Run

A single launch file starts everything on the workstation: joystick driver, command translator, GUI, safety arbiter, rosbridge WebSocket (port 9090), web controller server (port 8080), and the controller node.

ros2 launch gui_pkg workstation.launch.py

The launch file auto-prepends the ros2_controller_tuning_aid install dir to AMENT_PREFIX_PATH, so you do not need to source that overlay manually.

Launch arguments

ArgumentDefaultDescription
device/dev/input/js0Joystick device path
deadzone0.05Joystick deadzone
max_steps10Quantization steps for axes
btn_arm8Arm/disarm button index
btn_mode6Direct ↔ Controller mode toggle button index
control_mode0Initial controller mode (0=passthrough, 1=PID, 2=PID-AW, 3=CS)

Physical controller mapping

InputActionXbox One S (default)DS5
Left stick Y/XSurge / Sway
Right stick Y/XHeave / Yaw
D-pad up/downPitch trim
D-pad left/rightRoll trim
Arm/DisarmToggle armXbox (btn 8)PS (btn 10)
Mode toggleDirect ↔ ControllerView (btn 6)Share (btn 8)

Examples:

# DS5 button mapping
ros2 launch gui_pkg workstation.launch.py btn_arm:=10 btn_mode:=8
# Different joystick device
ros2 launch gui_pkg workstation.launch.py device:=/dev/input/js1

Direct mode (red lock icon in GUI): commands go to /nereo_cmd_vel_joysafety_node → ROV. Controller mode (blue lock icon in GUI): commands go to /nereo_cmd_vel_no_fb → controller node → safety_node → ROV.


Workspace structure

nereo_ros2_code/
├── gui_ws/ # Workstation workspace
│ └── src/
│ ├── gui_pkg/ # QML/PyQt6 dashboard + joystick + rov simulator
│ │ ├── gui_pkg/
│ │ │ ├── gui_node.py # Main node + ROSQmlBridge / SonarBridge / ControllerBridge
│ │ │ ├── rov_sim_node.py # Full ROV simulator (IMU, barometer, cameras, joy)
│ │ │ └── qml/ # QML UI
│ │ │ ├── main.qml
│ │ │ └── components/
│ │ │ ├── ControllerTunerWindow.qml # PID/CS tuning UI (deg/m setpoints)
│ │ │ ├── ControlPanelWindow.qml
│ │ │ ├── SonarWindow.qml
│ │ │ ├── Orientation2D.qml
│ │ │ └── VideoBox.qml
│ │ └── launch/
│ │ └── workstation.launch.py
│ ├── joystick_pkg/ # joy → CommandVelocity
│ ├── web_pkg/
│ │ ├── web_server_node.py # HTTP server on :8080
│ │ ├── safety_node.py # Arbitrates physical vs. web commands
│ │ └── static/ # Web controller UI assets
│ └── nereo_interfaces/ # CommandVelocity, ThrusterStatuses (submodule)
└── rpi_ws/ # Raspberry Pi workspace
└── src/
├── nereo_sensors_pkg/ # C++ — IMU (WT61P) + barometer (MS5837)
│ ├── src/imuPub.cpp # publishes /imu_data, service imu_reset_reference
│ ├── src/barPub.cpp # publishes /barometer_*, service barometer_reset_reference
│ └── include/nereo_sensors_pkg/
└── sonar_pkg/ # Python — Blue Robotics Ping1D

ROS 2 node and topic map

Nodes

NodePackageSideRole
imu_publishernereo_sensors_pkgRPiWT61P over I2C → /imu_data; service imu_reset_reference
bar_publishernereo_sensors_pkgRPiMS5837 over I2C → /barometer_*; service barometer_reset_reference
sonar_nodesonar_pkgRPiPing1D over serial → /sonar/*
gui_nodegui_pkgPCQML dashboard, telemetry fusion, QR code, tuner
joy_to_cmd_veljoystick_pkgPCJoystick → CommandVelocity
safety_nodeweb_pkgPCArbitrates controller + web commands → /nereo_cmd_vel
web_server_nodeweb_pkgPCServes the web controller on :8080
rosbridge_websocketrosbridge_serverPCWebSocket bridge for the web client (:9090)
nereo_controller_nodenereo_controller_nodePCPID / state-space controller (separate repo)
rov_sim_nodegui_pkgPCLocal ROV simulator (no hardware needed)
sonar_sim_nodesonar_pkgPCSynthetic sonar data

Topics

PublisherTopicTypeConsumers
imu_publisherimu_datasensor_msgs/Imugui_node, nereo_controller_node
imu_publisherimu_diagnosticdiagnostic_msgs/DiagnosticArray
bar_publisherbarometer_pressuresensor_msgs/FluidPressuregui_node, nereo_controller_node
bar_publisherbarometer_depth_saltstd_msgs/Float32gui_node (depth widget)
bar_publisherbarometer_depth_freshstd_msgs/Float32
bar_publisherbarometer_temperaturesensor_msgs/Temperaturegui_node
bar_publisherbarometer_diagnosticdiagnostic_msgs/DiagnosticArray
sonar_node / sonar_sim_nodesonar/distancestd_msgs/Float32gui_node
sonar_node / sonar_sim_nodesonar/confidencestd_msgs/Int32gui_node
sonar_node / sonar_sim_nodesonar/profilestd_msgs/Float32MultiArraygui_node
joy_to_cmd_vel/nereo_cmd_vel_joynereo_interfaces/CommandVelocitysafety_node
joy_to_cmd_vel/nereo_cmd_vel_no_fbnereo_interfaces/CommandVelocitynereo_controller_node
joy_to_cmd_vel/joy_control_activestd_msgs/Boolgui_node, web
joy_to_cmd_vel / web/set_arm_modestd_msgs/BoolROV firmware
nereo_controller_node/nereo_cmd_vel (controller mode)nereo_interfaces/CommandVelocitysafety_node
nereo_controller_node/controller/setpointsstd_msgs/Float64MultiArraytuner GUI
nereo_controller_node/controller/errorsstd_msgs/Float64MultiArraytuner GUI
nereo_controller_node/controller/pid_termsstd_msgs/Float64MultiArraytuner GUI
web client/web_cmd_velnereo_interfaces/CommandVelocitysafety_node
safety_node/nereo_cmd_vel (direct mode)nereo_interfaces/CommandVelocityROV firmware
ROV firmware/rov_armedstd_msgs/Boolgui_node, joy_to_cmd_vel, web
ROV firmware/thruster_statusnereo_interfaces/ThrusterStatuses

Services

ServiceTypeProviderUsed byEffect
barometer_reset_referencestd_srvs/Triggerbar_publisherGUI "ZERO" buttonRe-zeroes the surface pressure → depth = 0 at current spot
imu_reset_referencestd_srvs/Triggerimu_publisherGUI "ZERO RPY" buttonStores current orientation as offset → roll/pitch/yaw = 0 at current attitude

Safety arbitration (safety_node)

ConditionOutput
Controller active (last msg < 0.5 s)Controller command forwarded, web ignored
Only web activeWeb command forwarded
Web silent 0–1 sLast web command held
Web silent 1–1.5 sCommand ramped to zero
Web silent > 1.5 sZero command sent
No source activeZero command sent

Architecture diagram

graph LR
subgraph RPI[rpi_ws — Raspberry Pi]
IMU[imu_publisher\nimu_reset_reference svc]
BAR[bar_publisher\nbarometer_reset_reference svc]
SONAR[sonar_node]
CAM[GStreamer cameras\nH264 UDP]
end
subgraph CTRL[gui_ws — Control Station]
GUI[gui_node\nROSQmlBridge / SonarBridge / ControllerBridge]
JOY[joy_to_cmd_vel]
SAFETY[safety_node]
WEB[web_server_node\nport 8080]
RB[rosbridge_websocket\nport 9090]
end
subgraph TUNE[ros2_controller_tuning_aid]
NCN[nereo_controller_node]
end
subgraph SIM[Simulators — local testing]
ROVSIM[rov_sim_node]
SONARSIM[sonar_sim_node]
end
PHONE[Phone / tablet\nbrowser]
IMU -.->|imu_data| GUI
IMU -.->|imu_data| NCN
BAR -.->|barometer_pressure| GUI
BAR -.->|barometer_pressure| NCN
BAR -.->|barometer_depth_salt| GUI
SONAR -.->|sonar/*| GUI
CAM -.->|RTP/UDP 5001-5003| GUI
JOY -->|nereo_cmd_vel_joy| SAFETY
JOY -->|nereo_cmd_vel_no_fb| NCN
NCN -->|nereo_cmd_vel| SAFETY
PHONE -->|web_cmd_vel via :9090| RB
RB --> SAFETY
WEB -->|UI :8080| PHONE
SAFETY -->|nereo_cmd_vel| ROV[(ROV firmware\nmicroROS)]
JOY -->|set_arm_mode| ROV
ROV -->|rov_armed| GUI
ROV -->|rov_armed| JOY
GUI -.->|set_parameters| NCN
NCN -.->|/controller/*| GUI
Loading

GUI features

Main dashboard

  • 3× live H.264/UDP camera streams (main + 2 secondary)
  • IMU orientation widget (yaw / pitch / roll)
  • Depth, temperature, ROV arm/connection status
  • ZERO button → re-zeroes depth at the surface (calls barometer_reset_reference)
  • ZERO RPY button → re-zeroes attitude when the ROV is sitting level (calls imu_reset_reference)
  • Ctrl+Q → opens a popup with a QR code + URL (http://<ip>:8080) for the web controller. The IP is recomputed every time the shortcut is pressed, so it works even if you connect to Wi-Fi after launching the GUI.

Controller Tuner window (TUNER button)

  • Selects control mode (0 passthrough / 1 PID / 2 PID anti-windup / 3 CS)
  • Edits kp/ki/kd for depth/roll/pitch/yaw
  • Manual setpoint toggles per axis
  • Setpoint inputs in display units: depth in metres, roll/pitch/yaw in degrees. Conversion to controller units (Pa, rad) is done inside QML (density ρ = 1025 kg/m³, salt water — matches barometer_depth_salt). The controller-side parameters stay in their native units.
  • Full CS controller section (kx, ki, heave/angle limits)
  • Live telemetry: /controller/setpoints, /controller/errors, /controller/pid_terms

Sonar Viewer (SONAR button)

Waterfall + A-scan rendered into a QQuickImageProvider, confidence threshold filter.

Control Panel (CONTROL PANEL button)

Manual arming / mode toggles independent from the joystick.


Web controller (phone / tablet)

Open from any device on the same network:

  • Controller: http://<workstation-ip>:8080
  • ROV simulator: http://<workstation-ip>:8080/sim.html

The fastest way to get the URL on a phone: focus the GUI and press Ctrl+Q — a QR code with the URL appears.

The web client publishes on /web_cmd_vel. safety_node gives priority to the physical controller when both are active.

The web_server_node is robust to GUI shutdown: it installs a SIGTERM handler, closes the HTTP socket, and joins its serving thread on destroy_node(), so port 8080 is released cleanly and the launch file can be restarted without pkill.


Local testing (no ROV hardware)

# Terminal 1 — full ROV simulator (IMU, barometer, joystick, arm, 3 GStreamer test streams)
ros2 run gui_pkg rov_sim_node
# (without GStreamer: --ros-args -p simulate_cameras:=false)# Terminal 2 — sonar simulator
ros2 run sonar_pkg sonar_sim_node
# Terminal 3 — workstation stack
ros2 launch gui_pkg workstation.launch.py

Open http://localhost:8080 for the web controller and http://localhost:8080/sim.html for the top-down ROV simulator.

Manual test helpers

  • gui_ws/src/gui_pkg/test/cam_test.sh — three independent GStreamer test streams
  • ros2 run joystick_pkg rov_cmd_monitor — terminal dashboard of the live 6-DOF command vector

Unit tests

Inside unit_tests/ each subfolder is a CMake project for stdout-level debugging:

cd unit_tests/<name>
cmake .&& make
./<name>

Troubleshooting

SymptomCauseFix
Address already in use :8080 at relaunchOld web_server_node still runningShould not happen anymore (the node releases the socket on SIGTERM); if it does, pkill -f web_server_node
GUI starts but no nereo_controller_noderos2_controller_tuning_aid not built or not at sibling pathCheck ~/Documents/PoliTOcean/RD/ros2_controller_tuning_aid/install/ exists
imu_reset_reference service not availablerpi_ws not rebuilt after the new service was addedOn the Pi: colcon build --packages-select nereo_sensors_pkg
Ctrl+Q shows "Nessuna rete"No default-route interface upConnect to Wi-Fi / Ethernet, press Ctrl+Q again (re-evaluated each time)
Ctrl+Q shows "Pacchetto python3-qrcode mancante"Missing system packagesudo apt install python3-qrcode python3-pil
Tuner setpoint value looks wrong after RELOADController still has old unit valueThe GUI now converts deg↔rad and m↔Pa; if you set parameters from CLI in raw units, RELOAD will display them converted

About

No description, website, or topics provided.

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages