# Protocolo Concentrador NMEA2000-USB
**AR-Autopilot — Tarjeta Concentrador**
Versión 1.0 — 2026-05-23

---

## Arquitectura general

```
Software (J6412 / Tablet / PC)
        │
        │  NMEA 0183 ASCII  @115200 bps  por USB (CH340N)
        ▼
┌─────────────────────────────────────┐
│   ESP32 Concentrador                │
│   NMEA 0183 ←→ NMEA 2000 gateway   │
└─────────────────────────────────────┘
        │
        │  NMEA 2000 (CAN 250 kbps)
        ▼
   Backbone del barco
```

- **USB → ESP32 (IN)** : sentencias NMEA 0183 que el software envía como comandos
- **ESP32 → USB (OUT)**: sentencias NMEA 0183 que el ESP32 emite con datos del backbone
- **Formato**: ASCII, terminado en `\r\n`, checksum NMEA estándar `*XX`
- **Velocidad**: 115200 bps en todos los puertos USB

---

## Hardware — Puertos USB

```
UART1 TX → CH340N #1 → USB-OUT1   (puente     — solo lectura)
         → CH340N #2 → USB-OUT2   (cockpit    — solo lectura)
         → CH340N #3 → USB-OUT3   (flybridge  — solo lectura)
         → CH340N #4 → USB-OUT4   (reserva    — solo lectura)

UART2 RX ← CH340N #5 ← USB-IN1   (puente     — manda comandos)
         ← CH340N #6 ← USB-IN2   (cockpit    — manda si tiene el mando)
         ← CH340N #7 ← USB-IN3   (flybridge  — manda si tiene el mando)
         ← CH340N #8 ← USB-IN4   (reserva    — futuro)
```

**IDs de estación:**
| ID  | Estación  | Prioridad |
|-----|-----------|-----------|
| 01  | Puente    | Máxima — override sin confirmación |
| 02  | Cockpit   | Normal  |
| 03  | Flybridge | Normal  |
| 04  | Reserva   | Normal  |

---

## Sentencias NMEA 0183 estándar (salida ESP32 → USB-OUT)

El ESP32 convierte los PGNs del backbone a estas sentencias y las emite
por todos los puertos OUT cada vez que recibe datos nuevos del bus.

| Sentencia | Contenido | PGN fuente | Frecuencia |
|-----------|-----------|------------|------------|
| `$IIHDT,x.x,T*XX` | Heading verdadero | 127250 | 10 Hz |
| `$IIROT,x.x,A*XX` | Rate of turn (°/min) | 127251 | 10 Hz |
| `$IIVHW,,,x.x,N,x.x,K*XX` | Velocidad agua | 128259 | 1 Hz |
| `$IIDPT,x.x,0.0*XX` | Profundidad | 128267 | 1 Hz |
| `$GPGLL,x.x,N,x.x,W,hhmmss,A*XX` | Posición GPS | 129025 | 1 Hz |
| `$GPRMC,...*XX` | GPS completo (COG, SOG) | 129026 | 1 Hz |
| `$IIMTW,x.x,C*XX` | Temperatura agua | 130310 | 0.5 Hz |
| `$IIMWV,...*XX` | Viento | 130306 | 1 Hz |

---

## Sentencias propietarias AR-Autopilot (salida ESP32 → USB-OUT)

Prefijo `$PARP` (P=Proprietary, ARP=AR-Pilot).

### Estado del autopilot (broadcast continuo, 2 Hz)

```
$PARP,STATUS,<modo>,<setpoint>,<heading>,<rudder>,<commander>*XX
```

| Campo | Valores |
|-------|---------|
| modo | `STANDBY` `HEADING_HOLD` `TRACK` `ALARM` |
| setpoint | heading objetivo en grados (000.0–359.9) |
| heading | heading actual en grados |
| rudder | posición timón en grados (-35.0 a +35.0, + = estribor) |
| commander | ID estación con el mando (01–04, 00 = nadie) |

Ejemplo:
```
$PARP,STATUS,HEADING_HOLD,045.5,044.8,-3.2,01*4F
```

### Estado de transferencia de mando (broadcast al ocurrir)

```
$PARP,CMDTRANSFER,<desde>,<hacia>*XX
$PARP,CMDREQUEST,<estacion>*XX
```

Ejemplos:
```
$PARP,CMDTRANSFER,01,02*3A   ← el puente cedió el mando al cockpit
$PARP,CMDREQUEST,02*1B       ← cockpit pide el mando (pendiente de confirmación)
```

---

## Sentencias propietarias AR-Autopilot (entrada USB-IN → ESP32)

El ESP32 solo acepta estas sentencias de la estación que tiene el mando,
excepto `TAKECMD` y `REQCMD` que tienen reglas propias.

### Formato general

```
$PARP,<COMANDO>,<parametro>,<station_id>*XX\r\n
```

### Comandos de control del autopilot

| Sentencia | Acción | → PGN NMEA 2000 |
|-----------|--------|-----------------|
| `$PARP,ENGAGE,000.0,01*XX` | Activar autopilot en heading actual | PGN 127237 mode=heading_hold |
| `$PARP,DISENGAGE,000.0,01*XX` | Desactivar autopilot | PGN 127237 mode=standby |
| `$PARP,SETHEADING,045.5,01*XX` | Fijar heading objetivo | PGN 127237 commanded_heading |
| `$PARP,PORTONE,000.0,01*XX` | Girar 1° a babor | PGN 127237 (setpoint -= 1°) |
| `$PARP,STBDONE,000.0,01*XX` | Girar 1° a estribor | PGN 127237 (setpoint += 1°) |
| `$PARP,PORTTEN,000.0,01*XX` | Girar 10° a babor | PGN 127237 (setpoint -= 10°) |
| `$PARP,STBDTEN,000.0,01*XX` | Girar 10° a estribor | PGN 127237 (setpoint += 10°) |

