Files
argo/SUB Control

SUB Control

A deliberately small, low-bandwidth MAVLink controller for bench-testing an ArduSub vehicle over a USB serial connection. It sends no parameter-list request and no telemetry-stream request.

SUB Control interface

The desktop window uses a local HTML/CSS/JavaScript interface rendered by the Windows WebView. Python remains the only launcher and owns the MAVLink worker, arming, logging, output limiting, and parameter writes. It does not run a web server, open a browser tab, or require an internet connection.

The WebView is frameless and uses the compact title bar drawn by the interface. The vehicle rail includes a local STL viewer: choose Load STL to start with an orthographic top-view silhouette, then drag to orbit or use the mouse wheel to zoom. A cached STL copy is stored locally under the ignored runtime/ directory so the model can be restored on the next launch. STEP/STP CAD solids must first be tessellated/exported as STL because the embedded renderer intentionally does not include a full CAD kernel. Binary and ASCII STL faces are preserved in full; large assemblies are not rendered by skipping triangles. Imports are limited to 30 MB to keep startup and interactive orbiting responsive.

Safety first

  • Remove propellers for initial tests and restrain the vehicle.
  • Verify the ArduSub frame and SERVOx_FUNCTION, motor direction, ESC range, leak/failsafe, and emergency-stop arrangements before arming.
  • The application starts disarmed and limits pilot demand to 20% by default. Once armed, control movement is sent directly with no additional gate.
  • Closing or disconnecting sends neutral controls and a disarm request, but a physical power cutoff is still required.

The controls are pilot-demand axes, not direct PWM values. ArduSub performs the frame mixing and generates the configured ESC outputs. For ArduSub, MANUAL_CONTROL.z = 500 is neutral vertical thrust.

Run on Windows

  1. Install Python 3.11 or newer. A virtual environment installs packages, but it does not install Python itself.
  2. Open PowerShell in this folder and run ./run.ps1 (or right-click it and choose Run with PowerShell).
  3. Select the autopilot's COM port, then click Connect. Baud and control-rate settings are under the small ••• link button (normally 57600 baud / 20 Hz).
  4. Confirm ArduSub and a live heartbeat are shown.
  5. Click Set MANUAL, then ARM only when the bench is safe.
  6. Move the controls. Once armed, no additional enable button is needed.

run.ps1 automatically creates a private .venv on the first run, installs the versions declared in requirements.txt, and starts SUB Control. Later runs reuse the same environment and update missing dependencies when necessary. The .venv directory is intentionally ignored by Git: virtual environments contain machine-specific executables and can be recreated from requirements.txt.

Repository layout

The Gitea repository is named argo. SUB Control is a normal directory inside that repository containing this application. A Gitea Project is a planning board for issues and tasks; source code is not attached to a Project board. Git tracks the folder automatically because its files are committed on the repository's main branch.

Keyboard controls for SimpleROV-3: W/S forward/back, A/D yaw left/right, Q/E alternative yaw control, and R/F rise/dive. Releasing movement keys eases their controls back to neutral.

Three-thruster limitation

The common ArduSub SimpleROV-3 arrangement controls forward, yaw, and depth. It cannot independently control lateral, roll, or pitch. Accordingly, the main pad is mapped to forward/yaw, while the vertical slider controls depth thrust.

  • MAVLink GCS heartbeat: 1 Hz
  • MANUAL_CONTROL: selectable 10 or 20 Hz (default 20 Hz). Lower rates are intentionally excluded because they make short return ramps become abrupt steps at the flight controller.
  • No full parameter download
  • On connection, inherited bulk telemetry streams are stopped. Only SYS_STATUS at 1 Hz and SERVO_OUTPUT_RAW / RC_CHANNELS at 2 Hz are requested, in addition to the normal heartbeat and event-driven status text.
  • Arm/disarm uses MAV_CMD_COMPONENT_ARM_DISARM and displays COMMAND_ACK
  • Link health is based on received autopilot heartbeats
  • Battery voltage is read from SYS_STATUS at 1 Hz and shown as pack voltage, estimated per-cell voltage, and a voltage-only percentage. Cell count, capacity, C rating, full/empty cell voltages, and warning percentage are configurable and saved locally in battery_settings.json. Capacity consumed cannot be measured without a current sensor.
  • Servo configuration scans SERVO1_FUNCTION through SERVO16_FUNCTION and ArduSub's MOT_1_DIRECTION through MOT_12_DIRECTION one at a time, waiting for each response and retrying missed replies. A selected output can be assigned to Motor 1–12 or set unassigned. Motor direction is toggled with the corresponding MOT_n_DIRECTION value while disarmed; SERVOx_REVERSED is intentionally not used for Motor functions. It never downloads the full parameter list. The diagnostic scan also reads SERVO1–SERVO3 MIN/TRIM/MAX and ArduSub's joystick gain values into the local session log; these values remain read-only in the application.
  • Every session creates a local text log under logs/, including operator actions, outgoing manual-control values and commands, acknowledgements, status text, and all received MAVLink messages. This does not use ArduPilot's onboard logger or add any link traffic.

