Arquitectura de buses y fronteras de comunicacion

Documento de arquitectura · 2026-03-30
Como se comunican los datos entre componentes, pestañas, maquinas y hardware fisico

Las 4 fronteras de comunicacion

Antes de diseñar un bus hay que entender donde vive cada dato y que barreras tiene que cruzar para llegar a su destino. Cada frontera tiene mecanismos diferentes, costes diferentes, y limitaciones diferentes.

Frontera 1 — In-memory (misma pestaña) Variables JS, React context, refs, callbacks. Latencia: 0. Sin serializar. Sim1Bus DebugBus React Context DOM Events Float64Array Event emitter Provider/hook CustomEvent Serializar (structured clone) Frontera 2 — Mismo navegador, diferentes contextos Pestañas, Workers, ServiceWorker. APIs del browser: BroadcastChannel, SharedWorker, localStorage. Uso futuro: instructor y alumno en pestañas separadas, Web Worker para SimEngine pesado. TCP/IP (encode + transmit) Frontera 3 — Red (otros equipos y servidores) TCP/IP. MQTT, WebSocket, STOMP, AMQP, HTTP. Latencia variable. CommsManager RabbitMQ Node-RED Adaptadores PWA Hub multi-protocolo Orquestacion flujos ESP32 ↔ RabbitMQ ↔ Node-RED ↔ PWA · Instructor ↔ RMQ ↔ Alumno Gateway (ESP32, USB, dongle) Frontera 4 — Hardware fisico (señales electricas) ARINC 429 bipolar RZ, MIL-STD-1553 Manchester, CAN diferencial, RS-485, BLE radio Sim1 + Analyzer1 simulan esta frontera in-memory (Frontera 1 aparentando ser Frontera 4) Cada frontera requiere buses y mecanismos diferentes F1: acceso directo sin coste · F2: serializar dentro del browser · F3: TCP/IP + broker · F4: señal electrica + gateway Un dato puede cruzar las 4 fronteras: ESP32 (F4) → MQTT (F3) → RabbitMQ → PWA (F1) → Instrumento

Que implica cada frontera

FronteraLatenciaSerializacionAncho de bandaQuien controla
F1 In-memory0 (mismo tick)No necesariaIlimitado (RAM)Nuestro codigo JS
F2 Browser~1msStructured cloneAlto (mismo SO)APIs del browser
F3 Red1-500msJSON / binarioDepende de redTCP/IP + RabbitMQ
F4 Hardwareµs (determinista)Codificacion nativaFijo (bitrate del bus)Protocolo electrico

La decision clave de diseño: no enviar datos por una frontera superior a la necesaria. Si Sim1 y Col4 estan en la misma pestaña, los datos deben fluir por F1 (in-memory), no por F3 (MQTT via RabbitMQ). Solo se cruza F3 cuando instructor y alumno estan en maquinas diferentes.

Sim1Bus — bus binario de alta frecuencia

↑ Fronteras

Frontera 1 — in-memory. Diseñado para 60+ sensores a 60fps.

Bus interno de Sim1 optimizado para datos numericos de alta frecuencia. No es un event emitter — es un buffer compartido que los consumidores leen directamente.

Diseño del buffer

Estructura del Sim1Bus buffer (total ~680 bytes para 60 sensores):

Header (8 bytes):
  [0] Uint32: version global (incrementa cada tick)
  [1] Uint32: timestamp (ms desde inicio de simulacion)

System directory (4 bytes por sistema × 18 sistemas = 72 bytes):
  [systemIndex × 2]     Uint16: offset al bloque de datos
  [systemIndex × 2 + 1] Uint16: version del bloque (cambia solo si algun sensor del sistema cambio)

Sensor blocks por sistema (10 bytes por sensor × 60 sensores = 600 bytes):
  [offset + 0] Float32: valor actual
  [offset + 4] Float32: valor override (NaN si no hay override)
  [offset + 8] Uint8:   status (0=ok, 1=caution, 2=warning, 3=fail)
  [offset + 9] Uint8:   flags (bit0=override, bit1=frozen, bit2=faulted, bit3=pinned)

Patron productor / consumidor

Productor (SimulationEngine)

Escribe valores directamente en el buffer. Incrementa la version global y la version del sistema afectado. Sin callbacks, sin allocations.

