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>
This commit is contained in:
@@ -0,0 +1,246 @@
|
|||||||
|
# Firmware libraries — research notes (Sprint 1 prep)
|
||||||
|
|
||||||
|
Investigación realizada antes de planificar el Sprint 1 para elegir las
|
||||||
|
librerías del firmware ESP32. Documento de referencia — no normativo, solo
|
||||||
|
para que tengamos trazabilidad de por qué se eligió cada cosa.
|
||||||
|
|
||||||
|
Fecha: 2026-05-18.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. NMEA 2000 stack
|
||||||
|
|
||||||
|
### Decisión: `ttlappalainen/NMEA2000-library` + `ttlappalainen/NMEA2000_esp32`
|
||||||
|
|
||||||
|
**Por qué es el estándar de facto:**
|
||||||
|
|
||||||
|
- Es la stack NMEA 2000 más adoptada del ecosistema open-source para
|
||||||
|
microcontroladores. Casi todos los proyectos NMEA 2000 de aficionados
|
||||||
|
y muchos productos comerciales pequeños la usan.
|
||||||
|
- Cubre tanto consumo como publicación de PGN.
|
||||||
|
- Mantenida desde 2015, sigue activa.
|
||||||
|
|
||||||
|
**Componentes:**
|
||||||
|
|
||||||
|
| Repo | Rol |
|
||||||
|
|---|---|
|
||||||
|
| [`ttlappalainen/NMEA2000`](https://github.com/ttlappalainen/NMEA2000) | Stack core (parsing, PGNs, NMEA2000 class). Independiente de hardware |
|
||||||
|
| [`ttlappalainen/NMEA2000_esp32`](https://github.com/ttlappalainen/NMEA2000_esp32) | Driver CAN específico para ESP32. Incluye su propio low-level driver — no requiere libs adicionales |
|
||||||
|
|
||||||
|
**Configuración por defecto:**
|
||||||
|
|
||||||
|
- TX pin: `GPIO 16`
|
||||||
|
- RX pin: `GPIO 4`
|
||||||
|
- Override: definir `ESP32_CAN_TX_PIN` y `ESP32_CAN_RX_PIN` antes de
|
||||||
|
incluir los headers.
|
||||||
|
|
||||||
|
**Framework support:**
|
||||||
|
|
||||||
|
- ✅ Arduino framework
|
||||||
|
- ✅ ESP-IDF (añadido oficialmente en 2022)
|
||||||
|
- → Compatible con la decisión "Arduino-as-ESP-IDF-component"
|
||||||
|
|
||||||
|
**Licencia:** MIT (compatible con producto comercial propietario).
|
||||||
|
|
||||||
|
**Hardware externo necesario:**
|
||||||
|
|
||||||
|
- Transceiver CAN físico: Microchip MCP2562, NXP TJA1051T, o similar.
|
||||||
|
Conecta los pines GPIO16/4 del ESP32 al bus diferencial CAN-H/CAN-L
|
||||||
|
del backbone NMEA 2000. El driver de la librería NO incluye el
|
||||||
|
hardware del transceiver — es un chip aparte en la tarjeta
|
||||||
|
AR-NMEA-IO. (Probablemente ya lo tienes en la v1.0 que diseñaste.)
|
||||||
|
|
||||||
|
**PGN que vamos a consumir en Sprint 1:**
|
||||||
|
|
||||||
|
| PGN | Nombre | Contenido relevante |
|
||||||
|
|---|---|---|
|
||||||
|
| 127250 | Vessel Heading | heading (rad), deviation, variation, reference (true/magnetic) |
|
||||||
|
| 127251 | Rate of Turn | rate (rad/s) |
|
||||||
|
|
||||||
|
**Snippet de referencia para Sprint 1 (no es código final):**
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <NMEA2000_CAN.h> // selects ESP32 driver automatically
|
||||||
|
#include <N2kMessages.h>
|
||||||
|
|
||||||
|
void HandleHeading(const tN2kMsg &N2kMsg) {
|
||||||
|
unsigned char SID;
|
||||||
|
double Heading, Deviation, Variation;
|
||||||
|
tN2kHeadingReference HeadingReference;
|
||||||
|
if (ParseN2kHeading(N2kMsg, SID, Heading, Deviation, Variation, HeadingReference)) {
|
||||||
|
// Heading in radians; convert to degrees for our use
|
||||||
|
gLastHeadingDeg = Heading * 180.0 / M_PI;
|
||||||
|
gLastHeadingTimestamp = millis();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
NMEA2000.SetN2kCANMsgBufSize(8);
|
||||||
|
NMEA2000.SetMsgHandler(HandleHeading); // registers for ALL PGNs;
|
||||||
|
// we filter inside the handler
|
||||||
|
NMEA2000.Open();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
NMEA2000.ParseMessages(); // call frequently from a dedicated task
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Esto es **orientativo**. En el sprint lo encapsulamos en una tarea
|
||||||
|
FreeRTOS dedicada (`nmea2000_rx_task` en Core 0).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Modbus RTU server (slave)
|
||||||
|
|
||||||
|
### Decisión: `eModbus/eModbus` v1.7.4
|
||||||
|
|
||||||
|
**Comparación rápida:**
|
||||||
|
|
||||||
|
| Lib | Slave RTU | Async/Tasks FreeRTOS | Mantenimiento | Veredicto |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `eModbus/eModbus` v1.7.4 (2025-06) | ✅ | ✅ tasks separados, no bloquea | Activo | **Elegida** |
|
||||||
|
| `emelianov/modbus-esp8266` (a.k.a. `modbus-esp32`) | ✅ | Parcial | Activo | Alternativa válida |
|
||||||
|
| `MrMoses1911/ModbusRTU_ESP32` | ✅ | No documentado | Menor adopción | Descartada |
|
||||||
|
| `ArduinoModbus` (oficial Arduino) | ✅ | No | Mantenida pero feature-pobre | Descartada |
|
||||||
|
| `bertmelis/esp32ModbusRTU` | ❌ solo cliente | ✅ | Activo | No aplica (cliente, no slave) |
|
||||||
|
|
||||||
|
**Por qué eModbus:**
|
||||||
|
|
||||||
|
- Único que documenta explícitamente "Modbus communication is done in
|
||||||
|
separate tasks" y API "non blocking / asynchronous" → el lazo PID no
|
||||||
|
se bloquea jamás por tráfico Modbus.
|
||||||
|
- Soporta RTU, ASCII y TCP — si en el futuro queremos pasar a Modbus
|
||||||
|
TCP por Ethernet/WiFi, es la misma librería.
|
||||||
|
- Versión estable reciente (junio 2025).
|
||||||
|
- Licencia MIT.
|
||||||
|
|
||||||
|
**Punto a verificar en Fase 1.3:**
|
||||||
|
|
||||||
|
⚠️ El README de eModbus no confirma explícitamente que la librería
|
||||||
|
maneje sola la línea **DE/RE** (Driver Enable / Receiver Enable) del
|
||||||
|
transceiver RS-485 MAX485 / MAX3485. Si no lo hace, escribimos un
|
||||||
|
wrapper de ~30 líneas que toggle el `PIN_RS485_DE` del pinout antes
|
||||||
|
y después de cada respuesta.
|
||||||
|
|
||||||
|
**Framework support:**
|
||||||
|
|
||||||
|
- ✅ Arduino framework (confirmado)
|
||||||
|
- ❓ ESP-IDF nativo (no confirmado explícitamente; lo verifico al
|
||||||
|
integrarlo)
|
||||||
|
|
||||||
|
Si ESP-IDF nativo no está soportado: igual funciona desde el componente
|
||||||
|
Arduino dentro de un proyecto ESP-IDF (eso es el patrón
|
||||||
|
Arduino-as-ESP-IDF-component).
|
||||||
|
|
||||||
|
**Mapa de registros propuesto** (a refinar en Sprint 1):
|
||||||
|
|
||||||
|
| Tipo Modbus | Rango | Contenido |
|
||||||
|
|---|---|---|
|
||||||
|
| Holding (40001-) | 1-50 | Setpoints: heading_deseado, modo, comandos de operador |
|
||||||
|
| Input (30001-) | 1-50 | Estado: heading_actual, rudder_angle_actual, modo_activo, alarmas activas |
|
||||||
|
| Discrete (10001-) | 1-32 | Bits: engaged, alarms (uno por tipo), DI raw |
|
||||||
|
| Coils (00001-) | 1-32 | Comandos digitales: engage_request, disengage_request, alarm_ack |
|
||||||
|
|
||||||
|
El mapa exacto se finaliza al inicio de Fase 1.3 y se genera en
|
||||||
|
single-source-of-truth desde un YAML.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Framework: ESP-IDF vs Arduino vs híbrido
|
||||||
|
|
||||||
|
### Decisión: Arduino-as-ESP-IDF-component
|
||||||
|
|
||||||
|
**Resumen de la discusión:**
|
||||||
|
|
||||||
|
| Aspecto | Arduino solo | ESP-IDF solo | Arduino-as-ESP-IDF-component |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Manejo de FreeRTOS | Abstraído a single-thread | Nativo, control total | **Nativo (ESP-IDF), Arduino se compone encima** |
|
||||||
|
| Disponibilidad de libs NMEA2000/Modbus | ✅ inmediata | ⚠️ requiere wrap | ✅ inmediata |
|
||||||
|
| OTA con rollback automático | ❌ no | ✅ sí | ✅ sí |
|
||||||
|
| Secure boot + flash encryption | ⚠️ limitado | ✅ sí | ✅ sí |
|
||||||
|
| Determinismo PID real-time | ❌ unsuitable | ✅ excelente | ✅ excelente |
|
||||||
|
| Production-grade (TWDT, panic handlers, partitions, NVS) | Limitado | Completo | Completo |
|
||||||
|
| Complejidad inicial `platformio.ini` | Baja | Media | Media-alta (10 min de setup) |
|
||||||
|
| Curva de aprendizaje | Cortísima | Media | Media |
|
||||||
|
|
||||||
|
**Por qué para AR-Autopilot:**
|
||||||
|
|
||||||
|
El brief es taxativo:
|
||||||
|
|
||||||
|
> **Regla #11:** El PID corre en firmware ESP32, no en el display. Esto
|
||||||
|
> es no-negociable: latencia determinística, seguridad ante caída del
|
||||||
|
> display, eficiencia.
|
||||||
|
>
|
||||||
|
> **Sección 6 (frecuencias):** Lazo PID interno (timón a posición): 50
|
||||||
|
> Hz (20 ms) **Determinístico, sin jitter**.
|
||||||
|
|
||||||
|
"Sin jitter" en 50 Hz exige FreeRTOS real con prioridades correctas, no
|
||||||
|
el modelo abstraído de Arduino. Y al mismo tiempo no queremos perder 2
|
||||||
|
semanas reescribiendo NMEA2000_esp32 ni eModbus — son cientos de horas
|
||||||
|
de trabajo de la comunidad.
|
||||||
|
|
||||||
|
**Solución oficial Espressif:** [Arduino as an ESP-IDF component](https://docs.espressif.com/projects/arduino-esp32/en/latest/esp-idf_component.html).
|
||||||
|
Permite usar libs Arduino dentro de un proyecto ESP-IDF "puro". Es
|
||||||
|
exactamente lo que necesitamos.
|
||||||
|
|
||||||
|
**Configuración en PlatformIO (resumen):**
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[env:esp32-arautopilot]
|
||||||
|
platform = espressif32@~6.7.0
|
||||||
|
board = esp32dev ; ajustar al chip real (ESP32-DOWD)
|
||||||
|
framework = arduino, espidf ; ← aquí está el truco
|
||||||
|
|
||||||
|
; Pinneamos versión del core Arduino para reproducibilidad
|
||||||
|
platform_packages =
|
||||||
|
framework-arduinoespressif32@~3.20017.0
|
||||||
|
|
||||||
|
lib_deps =
|
||||||
|
ttlappalainen/NMEA2000-library
|
||||||
|
ttlappalainen/NMEA2000_esp32
|
||||||
|
https://github.com/eModbus/eModbus.git#v1.7.4.stable
|
||||||
|
|
||||||
|
build_flags =
|
||||||
|
-DCORE_DEBUG_LEVEL=3
|
||||||
|
-DCONFIG_FREERTOS_HZ=1000
|
||||||
|
; ... más flags al hacer el sprint
|
||||||
|
|
||||||
|
monitor_speed = 115200
|
||||||
|
```
|
||||||
|
|
||||||
|
**Riesgo conocido:** el modo dual `framework = arduino, espidf` a veces
|
||||||
|
se rompe entre versiones de PlatformIO. Mitigación: pinneamos versión
|
||||||
|
exacta del `platform` y de `framework-arduinoespressif32`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Hardware externo / transceivers (referencia)
|
||||||
|
|
||||||
|
Pinout `firmware/ar_autopilot_v1/src/hal/pinout.h` ya declara los pines
|
||||||
|
lógicos. Para que el sistema físicamente funcione, la tarjeta
|
||||||
|
AR-NMEA-IO debe tener:
|
||||||
|
|
||||||
|
| Función | Chip típico | Pin ESP32 |
|
||||||
|
|---|---|---|
|
||||||
|
| Transceiver CAN (NMEA 2000) | MCP2562, TJA1051T | GPIO16 (TX), GPIO4 (RX) — overridable |
|
||||||
|
| Transceiver RS-485 (Modbus) | MAX485, MAX3485, SP485E | GPIO21 (TX), GPIO22 (RX), GPIO23 (DE) |
|
||||||
|
| Acondicionamiento AI1 (rudder sensor) | Op-amp + divisor / módulo 4-20 mA loop | GPIO36 (ADC1_CH0) |
|
||||||
|
| Driver de potencia para DO1/DO2 (bomba) | Relé SSR o transistor + flyback | GPIO13, GPIO12 |
|
||||||
|
|
||||||
|
Asumo que tu tarjeta v1.0 ya los lleva (es la misma usada en
|
||||||
|
VMS-Sailor). Si hay alguna diferencia, lo cuadramos al inicio de
|
||||||
|
Fase 1.2 con tu esquemático.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Fuentes consultadas
|
||||||
|
|
||||||
|
- [NMEA2000_esp32 — GitHub](https://github.com/ttlappalainen/NMEA2000_esp32)
|
||||||
|
- [NMEA2000 library — GitHub](https://github.com/ttlappalainen/NMEA2000)
|
||||||
|
- [NMEA2000_esp32 — PlatformIO Registry](https://registry.platformio.org/libraries/ttlappalainen/NMEA2000_esp32)
|
||||||
|
- [eModbus — GitHub](https://github.com/eModbus/eModbus)
|
||||||
|
- [Arduino as ESP-IDF component — Espressif docs](https://docs.espressif.com/projects/arduino-esp32/en/latest/esp-idf_component.html)
|
||||||
|
- [ESP-IDF vs Arduino Framework for ESP32 — Hubble Network Community](https://hubble.com/community/comparisons/esp-idf-vs-arduino-framework-for-esp32-when-to-switch-and-why/)
|
||||||
|
- [ESP-IDF vs. Arduino for ESP32-S3: The Engineering Pivot to Production — Hoomanely Tech](https://tech.hoomanely.com/esp-idf-vs-arduino-for-esp32-s3-the-engineering-pivot-to-production/)
|
||||||
|
- [Modbus RTU slave (RS485) for ESP32 on Arduino platform — PlatformIO Community](https://community.platformio.org/t/modbus-rtu-slave-rs485-for-esp32-on-arduino-platform/16196)
|
||||||
@@ -0,0 +1,273 @@
|
|||||||
|
# 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`](../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:**
|
||||||
|
```powershell
|
||||||
|
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
|
||||||
Reference in New Issue
Block a user