Files
AR-Autopilot/docs/concentrador_protocol.md
T
alro65 4cc5b19f0c docs: spec protocolo NMEA 0183/2000 del concentrador USB
Define el protocolo completo de la tarjeta concentrador NMEA2000-USB:
- Sentencias NMEA 0183 estandar emitidas por el ESP32 hacia los puertos OUT
- Sentencias propietarias $PARP para comandos del autopilot
- Protocolo de transferencia de mando entre estaciones (puente, cockpit, flybridge)
- Mapeo completo NMEA 0183 <-> NMEA 2000 PGNs
- Seguridad: validacion de checksum, permisos por estacion, alarmas

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 01:07:06 -04:00

243 lines
8.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.0359.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 (0104, 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
```