// SimEngine escribe en cada tick
buffer.values[sensorIndex] = newValue;
buffer.status[sensorIndex] = sensorStatus(sensor, newValue);
Atomics.add(buffer.version, 0, 1);
buffer.systemVersion[systemIndex]++;

Consumidores (pull-based)

Cada consumidor guarda su ultimo lastVersion. En su RAF/tick, compara con la version global. Si cambio, lee los datos que necesita. Zero-copy.

// TabChart (30fps) — lee solo si hay datos nuevos
const currentVersion = Atomics.load(buffer.version, 0);
if (currentVersion !== this.lastVersion) {
  // Leer solo los sensores activos
  activeSensors.forEach(s => {
    const val = buffer.values[s.index];
    this.pushToRingBuffer(s.id, val);
  });
  this.lastVersion = currentVersion;
}

Consumidores y frecuencias

ConsumidorFrecuenciaQue leePor que necesita el bus
TabChart (strip chart)30-60fpsValores de sensores activosCanvas necesita datos a alta frecuencia
TabMatrix (tabla)4fpsValores + status de todos los sensores del displayActualizar tabla cada 250ms
TabAlertsOn-changeStatus changes (ok→caution, caution→warning)Solo reacciona a cruces de umbral
InstrumentBridge30fpsValores para instrumentos enlazadosMover los gauges en Col4
Analyzer1VariableValores para encode con ProtocolAdapterGenerar frames para inspeccion
FDR recorder1-10fpsSnapshot completo del bufferGrabar serie temporal
DebugBus bridgeOn-eventSolo eventos discretos (fault, override)Logging, no datos continuos

Por que no usar DebugBus para esto

DebugBus esta diseñado para eventos de texto con nivel y categoria. Publicar 60 sensores × 60fps = 3600 eventos/segundo lo saturaria. Sim1Bus es un buffer numerico compartido que los consumidores leen cuando necesitan — pull, no push. La diferencia fundamental: DebugBus notifica a los suscriptores (push), Sim1Bus expone los datos para lectura (pull).

Por que no usar CommsManager para esto

CommsManager esta diseñado para cruzar la Frontera 3 (red). Publicar por MQTT 3600 veces/segundo para datos que van a ser consumidos en la misma pestaña es un desperdicio absurdo: serializar JSON, pasar por el WebSocket al broker, volver por otro WebSocket, deserializar. Sim1Bus evita todo eso — acceso directo en memoria.

Solo cuando los datos necesitan salir del browser (instructor remoto, FDR en RabbitMQ Streams) se usa el SimulationCommsAdapter para publicar por CommsManager. Y lo hace a frecuencia reducida (1-10fps, no 60fps).

DebugBus — bus de eventos de diagnostico

↑ Sim1Bus

Frontera 1 — in-memory. Diseñado para eventos de texto con nivel y categoria.

Bus de diagnostico existente, usado por Debug1, ObjectRegistryContext, TabLog, TabConfig. Patron event emitter clasico: dbg.registry('info', 'hwObj registrado', data).

Canales existentes

CanalProductor principalTipo de eventos
registryObjectRegistryContexthwObj register/unregister, enlace create/delete, channel exclusivity
commsCommsManagerAdapter connect/disconnect, message send/receive, status change
bridgeInstrumentBridgeEnlazar swObj↔hwObj, channel mapping, animate dispatch
navPagesRoute change, page telemetry
chatTabChatMessages sent/received, _execCommand
simSim1 (pendiente)Engine start/stop, scenario change, fault inject, threshold crossed
analyzerAnalyzer1 (pendiente)Protocol selected, capture start/stop, frame inspected

Relacion con Sim1Bus

Sim1Bus y DebugBus son complementarios, no competidores:

Sim1Bus transporta datos numericos de alta frecuencia (valores de sensores, 60fps). Solo datos. Sin texto, sin niveles, sin categorias.

DebugBus transporta eventos discretos con contexto humano (fault inyectado, escenario cambiado, umbral cruzado). Baja frecuencia, alta legibilidad.

Un puente entre ambos: cuando un sensor cruza un umbral (detectado leyendo Sim1Bus), se emite un evento al DebugBus canal 'sim': dbg.sim('warn', 'EGT_L crossed caution: 852°C', { sensor: 'EGT_L', value: 852, threshold: 850 }).

