Files
AR-VMS-Seaman/VMS_Sailor_v2_Parte_04_Hardware_Firmware.md
T
alro65 deb04c9315 sprint-0: fundaciones VMS-Sailor
Sprint 0 completo del producto VMS-Sailor (Vessel Management System
integrado para buques 30-40m). Brief de referencia en
VMS_Sailor_v2_Parte_*.md (intacto).

Core (vmssailor.core, 95.17% coverage, 99 tests verde):
- ShipCoord: sistema naval x_pp/y_cl/z_bl frozen
- Vessel, Deck, Bulkhead
- Equipment, EquipmentModel, Sensor, EquipmentSpec
- Tag, AlarmConfig, TagBinding, Scaling
- CardInstance, Bus, Topology con validacion 21 puntos I/O AR-NMEA-IO-v1.0
- Alarm, PermissiveRule, Condition
- Project agregado raiz con validacion cross-entity
- Persistencia portable .vmsproj (SQLite) con roundtrip verificable

Biblioteca curada seed (vmssailor.library):
- systems_catalog.json completo (catalogo maestro Parte 1 sec 7)
- 2 vessels: Sunseeker 76, Ferretti 850
- 2 motores: MTU 12V 2000 M96, Volvo D13-900
- 1 genset: Northern Lights M65C13
- yacht_motor_planeo.yaml (reglas heuristicas)
- TODO marcado data_source=seed_estimate - requiere validacion datasheets

Tools:
- vms-validate-library: CLI valida biblioteca completa
- vms-generate-test-project: CLI demo + verificacion roundtrip persistencia

Design System + 8 mockups HTML estaticos:
- docs/design_system.md (paleta Deep Ocean, gradientes, typography, motion)
- docs/brand/ (logo + variantes SVG)
- docs/mockups/splash, studio_main, runtime_overview,
  runtime_mimic_fuel (P&ID animado), runtime_alarms, runtime_trim (panel
  estrella con horizonte artificial), mobile_overview, mobile_trim
- docs/mockups/index.html (galeria)

Firmware (Sprint 12+ implementacion):
- firmware/ar_nmea_io_v1/src/config/pinout.h con macros GPIO

Decisiones autonomas documentadas en docs/decisions_sprint0.md.

Stack: Python 3.11 + uv + Pydantic v2 + SQLite stdlib + hatchling +
pytest 9 + ruff + mypy. Sin PySide6, FastAPI, Flutter ni firmware
funcional (entran en sprints siguientes).

Criterio de aceptacion Sprint 0: cumplido.
- uv sync: OK
- pytest: 99/99 verde
- cov vmssailor.core: 95.17% (objetivo >=80%)
- ruff: clean
- vms-validate-library: OK
- vms-generate-test-project: INTEGRIDAD OK

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

15 KiB
Raw Blame History

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).