Files
alro65 1d7dd63327 docs(sprint-1): proposed plan + firmware libraries research
Two new documents to drive the Sprint 1 approval cycle when the user
reviews in the morning. NO firmware code touched -- this is planning
material only, per brief rule #1 "Antes de cada sprint, me presentas
plan detallado y esperas mi OK. No improvises features."

docs/sprint-1-plan.md
  - Sprint 1 objective (firmware boot + STANDBY + Modbus slave +
    NMEA 2000 consume of PGN 127250/127251 + watchdog).
  - 4 explicit technical decisions awaiting the user's go/no-go:
      2.1 Framework: Arduino-as-ESP-IDF-component (recommended)
      2.2 New libraries: NMEA2000-library, NMEA2000_esp32, eModbus
          (asked per brief rule #4 "No agregues dependencias sin
          preguntarme")
      2.3 FreeRTOS core/priority mapping (PID isolated on Core 1)
      2.4 Logging: ESP_LOG via UART0
  - 7-phase breakdown over 3-4 weeks (mirrors brief's Sprint 1 scope).
  - Acceptance criteria, risks, and the inputs needed from the user
    before kickoff (hardware availability, schematic, NMEA 2000 bus).

docs/firmware-libraries-research.md
  - Detailed rationale for each library choice with comparison tables.
  - Pinout / hardware references aligned with the existing
    firmware/ar_autopilot_v1/src/hal/pinout.h.
  - Draft platformio.ini outline (NOT yet written to the firmware
    directory -- needs user OK first).
  - All external sources cited for traceability.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 07:37:53 -04:00

14 KiB

Sprint 1 — Firmware ESP32 base — Plan propuesto

Status: propuesto, esperando tu OK. Brief reference: sección 12, Sprint 1. Duración estimada brief: 3-4 semanas. Pre-requisito: Sprint 0 aprobado (tag sprint-0-approved).

Este documento es mi propuesta. Antes de tocar firmware necesito tu OK explícito (regla de oro #1 del brief). Léelo, corrige lo que no te encaje, y luego me das luz verde o me pides cambios.


1. Objetivo del sprint

Levantar la base del firmware del controlador AR-Autopilot sobre la tarjeta AR-NMEA-IO v1.0. Al final del sprint la tarjeta debe poder:

  • Arrancar en modo STANDBY (timón manual, sin control del piloto).
  • Leer los 4 AI + 5 DI + 1 RPM, escribir los 10 DO declarados en firmware/ar_autopilot_v1/src/hal/pinout.h.
  • Hablar Modbus RTU como esclavo con el display dedicado (todavía inexistente en código — usamos un cliente de prueba en PC).
  • Consumir del bus NMEA 2000 los PGN mínimos: 127250 (Heading) y 127251 (Rate of Turn). Solo logging, no control.
  • Reaccionar al pulsador físico de disengage (DI1): no hay nada enganchado en Sprint 1, pero el evento se registra y se ven los bits cambiar en Modbus.

Lo que NO hace este sprint (importante — ver brief sección 12):

  • No corre PID real. Hay un "stub" del PID que devuelve siempre 0.
  • No publica al backbone NMEA 2000 (eso es Sprint 6).
  • No tiene seguridad funcional completa (Sprint 6 también).
  • No conoce los modos HEADING_HOLD / TRUE_COURSE / TRACK_KEEPING más allá de exponerlos como enum por Modbus.

2. Decisiones técnicas que pido aprobar

Estas son las preguntas que necesito que me respondas antes de empezar.

Decisión 2.1 — Framework: ESP-IDF puro vs Arduino-as-ESP-IDF-component

Opción Pros Cons Veredicto
Arduino framework puro Más fácil. Las libs NMEA2000_esp32 y eModbus son nativas Arduino. Curva corta Arduino abstrae FreeRTOS en un modelo single-threaded. Mal para nuestras 4 tareas concurrentes con prioridades distintas. Menos control sobre OTA con rollback, secure boot, deterministic timing No para producto safety-critical
ESP-IDF nativo Control total de FreeRTOS, deterministic real-time, OTA con rollback automático, secure boot/flash encryption, mejor para producción comercial Las libs Modbus/NMEA2000 maduras son Arduino-first; hay que wrappear o reescribir partes Limpio pero costoso
Arduino-as-ESP-IDF-component Combina lo mejor: ESP-IDF maneja el sistema (tareas, prioridades, OTA, security), Arduino se usa solo para las libs maduras (NMEA2000_esp32, eModbus). Patrón oficial Espressif Configuración inicial un poco más complicada en platformio.ini Recomendado

Mi recomendación: opción . El brief (regla #11) dice "El PID corre en firmware ESP32, no en el display. Esto es no-negociable: latencia determinística, seguridad ante caída del display, eficiencia." Eso implica ESP-IDF/FreeRTOS real, no Arduino single-thread. Pero no queremos perder semanas reescribiendo NMEA2000_esp32 ni eModbus.

Tu pregunta: ¿Apruebas opción , o prefieres una de las otras dos?

Decisión 2.2 — Librerías nuevas a añadir a platformio.ini

Por la regla #4 del brief ("No agregues dependencias sin preguntarme"), te las pido por adelantado:

Lib Versión / fuente Por qué Licencia
ttlappalainen/NMEA2000-library latest stable, PIO registry Base de toda la pila NMEA 2000. Sin esto no leemos heading MIT
ttlappalainen/NMEA2000_esp32 latest stable, PIO registry Driver CAN específico para ESP32 (TX=GPIO16, RX=GPIO4 por defecto). Incluye su propio driver CAN, no hace falta más MIT
eModbus/eModbus v1.7.4 (Jun 2025) Modbus RTU server (slave) con tasks FreeRTOS asíncronos, no bloquea el lazo principal MIT

Todas MIT, todas maduras, todas mantenidas. Las añado a platformio.ini con versiones pinneadas (no latest) para reproducibilidad.

Tu pregunta: ¿Las apruebo así o quieres otras alternativas?

Decisión 2.3 — Asignación de núcleos y prioridades FreeRTOS

ESP32-DOWD tiene 2 núcleos. Propuesta de mapeo:

Núcleo Tarea Prioridad Frecuencia
1 (Core 1 / APP_CPU) pid_inner_loop_task (Sprint 2; stub en Sprint 1) 24 (alta) 50 Hz, hard real-time
1 rudder_sensor_task (lectura AI1, mediana 5 muestras) 23 100 Hz
1 safety_monitor_task (limit switches, watchdog feed) 22 50 Hz
0 (Core 0 / PRO_CPU) nmea2000_rx_task (CAN polling, parsing PGN) 15 event-driven
0 modbus_slave_task (gestiona eModbus internals) 14 event-driven
0 health_reporter_task (uptime, RAM libre, IDFs varios) 5 1 Hz

Aislamiento explícito: el lazo PID nunca comparte núcleo con comunicaciones. Si el bus NMEA 2000 satura, el control no se ve afectado.

Tu pregunta: ¿Mapeo OK o quieres modificar?

Decisión 2.4 — Estrategia de logging en firmware

Para depurar sin display, necesito ver qué pasa adentro de la ESP32. Opciones:

  1. Solo ESP_LOG por UART0 (consola serie por USB). Estándar ESP-IDF. Cero overhead cuando se desactiva. (Recomendado)
  2. Log circular en RAM + dump por Modbus a petición. Más sofisticado, útil para post-mortem en campo, pero complejidad innecesaria en Sprint 1.
  3. Log a SD card. La tarjeta AR-NMEA-IO no tiene SD según el brief; descartado.

Mi recomendación: opción 1 para Sprint 1. La 2 se podría añadir en Sprint 6 (Seguridad y alarmas) si te parece útil.

Tu pregunta: ¿Opción 1?


3. Entregables del sprint

firmware/ar_autopilot_v1/
├── platformio.ini                      # nuevo: configurado con framework + libs aprobadas
├── sdkconfig.defaults                  # nuevo: tuning ESP-IDF (FreeRTOS tick, etc.)
├── src/
│   ├── main.cpp                        # nuevo: arranca tareas FreeRTOS, entra en loop tonto
│   ├── pid/
│   │   ├── pid_outer.cpp               # stub: devuelve setpoint=0, log
│   │   └── pid_inner.cpp               # stub: devuelve PWM=0, log
│   ├── modes/
│   │   └── standby.cpp                 # nuevo: única lógica de modo en Sprint 1
│   ├── safety/
│   │   ├── watchdog.cpp                # nuevo: TWDT habilitado a 2 s
│   │   └── override_handler.cpp        # nuevo: DI1 → siempre fuerza STANDBY
│   ├── protocols/
│   │   ├── modbus_slave.cpp            # nuevo: servidor Modbus mínimo
│   │   ├── modbus_registers.h          # nuevo: mapa de registros expuestos
│   │   └── nmea2000_consumer.cpp       # nuevo: lee 127250 + 127251, loguea
│   ├── hal/
│   │   ├── pinout.h                    # ya existe (Sprint 0)
│   │   ├── rudder_sensor.cpp           # nuevo: lee AI1 con filtro mediana
│   │   ├── rudder_actuator.cpp         # nuevo: stub que escribe DO1/DO2 pero comando=0
│   │   └── di_do.cpp                   # nuevo: helpers para todos los DI/DO
│   └── system/
│       └── health_reporter.cpp         # nuevo: log periódico de salud
├── include/                            # headers públicos
├── test/
│   ├── test_modbus_registers/          # Unity: validar mapa de registros
│   ├── test_filters/                   # Unity: mediana de 5 muestras
│   └── test_pinout/                    # Unity: que los pines no se pisen
└── tools/
    └── modbus_client_test.py           # script Python para probar el slave desde PC

Y en el lado Python:

arautopilot/
├── core/
│   └── modbus_registers.py             # nuevo: mismo mapa que firmware, generado / espejo
├── shared/
│   └── modbus_register_map.py          # constantes single-source-of-truth para ambos lados
└── tests/
    └── test_modbus_register_map.py     # nuevo: sanity check sobre el mapa

Importante: el mapa de registros Modbus tiene que ser single-source-of-truth. Mi propuesta: definirlo en YAML en firmware/ar_autopilot_v1/modbus_registers.yaml y generar tanto modbus_registers.h (C++) como modbus_registers.py (Python) con un script en tools/. Así no se desincronizan jamás. Te lo presento mejor cuando arranquemos.


4. Plan de fases dentro del sprint

Fase Días Objetivo Demo al final
1.1 1-2 platformio.ini + esqueleto FreeRTOS + LED de vida Tarjeta arranca, parpadea LED, log por UART0
1.2 3-4 HAL: lectura de los 9 inputs, escritura de los 10 outputs Script Python por UART manda comandos manuales, se ven los LEDs/relés
1.3 5-7 Modbus RTU slave + mapa de registros generado Cliente Modbus desde PC lee estado, escribe setpoint dummy
1.4 8-10 NMEA 2000 consumer: PGN 127250, 127251 Conectada al simulador NMEA 2000 (o a tu AR-ECDIS si está), aparecen heading y ROT en los registros Modbus
1.5 11-13 Watchdog + DI1 override + modo STANDBY explícito DI1 pulsador funciona, watchdog reinicia si se cuelga firmware
1.6 14-15 Tests Unity + pruebas integradas + documentación pio test verde, docs/firmware.md con instrucciones de flash
1.7 16-20 Buffer + integración con arautopilot.studio.simulator (vessel + sensor sim) Demo end-to-end: simulador → ESP32 → cliente Modbus en PC

5. Criterios de aceptación del Sprint 1

(Equivalente a lo que hicimos en Sprint 0)

  1. pio run compila sin warnings en modo release y debug.
  2. pio test pasa todos los tests Unity del firmware.
  3. pytest sigue verde en el lado Python (80+ tests existentes + nuevos del mapa Modbus).
  4. bash scripts/dev.sh check (lint+typecheck+test Python) verde.
  5. Demo manual documentada en docs/sprint-1-demo.md:
    • Conexión por USB → terminal serie a 115200 baudios → ver logs
    • Cliente Modbus Python (tools/modbus_client_test.py) → lee estado y bits de I/O
    • Simulador NMEA 2000 → publica PGN 127250 con heading 045°, el firmware lo loguea y lo expone en el registro Modbus
  6. Watchdog: si descomento un vTaskDelay(10000) en pid_outer_loop_task, el firmware se resetea solo y vuelve a STANDBY con alarma loggeada.
  7. Tag sprint-1 en main.

6. Riesgos identificados

Riesgo Mitigación
Tarjeta AR-NMEA-IO física no disponible durante el sprint Sprint 1 se puede empezar sin hardware: PlatformIO permite compilación + tests sin board. Los tests Unity corren en host. Las pruebas integradas se postergan hasta tener tarjeta. Tu input: ¿la tarjeta está físicamente disponible o trabajo "ciego" hasta tenerla?
GPIOs del pinout.h no coinciden con la tarjeta real El header del Sprint 0 dice claramente "PLACEHOLDERS aligned with the AR-NMEA-IO schematic. They will be confirmed and locked in Sprint 1 against the real board revision." Antes de Fase 1.2 te pido el esquemático o el manual de la tarjeta
El bus NMEA 2000 del entorno de pruebas no está disponible Plan B: simulador software (tools/n2k_simulator.py) que inyecte PGN 127250 sintéticos a la ESP32 vía un transceiver USB-CAN (Canable, Yacht Devices YDNU-02)
eModbus no maneja DE/RE automáticamente Lo verifico en Fase 1.3. Si no, escribo un wrapper de 30 líneas que use el PIN_RS485_DE del pinout
Conflicto de IRQ entre CAN driver y UART de logging Asignamos a núcleos separados (decisión 2.3). El log va por UART0 que es PRO_CPU; CAN driver corre interrupts en APP_CPU sin solapamiento
Arduino-as-ESP-IDF-component se rompe entre versiones Pinneamos versión exacta de framework-arduinoespressif32 en platformio.ini, no latest

7. Lo que necesito de ti antes de empezar

  1. Apruebas/modificas las 4 decisiones técnicas de la sección 2.
  2. Tag de Sprint 0 aprobado:
    git tag -a sprint-0-approved -m "Sprint 0 reviewed and approved"
    
  3. Confirmas disponibilidad del hardware (tarjeta AR-NMEA-IO física):
    • Si la tienes: trabajamos contra ella desde Fase 1.2.
    • Si no: trabajamos en bench (todos los tests Unity + simulador) y la integración real se pospone para cuando llegue.
  4. Esquemático o pinout real de la AR-NMEA-IO v1.0, para confirmar los GPIOs placeholder de pinout.h. Si está en repositorio separado, dame la ruta. Si está en papel, una foto sirve.
  5. Acceso a un bus NMEA 2000 funcional para pruebas (o lo hacemos con un simulador software y un adaptador USB-CAN).

8. Plan de comunicación durante el sprint

Mismo modelo que Sprint 0 (resultó bien):

  • Inicio de cada fase: te resumo qué voy a hacer y espero OK.
  • Final de cada fase: commit + breve resumen + demo de qué se puede tocar/ver.
  • Si encuentro algo no previsto (riesgo nuevo, decisión técnica con trade-off real), paro y te pregunto antes de improvisar.
  • Tests siempre verdes antes de cerrar fase. Si rompo algo, lo arreglo antes de pedirte review.

9. Estado actual del repo (al momento de escribir este plan)

$ git log --oneline
8d4a698 polish(sprint-0): clean code per ruff + mypy strict
700756c sprint-0: foundations -- data model, seed library, tests, demo

(Y el commit del runner dev que viene a continuación de este plan.)

Working tree limpio, 80/80 tests verde, ruff limpio, mypy strict limpio.


10. Fin

Cuando termines de leer:

  • Si todo bien: tag sprint-0-approved + "dale Sprint 1" → arranco por Fase 1.1.
  • Si hay que cambiar algo: dime qué decisión te encaja distinto y reescribo el plan. No empiezo nada hasta que coincidamos.

— Claude