CommsManager — bus de red

↑ DebugBus

Frontera 3 — Red. Diseñado para comunicacion entre maquinas via TCP/IP.

Orquestador de adaptadores de transporte. Cada adaptador implementa la misma interfaz (connect, disconnect, subscribe, publish). La app no sabe si los datos llegan por MQTT, WebSocket o STOMP.

Adaptadores activos y planificados

AdaptadorFronteraTransporteEstadoUso
MqttAdapterF3MQTT 5 sobre TCP :1883ActivoHardware ESP32 → broker → PWA
WebSocketAdapterF3WebSocket :1880ActivoNode-RED ↔ PWA bidireccional
StompAdapterF3STOMP/WS :15674PendienteRabbitMQ directo desde browser
LoopbackAdapterF1In-memoryActivoTesting sin hardware
BleAdapterF3+F4Web Bluetooth APIPlanificadoSensores BLE directos
SerialAdapterF3+F4Web Serial APIPlanificadoDebug directo USB
SimulationCommsAdapterF1→F3Publica por CommsManagerPendienteSim1 datos → red (instructor remoto)

Cuando usar CommsManager vs Sim1Bus

Sim1Bus — cuando productor y consumidor estan en la misma pestaña. Sim1 → TabChart, Sim1 → InstrumentBridge local, Sim1 → Analyzer1 embed.
CommsManager — cuando los datos necesitan cruzar la red. Sim1 → MQTT → RabbitMQ → PWA del alumno. ESP32 → MQTT → PWA. Instructor → WS → Node-RED → alumno.
Ambos — cuando Sim1 opera en modo distribuido. Sim1Bus alimenta los consumidores locales a 60fps. SimulationCommsAdapter lee del bus a 1-10fps y publica por CommsManager para consumidores remotos.

RabbitMQ — hub central de la Frontera 3

↑ CommsManager

RabbitMQ es el unico componente que habla 5 protocolos simultaneamente y traduce entre ellos.

Cada productor entra por la puerta que le conviene (MQTT, AMQP, STOMP) y cada consumidor sale por la suya. La traduccion es interna y transparente.

Infraestructura actual

PuertoProtocoloQuien lo usa
1883MQTT 5.0ESP32 hardware, SimulationCommsAdapter
5672AMQP 0-9-1Node-RED (nodos amqp-in/out)
15674STOMP/WebSocketPWA browser (StompAdapter)
5552StreamsFDR recorder, replay historico
15672Management HTTPAdministracion, Analyzer1 estadisticas

Flujo de datos tipico

Hardware real: ESP32 → MQTT :1883 → RabbitMQ exchange amq.topic → binding → queue → Node-RED consume por AMQP → procesa → publica WS → PWA recibe por WebSocketAdapter → Registry → Bridge → Col4
Simulacion local: Sim1 engine → Sim1Bus (F1 in-memory) → InstrumentBridge → Col4. Sin RabbitMQ. Sin red.
Simulacion remota: Sim1 engine → Sim1Bus → SimulationCommsAdapter lee bus → publica MQTT → RabbitMQ → STOMP/WS → PWA alumno en otra maquina → Col4
FDR grabacion: Sim1 engine → Sim1Bus → FDR recorder lee bus cada 100ms → publica a RabbitMQ Stream → persiste en disco → replay posterior leyendo desde offset

RabbitMQ + Analyzer1

Analyzer1 tiene 3 puntos de intercepcion del trafico:

Tap interno (F1): Lee directamente del Sim1Bus o intercepta las llamadas encode() de los ProtocolAdapters. Todo in-memory, sin latencia.

Tap MQTT (F3): Suscribe a lru/# via MqttAdapter para ver el trafico real del hardware.

Tap Stream (F3): Lee historico desde RabbitMQ Streams para analisis post-mortem. Como un FDR replay.

En los tres casos, Analyzer1 aplica el ProtocolAdapter correspondiente para decodificar e inspeccionar los datos.

APIs del browser — Frontera 2

↑ RabbitMQ

La Frontera 2 (comunicacion entre contextos del mismo browser) no la usamos actualmente, pero es importante conocerla para decisiones futuras.