The car-style virtual joystick maps only to forward/reverse and continuous differential turning; it never sends lateral motion. The joystick and vertical slider ease back to center over 400 ms after mouse/key release, so neither can be left commanding motion. Focus loss, stale telemetry, disconnect, and disarm clear commanded inputs without easing. This is a neutral-command request, not proof that motors have stopped when the communication link is unavailable.

In addition, transmitted pilot demands have a small 4% input deadband and a 0.25-second full-range slew limit. This makes both acceleration and deceleration arrive at the flight controller as a sequence of stable intermediate values. Forward and yaw are normalized as a SimpleROV-3 motor pair before the output limit is applied, preventing their mixer contributions from adding beyond the selected horizontal limit.

ArduPilot may ignore manual input when MAV_GCS_SYSID does not match this application's system ID (default 255), or when MAV_OPTIONS does not allow MAVLink input. Those parameters must already be correctly configured.

Engage, confirmation, and interruption handling

The compact window opens at 1100 × 650. Empty sidebar areas drag the window; the STL viewer, buttons, and sliders are separate interactive regions. Output limit and Motor setup sit below the main controls. Recent activity retains the last three events, oldest at the top and newest emphasized at the bottom. Routine hold instructions are kept in tooltips; pending confirmations and safety warnings still appear above the controls. The full-width text control displays engage / disengage; disengage sends the same immediate disarm request described below.

  • Hold ENGAGE for 1.4 seconds (or hold F2). Early release, leaving the button, interrupted touch, and focus loss cancel the hold without sending arm.
  • ENGAGING… means a request is pending. A send result or accepted command ACK alone does not prove that the vehicle armed: a fresh autopilot heartbeat reporting the requested state is the final confirmation. A heartbeat can confirm state even if the ACK packet was lost.
  • DISARM is one tap or F3, with no long hold. While confirmation is pending, DISARMING… stays red and driving is disabled. Disarm can also interrupt a pending Engage request. No forced-arm/disarm bypass is used.
  • Confirmation timeout: 5 seconds in the backend (6-second UI fallback). Rejection and timeout remain visible. Late heartbeat updates still determine the displayed state; the UI never substitutes an animation state for telemetry.
  • A heartbeat older than 3 seconds is stale. A missing bridge reply after 1.5 seconds shows STATE UNKNOWN, preserving the last-known state without claiming disarm. Battery readings older than 5 seconds are marked stale.
  • The web frontend renews an input lease locally every 250 ms. If it stops for 1 second, the worker clears stored demands and sends neutral at its configured control rate. This adds no MAVLink stream or parameter download. Focus loss clears both captured touch pointers and queued input; reconnect never replays the old joystick position. Normal release still eases back to center.
  • Link loss cannot deliver a stop over a broken tether. Verify the vehicle's own failsafe and use independent physical power isolation for bench tests.

Regression checks (no hardware)

Run from this project folder after setting up the Python environment:

.venv\Scripts\python.exe -m unittest discover -s tests -v
node --test tests/control_ui.test.cjs

Node is needed only for the frontend regression tests, not to run the app. The tests use mocked transport and synthetic pointer events, including independent joystick/vertical pointers. Validate physical multi-touch on the intended screen and FC failsafes on a restrained, propeller-free setup before powered operation.