# VMS-Sailor · Brief para Claude Code · Parte 4 de 6 ## Hardware AR-NMEA-IO-v1.0 + Firmware ESP32 > Esta parte detalla la tarjeta de I/O y su firmware. Asume que ya leíste Partes 1-3. --- ## 1. Descripción física de la tarjeta La tarjeta `AR-NMEA-IO-v1.0` es hardware diseñado por Álvaro, una sola SKU física que se replica idénticamente para todos los proyectos. Lo que varía entre proyectos es la **configuración** descargada desde el VMS, no el hardware. ### Capacidades fijas | Recurso | Cantidad | Detalle | |---|---|---| | Salidas digitales (DO) | 10 | MOSFET IRLML6344TRPBF, opto PC817, diodo flyback SS14, 30V/5A | | Entradas digitales (DI) | 5 | Optoacopladas PC817, contacto o 24VDC, resistencia limitadora 4.7kΩ | | Entrada frecuencia (RPM) | 1 | Optoacoplada, pickup magnético / tacómetro / inductivo | | Entradas analógicas (AI) | 4 | Con divisores y filtros V3061, ADC del ESP32 (12 bits + oversampling) | | Microcontrolador | 1 | ESP32-DOWD (módulo ESP32 DevKit china) | | Bus serie | RS485 | Transceiver SN65HVD1781 (TI), ESD ±16kV, conector RJ45 | | Bus CAN | NMEA 2000 | Transceiver MCP2562T-E_MF (Microchip), ESD SP0502, conector estándar | | WiFi | Integrado | Del ESP32, usado para OTA y MQTT opcional | | USB | 1 | Para programación inicial (CH340 o CP2102 del DevKit) | | Alimentación | 12 VDC | Conector Phoenix de 2 vías, TVS SMET24A, ferrita B2U-1000P | | Reguladores | 2× MP2338 | Bucks 12V→5V y 5V→3.3V | **Aislamiento galvánico:** TODOS los I/O (DI, DO, RPM) están aislados con optoacopladores PC817. Un fallo eléctrico en un sensor o actuador NO daña el ESP32. **Total: 21 puntos I/O por tarjeta.** ### GPIOs del ESP32 usados (Información extraída del esquemático real que me pasó Álvaro:) - **DO**: GPIO15, GPIO5, GPIO17, GPIO16 (cuidado: este es Rx2), GPIO18, GPIO22, GPIO25, GPIO26, GPIO27, GPIO34 (cuidado: solo entrada normalmente) - **DI**: GPIO13, GPIO2, GPIO0, GPIO4, GPIO14 (algunos son boot-strap) - **AI**: GPIO33 (BAT), GPIO34 (SPARE2), GPIO32 (WATER), GPIO35 (OILP) - **RPM**: GPIO15 o similar (verificar en esquemático) - **RS485**: GPIO04 (DE/RE), pines TX/RX UART - **CAN**: GPIO21 (TX), GPIO23 (RX), GPIO22 (STBY) **Nota para Claude Code:** Los pines exactos los verificaremos en el firmware. Algunos GPIO del ESP32 tienen restricciones (strapping, solo entrada, conflictos con boot). El firmware debe respetar las asignaciones reales del schematic. --- ## 2. Filosofía del firmware ### Universal y autoconfigurable **Una sola SKU física = un solo firmware base = una sola variante de tarjeta.** La tarjeta sale de fábrica con firmware idéntico. Su rol y configuración se descargan del VMS al conectarse al bus. Patrón **"plug-and-produce"** estándar industrial moderno (similar a Beckhoff EtherCAT, Wago I/O System). ### Lo que NO se programa por hardware - Qué hace cada DO (eso lo decide el VMS) - Qué lee cada DI (eso lo decide el VMS) - Qué tipo de sensor entra en cada AI (eso lo decide el VMS, dentro de los rangos físicos que el divisor permite) - Dirección Modbus de la tarjeta (la asigna el VMS, junto con confirmación del dipswitch físico) - Filtros, alarmas locales, permissives locales (todo desde el VMS) ### Lo que SÍ va fijo en el firmware - Pinout: qué GPIO corresponde a qué función (DO1=GPIO15, AI1=GPIO33, etc.) - Stack de protocolos: Modbus RTU, NMEA 2000, J1939, MQTT (siempre disponibles, se activan según config) - Stack de WiFi y OTA - Boot sequence con discovery broadcast - Cliente OTA seguro - Watchdog y recuperación ante fallos - Buffer offline (60s en flash) --- ## 3. Estructura del firmware ``` firmware/ar_nmea_io_v1/ ├── platformio.ini # PlatformIO o ESP-IDF ├── README.md ├── src/ │ ├── main.cpp # Entry point + boot sequence │ ├── config/ │ │ ├── pinout.h # Definición física de GPIOs │ │ ├── card_identity.cpp # Serial + HWID de cada tarjeta │ │ ├── dipswitch_reader.cpp # Lee dipswitches físicos │ │ └── runtime_config.cpp # Aplica config descargada del VMS │ │ │ ├── channels/ # Manejo de los 21 puntos I/O │ │ ├── do_handler.cpp # 10 DO │ │ ├── di_handler.cpp # 5 DI │ │ ├── rpm_handler.cpp # 1 entrada frecuencia │ │ └── ai_handler.cpp # 4 AI con oversampling │ │ │ ├── filters/ # Procesamiento local │ │ ├── moving_average.cpp │ │ ├── median.cpp │ │ ├── deadband.cpp │ │ └── rate_limit.cpp │ │ │ ├── protocols/ │ │ ├── modbus_slave.cpp # Modbus RTU esclava │ │ ├── modbus_master.cpp # Modbus RTU maestra (si así se configura) │ │ ├── nmea2000_node.cpp # Publica y suscribe PGNs │ │ ├── j1939_listener.cpp # Escucha J1939 (motores) │ │ ├── mqtt_client.cpp # MQTT WiFi (opcional) │ │ └── discovery.cpp # Discovery broadcast al arrancar │ │ │ ├── local_logic/ # Lógica local en la tarjeta │ │ ├── local_alarms.cpp # Alarmas locales con umbrales │ │ ├── local_permissives.cpp # Permissives locales críticos (E-stop) │ │ └── safety_outputs.cpp # Salidas de seguridad (cortar relé si E-stop) │ │ │ ├── storage/ │ │ ├── nvs_config.cpp # Configuración en NVS (flash) │ │ └── offline_buffer.cpp # Buffer 60s en flash si bus cae │ │ │ ├── system/ │ │ ├── watchdog.cpp │ │ ├── ota_client.cpp # Cliente OTA seguro │ │ ├── health_reporter.cpp # Reporta estado al maestro │ │ └── reboot_handler.cpp │ │ │ └── utils/ │ ├── crc.cpp │ ├── logger.cpp │ └── time_sync.cpp │ ├── common/ # Librerías compartidas con futuros firmwares │ ├── modbus_lib/ │ ├── nmea2000_lib/ │ └── ota_lib/ │ └── test/ # Tests del firmware (PlatformIO test framework) ├── test_pinout.cpp ├── test_modbus.cpp ├── test_filters.cpp └── test_discovery.cpp ``` --- ## 4. Boot sequence (plug-and-produce) Cuando la tarjeta arranca por primera vez (o después de un reset): ### Paso 1 — Identidad - Lee su serial number del NVS (asignado en fábrica) - Lee los dipswitches físicos para obtener su "slot number" en el bus (1-16) - Lee firmware version ### Paso 2 — Inicialización de hardware - Inicializa GPIOs según `pinout.h` - Pone todos los DO en estado seguro (apagados) - Inicializa transceivers RS485 y CAN - Inicializa ADC para AI - Inicia WiFi en modo cliente (si hay credenciales guardadas) o AP (si no) ### Paso 3 — Discovery broadcast Por RS485 envía un mensaje broadcast Modbus: ``` [broadcast addr 0] function: 65 (custom) payload: { serial: "ARC-2026-00153", hw_version: "1.0", fw_version: "1.4.2", dipswitch_slot: 5, capabilities: { do:10, di:5, rpm:1, ai:4 } } ``` ### Paso 4 — Espera config del maestro - Si el maestro (VMS) responde con `CONFIG_PUSH` → aplica la configuración recibida - Si no responde en 10s → modo "huérfana": espera silencio, reintenta cada 30s ### Paso 5 — Config aplicada La configuración recibida incluye: - Su rol: ESCLAVA con dirección Modbus N - Modo de bus: solo Modbus, solo NMEA 2000, dual, o bridge - Asignación de cada uno de los 21 puntos a un tag específico (con su escalado, tipo de señal, filtros) - Permissives locales (lista de condiciones if-then críticas) - Alarmas locales (umbrales que dispararán acciones locales sin esperar al VMS) - Frecuencia de reporte - Configuración WiFi para OTA y MQTT (opcional) La guarda en NVS para persistencia. ### Paso 6 — Modo operativo A partir de aquí, ciclo principal: - Lee canales según frecuencia configurada - Aplica filtros locales - Evalúa alarmas locales y permissives locales - Responde a queries Modbus del maestro - Publica PGNs NMEA 2000 si está habilitado - Envía health beacon cada 60s --- ## 5. Modos de operación de bus Configurable por software desde el VMS: ### Modo "Modbus solo" - Solo RS485 activo, CAN apagado - Esclava Modbus con dirección N - Útil para tarjetas en sistemas auxiliares (tanques, FiFi, A/A, luces) ### Modo "NMEA 2000 solo" - Solo CAN activo, RS485 apagado (o solo escucha) - Publica PGNs estándar al backbone - Útil cuando la tarjeta es de un sistema que se quiere exponer al ecosistema marino (motor, genset) ### Modo "Dual" (recomendado para motores y gensets) - RS485 + CAN ambos activos - Reporta al VMS por Modbus para control fino - Publica al backbone NMEA 2000 para que plotters Garmin/Raymarine vean datos del motor - Doble alcance, mismo dato ### Modo "Bridge" - Escucha CAN/NMEA 2000 y traduce a Modbus - Útil para tener un equipo que solo habla NMEA 2000 (ej: sensor de viento Airmar) y que el VMS pueda leerlo --- ## 6. PGNs NMEA 2000 que publica la tarjeta Cuando una tarjeta está configurada en modo NMEA 2000 (típicamente motores y gensets): | PGN | Nombre | Datos publicados | |---|---|---| | 127488 | Engine Parameters Rapid | RPM, boost pressure, trim | | 127489 | Engine Parameters Dynamic | Presión aceite, temp aceite, temp refrigerante, voltaje alternador, % carga, horas | | 127493 | Transmission Parameters | Presión aceite caja, temperatura | | 127505 | Fluid Level | Tanques combustible/agua/aceite (si la tarjeta es de tanques) | | 127508 | Battery Status | Voltaje, corriente, temp batería | | 127257 | Attitude | (consume del backbone, no publica — viene del AR-ECDIS) | Esto le da al cliente un beneficio gratis: el plotter del puente muestra automáticamente datos del motor sin configuración extra. --- ## 7. Procesamiento local en la tarjeta Esto es lo que diferencia tu sistema de tarjetas Modbus pasivas: ### Filtros locales Configurables por canal desde el VMS: - **Moving average**: promedia últimas N muestras - **Median**: filtra spikes (excelente para señales con ruido EMI) - **Deadband**: solo reporta si el cambio supera X - **Rate limit**: rechaza cambios bruscos (anti-glitch) Beneficio: reduce tráfico Modbus 10× típicamente, y mejora calidad de la señal. ### Alarmas locales La tarjeta tiene umbrales propios y puede activar una salida si una entrada se sale de rango, sin esperar al VMS. Ejemplo: ``` IF AI1 (temp aceite) > 110°C THEN DO1 (relé alarma local) = ON AND envía mensaje urgente al VMS por Modbus ``` Tiempo de reacción: <50ms (vs 200-500ms si pasa por el VMS). ### Permissives locales críticos Cuando un permissive es de seguridad funcional (E-stop, sobre-velocidad), se ejecuta LOCAL en la tarjeta: ``` IF DI2 (pulsador E-stop) == ACTIVO THEN DO1 (relé arranque motor) = OFF inmediato AND DO2 (parada emergencia) = ON AND envía evento al VMS ``` Esto cumple con SIL (Safety Integrity Level) básico — el corte no depende de que el bus esté disponible. --- ## 8. OTA (Over-The-Air firmware update) El ESP32 soporta OTA nativo y maduro. Stack del firmware: ### Flujo OTA 1. Álvaro publica nueva versión de firmware en el Studio (ej: 1.5.0) 2. Al generar el `.vmspack` o `.vmsdelta`, incluye el `.bin` 3. El Runtime, vía VPN o presencial, recibe el paquete 4. El Admin del buque aprueba la actualización 5. El Runtime envía el `.bin` a cada tarjeta vía Modbus o WiFi 6. La tarjeta: - Verifica firma criptográfica del firmware (clave pública de Álvaro embebida) - Verifica que la versión es compatible - Guarda en partición OTA - Reinicia - Si arranca OK → confirma al VMS - Si falla → rollback automático a la partición anterior ### Seguridad OTA - Firma RSA-2048 o Ed25519 del firmware - Verificación obligatoria antes de aplicar - Sin firma válida = rechazado - Rollback automático si nuevo firmware no responde en 60s ### Tiempo de actualización - Por Modbus RTU a 115200 baud: ~2 min por tarjeta de 500KB - Por WiFi: ~10 segundos - Para 7 tarjetas, total ~5-15 minutos --- ## 9. Comunicación tarjeta ↔ VMS ### Mensajes Modbus custom (function codes propietarios) Además del Modbus estándar (read holding, write coils, etc.), agregamos function codes custom para gestión: | FC | Nombre | Propósito | |---|---|---| | 65 | DISCOVERY_BROADCAST | Tarjeta anuncia presencia | | 66 | CONFIG_PUSH | VMS empuja configuración | | 67 | CONFIG_ACK | Tarjeta confirma config aplicada | | 68 | HEALTH_BEACON | Tarjeta reporta su salud cada 60s | | 69 | OTA_BEGIN | Inicia transferencia firmware | | 70 | OTA_CHUNK | Bloque de firmware | | 71 | OTA_END | Finalizar y reiniciar | | 72 | LOCAL_ALARM_EVENT | Tarjeta notifica alarma local | | 73 | LOCAL_PERMISSIVE_TRIGGERED | Tarjeta notifica permissive ejecutado | --- ## 10. Identificación de tarjetas (reemplazo de tarjeta dañada) Combinación A+C (decisión Álvaro): ### Dipswitches físicos Cada tarjeta tiene 4-5 dipswitches que el técnico setea al instalarla. Indican el "slot number" en el bus (1-16). La tarjeta envía este número en su discovery. ### Confirmación en pantalla del Runtime Cuando se conecta una tarjeta nueva (slot que estaba vacío o slot que estaba con otra tarjeta serial), el Runtime alerta: ``` Nueva tarjeta detectada: Serial: ARC-2026-00187 Slot dipswitch: 5 Reemplaza a: ARC-2026-00153 (Motor PORT) [confirmado por Álvaro o Técnico] [Aceptar y reasignar rol] [Rechazar - no esperaba esta tarjeta] ``` Si los dipswitches no coinciden con lo esperado, alarma: "Tarjeta con dipswitch 7 conectada en slot 5 - corregir dipswitch o reasignar en Studio". --- ## 11. Sprints relacionados con firmware - **Sprint 0** (parte): definición de `pinout.h` y estructura básica del firmware - **Sprint 12**: firmware completo Modbus RTU esclava + discovery + config push + filtros locales - **Sprint 13**: NMEA 2000 publishing + J1939 listening - **Sprint 14**: OTA + health reporter + permissives locales + alarmas locales - **Sprint 15**: pruebas de stress, watchdog, recovery **Nota:** El firmware ES parte del proyecto VMS-Sailor (mismo repositorio, mismo control de versiones). Va junto con el resto. --- ## 12. Stack tecnológico del firmware | Componente | Tecnología | |---|---| | Framework | PlatformIO + Arduino framework O ESP-IDF (a decidir en Sprint 12) | | Lenguaje | C++ | | Modbus RTU | `eModbus` o `ArduinoModbus` | | NMEA 2000 | `NMEA2000_esp32` library | | J1939 | librería específica | | OTA | `ArduinoOTA` o `esp_ota` de ESP-IDF | | MQTT | `PubSubClient` o `AsyncMqttClient` | | Filesystem (NVS) | nativo ESP32 | | Tests | Unity + PlatformIO test runner | --- **Fin de Parte 4 de 6.** Próxima: Parte 5 — VMS-Sailor Mobile (Flutter).