Saltar a contenido

Stack tecnológico

Elecciones

Capa Elección Estado
Lenguaje TypeScript, un solo proyecto firme (D-04)
Runtime Node LTS firme
Servidor Modbus modbus-serial (ServerTCP) firme, verificado en fase 0 (D-17)
UDP node:dgram firme, sin dependencia
WebSocket ws firme
Registro de eventos SQLite con better-sqlite3, modo WAL firme
Validación de configuración JSON Schema con Ajv firme
Interfaz SPA ligera servida por el mismo proceso firme
Despliegue Docker Swarm + cloudflared firme (D-05)
Documentación MkDocs Material firme

Por qué un solo proceso y un solo lenguaje

Núcleo, servidor Modbus, emisor UDP, servidor WebSocket e interfaz viven en el mismo proceso Node (D-01). Un salto de red entre el adaptador Modbus y el núcleo añadiría jitter que no existe en el radar real, y el banco existe para medir jitter.

El riesgo técnico principal — resuelto en fase 0

Múltiples unit IDs sobre una sola conexión TCP

El diseño exige que el servidor atienda diez unit IDs sobre la misma conexión TCP, porque las direcciones de los módulos ADAM colisionan entre sí y la resolución es un unit ID por módulo físico (Modbus TCP).

Prueba de concepto de la fase 0: jsmodbus no lo soporta (buffer único compartido entre unit IDs, sin importar request.unitId). modbus-serial sí, dejando options.unitID sin fijar y enrutando a mano por el unitID que la librería pasa a cada callback del vector. También expone excepción Modbus ante FC05 sobre coil de solo lectura, y el vector puede capturar el instante de recepción con process.hrtime.bigint() (resolución de nanosegundos; el jitter del setTimeout(0) interno de la librería añade ~0,6-2,2 ms de latencia hasta la captura, medido sin carga). Detalle completo en D-17.

Códigos de función a implementar

Solo seis, por cómo Advantech usa los espacios de direcciones:

FC Función Uso
01 Read Coils DI y DO
03 Read Holding Registers AI y AO
05 Write Single Coil DO
06 Write Single Register AO
15 Write Multiple Coils DO
16 Write Multiple Registers AO

FC02 y FC04 no se usan: las entradas digitales viven en 0X y las analógicas en 4X. Ver Modbus TCP.

Estructura del repositorio

rd100s-emu/
├── src/
│   ├── core/          # estado, tick, grafo, modos de señal
│   ├── config/        # esquema, validación, carga
│   ├── adapters/
│   │   ├── modbus/    # servidor, mapeo, conversión de crudo
│   │   ├── udp/       # emisor de encoder y degradaciones
│   │   └── ws/        # telemetría, eventos, órdenes
│   ├── log/           # SQLite, sesiones, descarga
│   ├── blocks/        # biblioteca de comportamiento (fase 2)
│   └── ui/            # SPA
├── config/            # rd100s.json y otras configuraciones
├── scenarios/         # escenarios y aserciones (fase 3)
├── docs/              # este sitio
├── stack.yml
└── mkdocs.yml

El núcleo no importa nada de adapters/. La dependencia va en un solo sentido y conviene imponerlo con una regla de lint, no solo por convención: es lo que mantiene el modelo independiente de los transportes.

Convenciones

  • Reloj monótono en todo lo que se registre o se emita. Nunca hora de pared.
  • Marcado // PEND-nn en cada punto donde se use un valor provisional.