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>
This commit is contained in:
2026-05-17 07:26:06 -04:00
commit deb04c9315
96 changed files with 15335 additions and 0 deletions
+401
View File
@@ -0,0 +1,401 @@
# 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).