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>
15 KiB
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
- Álvaro publica nueva versión de firmware en el Studio (ej: 1.5.0)
- Al generar el
.vmspacko.vmsdelta, incluye el.bin - El Runtime, vía VPN o presencial, recibe el paquete
- El Admin del buque aprueba la actualización
- El Runtime envía el
.bina cada tarjeta vía Modbus o WiFi - 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.hy 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).