### Comandos de transferencia de mando

| Sentencia | Acción | Regla |
|-----------|--------|-------|
| `$PARP,REQCMD,000.0,02*XX` | Solicitar el mando | Avisa al commander actual; auto-transfer en 10s si no responde |
| `$PARP,RELCMD,000.0,01*XX` | Ceder el mando voluntariamente | Solo el commander actual |
| `$PARP,TAKECMD,000.0,01*XX` | Tomar el mando por override | Solo estación 01 (puente); inmediato, sin confirmación |
| `$PARP,ACKCMD,000.0,01*XX` | Confirmar o denegar solicitud de mando | Solo el commander actual |
| `$PARP,DENYCMD,000.0,01*XX` | Denegar solicitud de mando | Solo el commander actual |

---

## Protocolo de transferencia de mando

```
Estado normal: Puente (01) tiene el mando
                    │
Cockpit quiere mando:
    Cockpit ──► $PARP,REQCMD,000.0,02*XX
    ESP32 ──► broadcast $PARP,CMDREQUEST,02*XX  (todos se enteran)
                    │
           ┌────────┴────────┐
     Puente confirma     Puente deniega     Puente no responde (10s)
           │                  │                      │
  $PARP,RELCMD,02     $PARP,DENYCMD,02      auto-transfer
           │                  │                      │
  commander = 02      commander = 01         commander = 02
  $PARP,CMDTRANSFER,01,02*XX              $PARP,CMDTRANSFER,01,02*XX

Override del puente (siempre posible):
    Puente ──► $PARP,TAKECMD,000.0,01*XX
    ESP32 ──► commander = 01 inmediatamente
    ESP32 ──► broadcast $PARP,CMDTRANSFER,XX,01*XX
```

---

## Mapeo NMEA 0183 → NMEA 2000

| Sentencia IN | PGN generado | Descripción PGN |
|-------------|-------------|-----------------|
| `$PARP,ENGAGE` | 127237 | Heading/Track Control — mode: heading_hold |
| `$PARP,DISENGAGE` | 127237 | Heading/Track Control — mode: standby |
| `$PARP,SETHEADING` | 127237 | commanded_heading field |
| `$PARP,PORT*` / `$PARP,STBD*` | 127237 | commanded_heading ± delta |

## Mapeo NMEA 2000 → NMEA 0183

| PGN recibido | Sentencia generada |
|-------------|-------------------|
| 127250 — Vessel Heading | `$IIHDT` |
| 127251 — Rate of Turn | `$IIROT` |
| 127237 — Heading/Track Control | `$PARP,STATUS` |
| 128259 — Speed Through Water | `$IIVHW` |
| 128267 — Water Depth | `$IIDPT` |
| 129025 — Position Rapid | `$GPGLL` |
| 129026 — COG & SOG | `$GPRMC` |
| 130310 — Water Temperature | `$IIMTW` |
| 130306 — Wind | `$IIMWV` |

---

## Cálculo de checksum NMEA

```python
def nmea_checksum(sentence: str) -> str:
    """sentence: contenido entre $ y * (sin incluir ambos)"""
    chk = 0
    for c in sentence:
        chk ^= ord(c)
    return f"{chk:02X}"

# Ejemplo:
# sentence = "PARP,STATUS,HEADING_HOLD,045.5,044.8,-3.2,01"
# checksum = "4F"
# resultado = "$PARP,STATUS,HEADING_HOLD,045.5,044.8,-3.2,01*4F\r\n"
```

---

## Seguridad y validación en el ESP32

1. **Checksum inválido** → descartar silenciosamente, no ejecutar
2. **Comando de estación sin mando** → descartar, emitir `$PARP,STATUS` con commander actual
3. **Heading fuera de rango** (< 0° o > 359.9°) → normalizar a rango válido
4. **Pérdida de datos del backbone** (> 3s sin PGNs) → emitir alarma `$PARP,STATUS,ALARM,...`
5. **Desconexión USB-IN** → no afecta la operación; el mando permanece asignado
6. **Power-on**: `commander = 00` (nadie), autopilot en STANDBY

---

## Ejemplo de sesión completa

```
[t=0s]  OUT → $PARP,STATUS,STANDBY,000.0,182.3,+0.0,00*XX
[t=1s]  IN1 ← $PARP,REQCMD,000.0,01*XX          (puente toma el mando)
[t=1s]  OUT → $PARP,CMDTRANSFER,00,01*XX
[t=2s]  OUT → $PARP,STATUS,STANDBY,000.0,182.5,+0.0,01*XX
[t=3s]  IN1 ← $PARP,ENGAGE,000.0,01*XX
[t=3s]  OUT → $PARP,STATUS,HEADING_HOLD,182.5,182.5,+0.0,01*XX
[t=5s]  IN1 ← $PARP,STBDTEN,000.0,01*XX
[t=5s]  OUT → $PARP,STATUS,HEADING_HOLD,192.5,183.1,+2.5,01*XX
[t=10s] IN2 ← $PARP,REQCMD,000.0,02*XX          (cockpit pide mando)
[t=10s] OUT → $PARP,CMDREQUEST,02*XX
[t=15s] IN1 ← $PARP,RELCMD,000.0,01*XX          (puente lo cede)
[t=15s] OUT → $PARP,CMDTRANSFER,01,02*XX
[t=16s] IN2 ← $PARP,SETHEADING,200.0,02*XX
[t=16s] OUT → $PARP,STATUS,HEADING_HOLD,200.0,193.4,+3.8,02*XX
```
