# Sprint 3 — PID outer loop + Heading Hold > Brief reference: §6 (cascaded PID), §12 Sprint 3. ## Objetivo Cerrar el lazo externo de **rumbo**: dado un rumbo deseado, calcular un setpoint de timón que el lazo interno (Sprint 2) lleva a hueso. Esto habilita el primer modo real distinto a STANDBY: **HEADING_HOLD**. ## Estrategia Misma topología que Sprint 2 (Python source-of-truth + C++ port): 1. **`vessel_heading.py`** — simulador del barco: yaw inertia, rate of turn (ROT), wash de timón (mayor velocidad sobre el agua = más yaw por grado de timón). Permite probar el PID outer sin barco real. 2. **`pid_outer.py`** — lazo externo. Inputs: rumbo deseado, rumbo actual, ROT actual, SOG. Output: setpoint de timón en grados que se pasa al PID inner. Incluye: - **Feed-forward de ROT**: anticipa cuándo dejar de aplicar timón para que el barco no sobrepase por inercia (brief §6). - **Gain scheduling por SOG**: interpola ganancias entre puntos de `GainSchedulePoint` (modelo de datos Sprint 0). - **Wrap-around de rumbo**: el error entre 358° y 2° es +4°, no -356°. - **Anti-windup + rate limit** del setpoint de timón producido. 3. **`pid_outer.h`** — port C++ header-only, byte-equivalente. 4. **`pid_outer_task.cpp`** — tarea FreeRTOS @ 10 Hz en Core 1. Lee rumbo de `nmea2000_consumer` (PGN 127250) + ROT (PGN 127251) + SOG (Sprint 1 todavía no la tenemos por PGN; Sprint 5 traerá COG/SOG vía PGN 129026; mientras tanto SOG=15 nudos hardcoded como default). Output: invoca `pid_inner_set_setpoint_deg(rudder_setpoint)`. 5. **Activación de HEADING_HOLD en `modes.cpp`**: `request_mode(HEADING_HOLD)` ahora aceptada **si**: - Sensor de rumbo válido (heading_valid == true) - Sensor de timón válido - Master power activado 6. **Captura del heading actual como setpoint inicial al engage**: cuando el operador hace engage en HH, el setpoint = rumbo actual, no el último valor escrito por Modbus. 7. **Tests Python** + cascada simulada inner+outer end-to-end (un barco virtual + PID outer + PID inner + simulador timón). ## Lo que NO hace Sprint 3 - True Course mode (compensación de deriva con COG). Sprint 5. - Track Keeping. Sprint 5. - Dodge. Sprint 5. - Alarmas off-course. Sprint 6. - Auto-disengage por pérdida de sensor. Sprint 6. ## Verificación - `pytest` verde (objetivo: 250+) - Cascada Python inner+outer estabiliza el rumbo dentro de ±2° en <30s - `pio run -e esp32-dev` compila clean - Modbus expone: heading_setpoint (RW), rudder_setpoint_from_outer (read), outer gains live, current SOG used