GUI-ROV HYDROSHIP — Input Mapping Reference
Version: 2.0 | Last Updated: 2026-07-30 | Wahana: BlueROV1 frame, 6 thruster 6-DoF (FRAME_CONFIG = 0)
Sumber kebenaran mapping default:
public/js/joystick-defaults.json. File itu dibaca oleh server (require) dan browser (fetch), jadi tidak ada lagi tabel default yang disalin ganda. Profil aktif operator tersimpan diserver/config/joystick-profile.jsondan menang atas default. Dokumen ini menjelaskan isi keduanya — kalau berbeda, file JSON yang benar.
Geser switch di belakang controller ke posisi X. Browser lalu melaporkannya
sebagai standard mapping Xbox-style.
| Jumlah axis | 4 saja — 0=Left X, 1=Left Y, 2=Right X, 3=Right Y |
| Trigger LT/RT | BUKAN axis. Keduanya adalah button analog nomor 6 dan 7 |
| Jumlah button | 17 (0–16), termasuk D-Pad di 12–15 dan Guide di 16 |
Inilah sebabnya grip analog memakai sumber virtual bernama triggers
(nilai = RT − LT) dan bukan "axis 4" — axis 4 tidak pernah ada di F310 X-Input.
| Input | Assigned | Stik fisik | Arah |
|---|---|---|---|
axis 0 |
Axis R → yaw |
Kiri ↔ | Kanan = putar CW |
axis 1 |
Axis Z → heave |
Kiri ↕ | Dorong ke atas = naik |
axis 2 |
Axis Y → sway |
Kanan ↔ | Kanan = geser kanan |
axis 3 |
Axis X → surge |
Kanan ↕ | Dorong ke atas = maju |
triggers |
Grip |
LT / RT | RT = menutup, LT = membuka (proporsional) |
Konvensi min/max: bila min > max, axis dibalik. Sumbu Y gamepad bernilai
negatif saat didorong ke atas, jadi heave dan surge memang dibalik.
| Btn | F310 | Aksi |
|---|---|---|
| 0 | A | grip_close |
| 1 | B | grip_open |
| 2 | X | grip_neutral |
| 3 | Y | — |
| 4 | LB | gain_dec |
| 5 | RB | gain_inc |
| 6 | LT | (grip analog) |
| 7 | RT | (grip analog) |
| 8 | Back | e_stop |
| 9 | Start | arm |
| 10 | LS klik | shift modifier |
| 11 | RS klik | — |
| 12 | D-Pad ↑ | mode_depth_hold |
| 13 | D-Pad ↓ | mode_manual |
| 14 | D-Pad ← | mode_stabilize |
| 15 | D-Pad → | light_toggle |
| 16 | Guide | — |
| Btn | F310 | Aksi |
|---|---|---|
| 8 | Back | e_stop |
| 9 | Start | disarm |
| sisanya | — |
e_stopsengaja ada di KEDUA layer. Penekanan shift yang tidak disengaja tidak boleh pernah menghilangkan tombol darurat.Shift memakai LS klik (bukan tombol muka) supaya A/B/X/Y tetap bebas. Profil lama memakai
shiftButton: 0— tombol A — sehingga A praktis tidak bisa dipakai.
no_function, arm, disarm, e_stop, mode_manual, mode_stabilize,
mode_depth_hold, mode_acro, grip_open, grip_close, grip_neutral,
light_toggle, gain_inc, gain_dec.
mode_acro sengaja tidak punya binding default: ke-16 tombol pad sudah
terpakai, dan menggeser binding yang sudah dihafal operator demi mode paling
berisiko adalah pertukaran yang buruk. Bind manual lewat halaman Joystick
kalau memang dibutuhkan saat trial.
Aksi lama (mount_tilt_*, mount_center, actuator1_*, lights_brighter/dimmer,
input_hold_set) sudah dihapus — wahana tidak punya hardware-nya dan
rov_agent.py tidak pernah punya handler-nya, jadi tombolnya diam-diam mati.
Profil tersimpan yang masih memuatnya dimigrasikan otomatis saat dimuat.
Empat parameter, semuanya bisa disetel di halaman Joystick dan ikut tersimpan
di profil. Implementasi murni ada di
public/js/axis-shaping.js.
raw(-1..1) → deadzone → expo → skala min/max(±1000) → × gain → rate limit → kirim
| Parameter | Default | Guna |
|---|---|---|
| Deadzone | 0.12 |
Drift stik di sekitar tengah jadi tepat 0. Memakai rescale, jadi keluaran mulai dari 0 di tepi zona — tidak melompat. |
| Expo | 0.35 |
Melandaikan bagian tengah untuk koreksi halus. Defleksi penuh tetap ±1000. |
| Gain | 6 langkah 25%…100%, mulai di 40% |
Membatasi thrust maksimum. Diubah saat operasi lewat LB/RB, tampil di HUD. Berlaku untuk keyboard juga. |
| Rate limit | 4000 /detik |
Meredam hentakan stik jadi lonjakan arus baterai. Sapuan penuh ≈ 0,5 detik. |
Preview di halaman Joystick memanggil fungsi yang sama persis dengan jalur
kirim (axisOutputFor), jadi angka di layar dijamin identik dengan yang diterima ROV.
Hanya aktif saat tab controller = Keyboard.
| Key | Axis | Key | Aksi | |
|---|---|---|---|---|
W / S |
surge maju / mundur | H |
Gripper OPEN | |
A / D |
sway kiri / kanan | G |
Gripper CLOSE | |
Q / E |
yaw CCW / CW | Space |
E-Stop | |
R / F |
heave naik / turun |
Besar langkah = CONFIG.CONTROL.KEY_AXIS_STEP (default 400) dikali gain.
Keyboard tunduk pada gerbang otoritas yang sama dengan gamepad: input ditolak
saat mode Autonomous atau E-Stop terkunci. Space dan H/G aktif tanpa
memandang tab controller.
Peta nama mode punya satu sumber kebenaran per bahasa —
rov_modes.PILOT_MODE_MAP di sisi Python dan
shared/rov-modes.js (PILOT_MODE_MAP,
ARDUSUB_MODE_TO_TAB, RISKY_ARDUSUB_MODES, ACRO_CONFIRM) di sisi JS —
diimpor langsung oleh public/js/app.js, bukan didefinisikan ulang di sana.
Tambah mode baru di kedua file.
| Tab GUI | D-Pad | Perintah | Mode Pixhawk |
|---|---|---|---|
| Manual | ↓ | pilot_mode="manual" |
MANUAL |
| Stabilize | ← | pilot_mode="stabilize" |
STABILIZE |
| Depth Hold | ↑ | pilot_mode="depth_hold" |
ALT_HOLD |
| Acro | — (bind manual) | pilot_mode="acro" |
ACRO |
Sorotan tab tidak diset lokal saat diklik — sumbernya hanya string mode dari
HEARTBEAT di telemetry. Karena itu tab GUI dan D-Pad selalu sinkron, dan tab
tidak pernah membohongi operator kalau Pixhawk menolak perpindahan mode (mis.
ALT_HOLD ditolak saat sumber kedalaman belum sehat). Tab yang menunggu
konfirmasi tampil putus-putus; setelah 2 detik tanpa konfirmasi muncul peringatan.
Mode di luar keempat tab (SURFACE, POSHOLD, …) tetap terbaca pada badge di
sebelah kanan tab bar.
ACRO tidak ada di semua build/frame ArduSub. rov_agent.py memeriksanya lewat
master.mode_mapping() — yang berasal dari firmware yang benar-benar terpasang
— dan menolak perintah bila mode tidak ada, alih-alih mengirim set_mode
yang akan diabaikan diam-diam. Gejalanya di GUI: tab Acro tetap putus-putus dan
muncul peringatan 2 detik kemudian.
MANUAL_CONTROL.z ArduSub adalah 0..1000 dengan 500 = diam — berbeda dari
tiga axis lain yang −1000..1000. Konversinya dilakukan
rov_axes.to_mavlink_z() di sisi Pi.
| Mode | Arti z = 500 |
|---|---|
| MANUAL | Nol thrust vertikal |
| STABILIZE | Nol thrust vertikal, attitude diratakan |
| ALT_HOLD | Tahan kedalaman |
| ACRO | Nol thrust vertikal — TIDAK menahan kedalaman |
Netral tetap bermakna benar di keempatnya (diam / tidak mendorong), jadi jalur axis tidak butuh penanganan khusus per-mode. Yang berbeda di ACRO adalah konsekuensinya: tidak ada yang menahan wahana, jadi netral berarti melayang mengikuti daya apung, bukan diam di kedalaman.
Di ACRO tidak ada stabilisasi attitude: stik memerintahkan rate (kecepatan sudut), bukan sudut. Wahana tidak akan meratakan dirinya sendiri. Di kolam dangkal KKI (≈0.9 m) ini mode yang paling mudah membuat ROV terguling.
Tiga pengaman yang dipasang:
- Depth hold dinonaktifkan.
ACROtidak masukrov_modes.DEPTH_HOLD_MODES, sehinggadepth_hold_active()bernilai false:gain_inc/gain_decditolak dengan log, danapply_depth_hold_bias()mengembalikan paket apa adanya. Tanpa ini, bias throttle akan mendorong wahana tanpa satu pun umpan balik yang menstabilkan. - Konfirmasi di GUI, seragam di semua jalur input. Klik tab Acro
maupun tombol gamepad
mode_acrosama-sama lewatrequestPilotMode()(public/js/app.js), yang menampilkan dialogACRO_CONFIRMsebelum mengirim apa pun. Membatalkan = tidak ada command yang dikirim sama sekali, dari jalur mana pun.Riwayat: versi awal sengaja melewati dialog untuk jalur gamepad (alasannya: tombol fisik hanya bisa ditekan operator yang sedang memegang pad). Audit berikutnya menandai ini sebagai gerbang keamanan yang tidak seragam antar jalur input, jadi diseragamkan — sekarang gamepad juga wajib konfirmasi seperti tab GUI.
- Peringatan visual. Selama HEARTBEAT melaporkan
ACRO, badge⚠ ACRO — TANPA STABILISASItampil di tab bar dan tab Acro diberi warna amber. Peringatan mengikuti mode aktual, bukan yang diminta.
Cakupan test: ketiga pengaman di atas punya unit test otomatis —
test_rov_modes.py (Python, gating depth-hold) dan
server/test/mode-gating.test.mjs (JS, pemetaan mode/risky/teks konfirmasi),
keduanya ikut jalan di python3 -m unittest test_rov_modes dan npm test
di server/.
Gerbang otoritas GUI. Saat Autonomous, thruster dan gripper dari GUI diblokir di sisi klien; FSM yang memegang kendali. Di luar cakupan trial ini.
Wahana memakai BlueROV1 (6 thruster, 6-DoF) — FRAME_CONFIG = 0.
Tidak ada mixing di repo ini. GUI hanya mengirim MANUAL_CONTROL (x/y/z/r);
ArduSub di Pixhawk yang membagi ke keenam motor sesuai FRAME_CONFIG. Satu-satunya
kendali level motor dari dashboard adalah pembalik arah (MOT_n_DIRECTION) di
halaman Setup.
Gripper: servo channel 10 (SERVO10_FUNCTION = 7), PWM buka 1900 / tutup
1100 / netral 1500, dengan rate-limit + EMA di
rov_gripper.py supaya tidak menyentak.
| Skenario | Respons | Diimplementasi di |
|---|---|---|
| Axis berhenti sampai ke Pi > 0,5 dtk | Pi streaming NEUTRAL (z=500, sisanya 0) dan menandai cmd_link: "stale"; dashboard menampilkan banner merah |
rov_axes.resolve_manual_packet, joystick_sender di rov_agent.py |
| WebSocket putus | E-Stop dikunci, semua axis dinetralkan | app.js |
| Gamepad dicabut | Semua axis dinetralkan seketika | gamepaddisconnected |
Space / Back / tombol STOP |
Netralkan seluruh thruster + disarm, joystick terkunci | btnStop |
| Mode Autonomous | Thruster & gripper dari GUI diblokir | gerbang di pollGamepad, keydown, dan sendGripper |
| ARM ulang | Melepas kunci E-Stop | btnArm |
Kenapa Pi tetap mengirim saat stale, bukan diam? ArduSub mengharapkan aliran
MANUAL_CONTROL yang kontinu. Kalau ground station diam, failsafe pilot-input
Pixhawk yang jalan dan perilakunya tergantung parameter. Streaming netral jauh
lebih bisa diprediksi: diam di tempat, dan di ALT_HOLD berarti tahan kedalaman.
Heartbeat axis. Dashboard mengirim ulang nilai axis saat ini ~15 Hz tanpa
memandang jenis controller — dan tetap mengirim nol eksplisit saat E-Stop
aktif. Dengan begitu stale benar-benar berarti link/GUI mati, bukan sekadar
operator sedang tidak menyentuh stik.
- Back (gamepad) / Spasi / tombol STOP → thruster netral seketika.
- Bila tidak respons, ARM untuk disarm.
- Setelah aman, Start (ARM ulang) untuk mengaktifkan kembali kontrol.
Gamepad / Keyboard
app.js · joystick-state.js (deadzone → expo → gain → rate limit)
│ WebSocket :8080 {type:"cmd", name, value}
server/server.js (clamp ±1000, tap rekaman)
│ UDP JSON :14550 {name, value, t}
rov_agent.py (clamp ulang, fail-safe idle, heave → z 0..1000)
│ pymavlink MANUAL_CONTROL / MAV_CMD_DO_SET_SERVO
Pixhawk ArduSub (mixing BlueROV1 → 6 thruster)
Telemetry balik: Pixhawk → rov_agent.py → UDP :14551 → server → WS → dashboard.
name |
value |
Keterangan |
|---|---|---|
arm |
true/false |
ARM / DISARM |
stop |
true |
Failsafe: disarm |
surge sway yaw heave |
−1000..1000 | Axis gerak (netral 0) |
pilot_mode |
"manual"/"stabilize"/"depth_hold"/"acro" |
Mode ArduSub |
control_mode |
"manual"/"autonomous" |
Gerbang otoritas GUI |
gripper |
"open"/"close"/-1000..1000 |
Posisi gripper |
light |
true/false |
Lampu (belum terhubung hardware) |
thruster_config |
{motors:{1..6: ±1}} |
MOT_n_DIRECTION |
Diterima tanpa aksi (murni state dashboard): controller, set_surface,
snapshot, record. Selain daftar di atas akan tercatat sebagai
unknown command di log Pi — itu memang sinyal ada yang perlu diperiksa.
Telemetry balik menambahkan cmd_link: "ok" | "stale" selain heading, depth,
roll, pitch, temp, voltage, armed, light, mode.