Mecanismos disponibles

APIQue haceSerializacionUso potencial
BroadcastChannelMensajes entre pestañas del mismo origenStructured cloneSync estado entre pestañas (instructor/alumno mismo PC)
SharedWorkerWorker compartido entre pestañaspostMessageHub de datos compartido: un SimEngine en un Worker, multiples pestañas consumen
Web WorkerThread separado para computo pesadopostMessage o SharedArrayBufferMover SimulationEngine a un worker para no bloquear el UI thread
SharedArrayBufferMemoria compartida entre threadsNo necesita (acceso directo)Sim1Bus podria ser un SAB si el engine se mueve a un Worker. Requiere COOP/COEP headers.
ServiceWorkerProxy de red + cache offlinePWA offline: cachear datos de simulacion, ultimo estado conocido
localStoragePersistencia sincrona key-valueString (JSON.stringify)Guardar workspaces, preferencias, ultimo escenario
IndexedDBBase de datos local asincronaStructured cloneAlmacenar grabaciones FDR locales, catalogo offline

SharedArrayBuffer — el puente entre F1 y F2

Si en el futuro movemos SimulationEngine a un Web Worker (para no bloquear el UI thread con el game loop de 60fps), necesitaremos transferir 60 sensores × 60fps entre threads. postMessage serializa y copia — demasiado lento. SharedArrayBuffer permite que ambos threads accedan al mismo bloque de memoria sin copia.

El diseño de Sim1Bus con Float64Array + Uint32Array version counter esta preparado para esta migracion. Si el backing store es un SharedArrayBuffer en vez de un ArrayBuffer, el Worker escribe y el UI thread lee sin serializar. Solo necesita Atomics para el version counter.

Requisito: SharedArrayBuffer necesita los headers Cross-Origin-Opener-Policy: same-origin y Cross-Origin-Embedder-Policy: require-corp. Caddy puede configurarlos.

Resumen — que bus usar y cuando

↑ Browser APIs Arbol de decision — que bus usar Necesito enviar datos de A a B Misma pestaña Otra maquina Numerico 60fps Evento discreto Sim1Bus Float64Array DebugBus Event emitter CommsManager via RabbitMQ Otra pestaña BroadcastChannel Structured clone Sim1Bus 60 sensores × 60fps. Buffer binario pull-based. Zero-copy. In-memory. DebugBus Eventos discretos con texto, nivel, canal. Push-based. 10-50 eventos/s. Diagnostico. CommsManager Datos entre maquinas. MQTT/WS/STOMP via RabbitMQ. 1-10 msg/s. Serializado JSON.

Buses necesarios en la LRU Platform

BusFronteraEstadoTipo de datosFrecuenciaConsumidores
Sim1BusF1DiseñadoNumerico binario60fpsTabChart, Matrix, Alerts, Bridge, Analyzer1, FDR
DebugBusF1ActivoEventos textoOn-eventDebug1, TabLog, TabConfig
CommsManagerF3ActivoMensajes redVariablePWA, Node-RED, hardware, instructor
React ContextF1ActivoEstado UIOn-changeComponentes React que necesitan re-render
DOM EventsF1ActivoswObj, events2On-eventRegistry, Bridge (legacy compatibility)

Solo Sim1Bus es nuevo. Los demas ya existen y funcionan. La clave arquitectonica es que cada bus tiene su dominio: Sim1Bus para datos numericos de alta frecuencia, DebugBus para diagnostico, CommsManager para la red. No compiten — se complementan.

Documentos relacionados

DocumentoRelacion
Analyzer1 — Plataforma de protocolosUsa Sim1Bus como fuente de datos, CommsManager para tap MQTT/AMQP, RabbitMQ Streams para replay
Sim1 — Sistema de simulacionSim1Bus es el bus principal de datos. SimulationCommsAdapter publica por CommsManager
Estado tecnicoCommsManager, DebugBus, infraestructura Docker/RabbitMQ
Roadmap s6+Sprint 1 implementa wire-up con Sim1Bus, Sprint 3 conecta con Analyzer1

LRU Platform — Arquitectura de buses y fronteras de comunicacion
2026-03-30 · Claude Opus 4.6
Simulacion · Estado tecnico · Analyzer1 · Roadmap