diff --git a/README.md b/README.md index 99b428cd..049bb01f 100644 --- a/README.md +++ b/README.md @@ -311,11 +311,22 @@ di kolam yang disarankan. ### Tuning gerak autonomous dari GUI -Buka **Setup → Autonomous Motion**, atur speed tiap fase (dive, ascend, surge, -yaw, search, approach, engage, dan unhook), lalu tekan **Apply Autonomous Motion** +Buka **Setup → Autonomous Motion**, atur target **Selam, Naik, Maju (m/s)** dan +**Putar (°/s)**, lalu tekan **Apply Autonomous Motion** saat FSM belum berjalan. Mode pilot ArduSub **ALT_HOLD boleh tetap aktif**. Nilai berlaku -pada start autonomous berikutnya; perubahan saat FSM sedang berjalan ditolak agar satu trial tetap konsisten. Batas input diterapkan -lagi di Pi dan hanya mengubah command axis persen—mixer/PWM tetap milik ArduSub. +pada start autonomous berikutnya; perubahan saat FSM sedang berjalan ditolak agar satu +trial tetap konsisten. Pi menerjemahkan target fisik ke command berdasarkan +`motion_calibration` di `autonomy/config/rov_tuned.yaml`, lalu membatasi command. +Telemetry `surge_speed`, `vertical_speed`, dan `yaw_rate` dicatat untuk membandingkan +target dengan gerak aktual. Mixer/PWM tetap milik ArduSub. +Selama autonomous aktif, server juga menyalin snapshot log terbaru dari Pi ke +`autonomy/logs/autonomous1.log`; file dapat diunduh dari panel Mission 5. + +Kalibrasi dilakukan dengan menjalankan beberapa command di kolam, mengukur jarak +atau perubahan kedalaman terhadap waktu, lalu memperbarui empat nilai +`motion_calibration`. Sebelum kalibrasi, angka target adalah estimasi command, +bukan jaminan kecepatan nyata. Jika `LOCAL_POSITION_NED` tidak valid, `surge_speed` +akan kosong dan tidak boleh dipakai sebagai bukti kecepatan maju. ### Deteksi QR robust + diagnosa (`decode_qr` & `--csv`) diff --git a/autonomy/config/loader.py b/autonomy/config/loader.py index 4fd34a0a..08c6aa12 100644 --- a/autonomy/config/loader.py +++ b/autonomy/config/loader.py @@ -45,6 +45,12 @@ ('speed', 'surge'): 'SURGE_SPEED', ('speed', 'yaw'): 'YAW_SPEED', + # Kalibrasi fisik: kecepatan pada command axis 50, diukur di kolam. + ('motion_calibration', 'dive_mps_at_50'): 'DIVE_MPS_AT_50', + ('motion_calibration', 'ascend_mps_at_50'): 'ASCEND_MPS_AT_50', + ('motion_calibration', 'surge_mps_at_50'): 'SURGE_MPS_AT_50', + ('motion_calibration', 'yaw_dps_at_50'): 'YAW_DPS_AT_50', + ('timeouts', 'dive'): 'TIMEOUT_DIVE', ('timeouts', 'scan'): 'TIMEOUT_SCAN', ('timeouts', 'grab'): 'TIMEOUT_GRAB', diff --git a/autonomy/config/rov_tuned.yaml b/autonomy/config/rov_tuned.yaml index 26853062..cafeeef0 100644 --- a/autonomy/config/rov_tuned.yaml +++ b/autonomy/config/rov_tuned.yaml @@ -26,6 +26,15 @@ invert: surge: false yaw: false +# ── Kalibrasi gerak fisik (WAJIB diukur di kolam) ──────────────────────────── +# Kecepatan pada command axis 50. Nilai ini hanya titik awal sampai hasil uji +# command→kecepatan dimasukkan; jangan menganggapnya sebagai spesifikasi motor. +motion_calibration: + dive_mps_at_50: 0.20 + ascend_mps_at_50: 0.20 + surge_mps_at_50: 0.30 + yaw_dps_at_50: 45.0 + # ── Gain PID servo docking — IBVS (piksel, tanpa kalibrasi kamera) ──────────── pid_ibvs: kp_sway: 45.0 diff --git a/autonomy/fsm/mission5.py b/autonomy/fsm/mission5.py index 8469b499..05d2a17b 100644 --- a/autonomy/fsm/mission5.py +++ b/autonomy/fsm/mission5.py @@ -67,10 +67,18 @@ HOOK_HEIGHT_FROM_FLOOR = None # m — tinggi ujung hook dari DASAR (KKI 2026 = 0.45) BOTTOM_CLEARANCE = None # m — jarak aman titik-tengah ROV di atas dasar (BERPINDAH antar venue) -DIVE_SPEED = 30 # % thruster vertikal saat menyelam -ASCEND_SPEED = 30 # % thruster vertikal saat naik -SURGE_SPEED = 35 # % surge saat navigasi horizontal -YAW_SPEED = 25 # % yaw saat rotasi +DIVE_SPEED = 30 # command axis (0..100) saat menyelam +ASCEND_SPEED = 30 # command axis (0..100) saat naik +SURGE_SPEED = 35 # command axis (0..100) saat navigasi horizontal +YAW_SPEED = 25 # command axis (0..100) saat rotasi + +# Kalibrasi fisik: estimasi kecepatan pada command axis 50. Nilai ini WAJIB +# diganti dengan hasil uji kolam; ia bukan spesifikasi thruster dan tidak +# membuktikan kecepatan nyata sebelum telemetry kecepatan tersedia. +DIVE_MPS_AT_50 = 0.20 # m/s turun pada command 50 +ASCEND_MPS_AT_50 = 0.20 # m/s naik pada command 50 +SURGE_MPS_AT_50 = 0.30 # m/s maju pada command 50 +YAW_DPS_AT_50 = 45.0 # deg/s pada command 50 # SCAN_QR dulu cuma yaw di tempat menunggu decode penuh — di air keruh QR baru terbaca # dari jarak jauh lebih dekat drpd air jernih (24 Agu: foto lapangan gagal decode walau QR @@ -668,6 +676,9 @@ def _log_sample(self, telem): self.runlog.event('sample', state=t['state'], active_cam=t['active_cam'], depth=telem.get('depth'), heading=telem.get('heading'), + surge_speed=telem.get('surge_speed'), + vertical_speed=telem.get('vertical_speed'), + yaw_rate=telem.get('yaw_rate'), distance_z=t['distance_z'], offset_x=t['offset_x'], offset_y=t['offset_y'], qr_data=t['qr_data'], qr_wall=t['qr_wall'], diff --git a/autonomy/rov_link.py b/autonomy/rov_link.py index 7159c2eb..adbb9341 100644 --- a/autonomy/rov_link.py +++ b/autonomy/rov_link.py @@ -26,7 +26,8 @@ Kontrak JSON (sesuai server.js + README-WORK §3): Command masuk : {"name": "...", "value": ..., "t": ...} - Telemetri keluar: {heading, roll, pitch, depth, temp, voltage, armed, light, mode, ts} + Telemetri keluar: {heading, roll, pitch, depth, surge_speed, vertical_speed, + yaw_rate, temp, voltage, armed, light, mode, ts} """ import argparse @@ -156,6 +157,7 @@ def __init__(self, args): # telemetri terbaru hasil parsing MAVLink self.telem = { "heading": None, "roll": None, "pitch": None, "depth": None, + "surge_speed": None, "vertical_speed": None, "yaw_rate": None, "temp": None, "voltage": None, "armed": False, "light": False, "mode": "manual", "poshold": False, } @@ -195,6 +197,13 @@ def _request_streams(self): self.master.mav.request_data_stream_send( self.master.target_system, self.master.target_component, mavutil.mavlink.MAV_DATA_STREAM_ALL, 10, 1) # 10 Hz + # Be explicit: some ArduSub/SITL configurations ignore the broad + # stream request for LOCAL_POSITION_NED. + self.master.mav.command_long_send( + self.master.target_system, self.master.target_component, + mavutil.mavlink.MAV_CMD_SET_MESSAGE_INTERVAL, 0, + mavutil.mavlink.MAVLINK_MSG_ID_LOCAL_POSITION_NED, + 100000, 0, 0, 0, 0, 0, 0) def arm(self, on): self.master.mav.command_long_send( @@ -406,6 +415,21 @@ def loop_mavlink_rx(self): self.telem["roll"] = round(math.degrees(msg.roll), 1) self.telem["pitch"] = round(math.degrees(msg.pitch), 1) self.telem["heading"] = round((math.degrees(msg.yaw) + 360) % 360, 1) + self.telem["yaw_rate"] = round(math.degrees(msg.yawspeed), 3) + elif t == "LOCAL_POSITION_NED": + # MAVLink LOCAL_POSITION_NED velocity: cm/s, NED frame. + try: + vn = float(msg.vx) / 100.0 + ve = float(msg.vy) / 100.0 + vd = float(msg.vz) / 100.0 + if not all(math.isfinite(v) for v in (vn, ve, vd)): + raise ValueError("velocity bukan finite") + hdg = math.radians(float(self.telem["heading"] or 0.0)) + self.telem["surge_speed"] = round(vn * math.cos(hdg) + ve * math.sin(hdg), 4) + self.telem["vertical_speed"] = round(vd, 4) + except (TypeError, ValueError): + self.telem["surge_speed"] = None + self.telem["vertical_speed"] = None elif t == "SCALED_PRESSURE2": self.last_press_abs = msg.press_abs depth = (msg.press_abs - self.surface_hpa) * 100.0 / (WATER_RHO * G) diff --git a/public/index.html b/public/index.html index 675029fc..6538586a 100644 --- a/public/index.html +++ b/public/index.html @@ -334,6 +334,7 @@