Interfaz para operar un polarímetro de fluorescencia desde un panel ED-HMI3010 de 7" (1024x600, apaisado) sobre Raspberry Pi CM4, hablando por puerto serie con una placa STM32G474 (NUCLEO-G474RE, Zephyr) con un protocolo ASCII de líneas.
pola_ui/
├── server/ servicio Node: puerto serie + API HTTP/WebSocket
│ ├── src/
│ │ ├── protocol/ codificar y parsear las líneas del protocolo
│ │ ├── transport/ puerto serie real y placa simulada
│ │ ├── link.ts secuencia, timeout, reintentos, un comando en vuelo
│ │ ├── instrument.ts las primitivas de la placa (PMT, LED, drive, gate)
│ │ ├── measurement.ts la secuencia de una muestra, mediana, P y anisotropía
│ │ ├── assay.ts el ensayo: fases, tubos, apareo y resultados
│ │ ├── contract.ts los tipos del cable — lo único que la UI importa
│ │ ├── state.ts estado del enlace + buffer de tráfico
│ │ ├── users.ts cuentas locales (scrypt, JSON atómico)
│ │ └── api/ WebSocket, REST, archivos estáticos
│ └── test/ 110 tests, sin hardware
├── ui/ interfaz Vue 3 + PrimeVue + Vite
│ ├── src/lib/ cliente WebSocket, sesión, marca
│ ├── src/styles/ tema de PrimeVue y base responsive
│ ├── src/components/ pantalla, indicadores
│ └── src/views/ bienvenida, ingreso, menú, ensayo, usuarios,
│ diagnóstico, ajustes
├── docs/
│ ├── protocol.md especificación de la interfaz con la placa
│ ├── modes.md modos de operación del equipo y permisos por rol
│ └── measurement.md la secuencia de medición
└── deploy/ systemd, udev, instalador para la Pi
La interfaz es Vue 3 con PrimeVue, compilada con Vite a archivos estáticos que sirve el mismo servicio Node. Sin SSR y sin router: son siete pantallas sin URLs propias, servidas desde la propia Pi y sin red.
Dos procesos separados en la Pi, a propósito: Chromium dibuja la UI y Node habla con la placa. Si la interfaz se cuelga o se recarga, la comunicación con el equipo no se interrumpe — y el ensayo en curso tampoco se pierde, porque su estado vive en el servicio.
La secuencia de medición la orquesta el panel. La placa expone primitivas (PMT, LED, drive del polarizador, gate y conteo) y este servicio arma con ellas la secuencia: tres lecturas por canal, mediana, fases de blancos y trazadores, y la matemática de P. La placa también puede correr la secuencia sola desde sus botones, pero acá no se usa así.
npm install
npm run dev # servicio en :8080 + UI en :5173Abrir http://localhost:5173. El servicio arranca con una placa simulada que habla el protocolo propuesto, emite telemetría a 5 Hz y responde a los comandos: se puede desarrollar y probar toda la interfaz sin hardware.
El objetivo de diseño es el panel de 1024x600, pero la misma interfaz se opera
desde una notebook, una tablet y un teléfono en vertical. Hay tres cortes,
definidos en ui/src/styles/base.css:
| Ancho | Qué cambia |
|---|---|
| < 700 px | teléfono en vertical: una columna, todo apilado, el estado pasa a una segunda línea de la barra, la tabla de resultados se convierte en fichas |
| 700–999 px | tablet: una columna ancha, botones y tipografía del panel |
| ≥ 1000 px | panel y escritorio: dos columnas |
Para verla al tamaño exacto del panel, en las devtools de Chrome elegir un dispositivo de 1024x600.
Los campos de texto son <input> nativos (los de PrimeVue lo son): el teclado
físico funciona, y en el panel táctil aparece el teclado en pantalla del sistema
operativo. Hay que habilitarlo en la Pi — ver deploy/README.md.
Comandos útiles:
| Comando | Qué hace |
|---|---|
npm run dev |
servicio + UI con recarga en caliente |
npm test |
tests del servidor (protocolo, enlace, estado, mock) |
npm run check |
type-check de los dos paquetes |
npm run build |
compila UI y servidor a dist/ |
npm start |
corre lo compilado, sirviendo la UI desde ui/dist |
Con hardware conectado en la máquina de desarrollo:
POLA_SERIAL_MOCK=false POLA_SERIAL_PATH=COM5 npm run dev:server # Windows
POLA_SERIAL_MOCK=false POLA_SERIAL_PATH=/dev/ttyACM0 npm run dev:serverTodo por variables de entorno, para que el archivo de systemd sea lo único que
cambia entre desarrollo y el equipo. Ver server/src/config.ts.
| Variable | Default | Para qué |
|---|---|---|
POLA_HTTP_PORT |
8080 |
puerto de la API y de la UI |
POLA_HTTP_HOST |
0.0.0.0 |
127.0.0.1 para que solo escuche local |
POLA_SERIAL_PATH |
/dev/pola-board |
puerto serie (symlink de udev) |
POLA_SERIAL_MOCK |
true fuera de producción |
placa simulada en proceso |
POLA_SERIAL_REPLY_TIMEOUT_MS |
1000 |
timeout de respuesta de la placa |
POLA_SERIAL_MAX_INFLIGHT |
1 |
comandos simultáneos hacia la placa |
POLA_USERS_FILE |
./data/users.json |
archivo de cuentas |
POLA_ALLOW_SHUTDOWN |
false |
habilita el botón Apagar |
POLA_LOG_LEVEL |
info |
debug para ver cada línea |
| docs/protocol.md | La especificación de la interfaz con la placa: los cambios de seguridad que hay que hacerle al firmware antes de conectarlo, el juego de comandos, el modo máquina y los criterios de aceptación. Fuente de verdad para los dos lados. |
| docs/modes.md | Los modos de operación del equipo — reposo, ensayo, calibración, diagnóstico, local y simulación — con sus transiciones, la regla del estado seguro y los permisos por rol. |
| docs/measurement.md | La secuencia de medición: fases, mediana, y la matemática de P. |
Definido en docs/protocol.md, y todavía sujeto a acuerdo con el firmware. En resumen, una línea por mensaje y el primer carácter dice de qué se trata:
#12 COUNT 1000 Pi -> placa comando con número de secuencia
#12 OK count=51234 gate=1000 placa -> Pi respuesta a ese comando
#13 ERR RANGE 1..6553 ms placa -> Pi comando rechazado
!TLM t=87 pmt=on chan=H led=0 placa -> Pi estado, periódico
!ALM code=GATE_FAIL sev=high placa -> Pi alarma
!BOOT fw=0.3.1 placa -> Pi la placa se reinició
%adc calibrado placa -> Pi log del firmware
El servidor se encarga del número de secuencia, del timeout, del reintento y de serializar los comandos. La placa solo tiene que responder a todo lo que recibe.
Dos primitivas necesitan trato especial y lo tienen: PMT ON bloquea 3 s
mientras la alta tensión se estabiliza, y COUNT tarda el gate — las dos
llevan su propio timeout, y COUNT no se reintenta nunca, porque reintentar un
conteo es medir dos veces.
Los tests de server/test/mock.test.ts son, en la
práctica, la especificación ejecutable de lo que se le va a pedir al firmware.
La primera vez, en la Pi:
git clone <repo> && cd pola_ui
npm ci && npm run build
sudo ./deploy/install.shDespués, las actualizaciones van desde la notebook por SSH:
npm run deploy pi@192.168.1.50Compila acá, verifica, publica, reinicia el servicio y vuelve atrás solo si no levanta. Y para desarrollar contra la placa real con recarga en caliente:
POLA_API=http://192.168.1.50:8080 npm run dev:uiDetalles, acceso remoto, permisos y resolución de problemas: deploy/README.md.
Es un polarímetro de fluorescencia. El firmware vive aparte
(C:\ncs\devel\counter, Zephyr sobre NUCLEO-G474RE) y la secuencia de medición
que ejecuta está documentada en docs/measurement.md:
blancos, trazadores apareados, 3 lecturas por canal, mediana, y
P = (Iv - G·Ih)/(Iv + G·Ih).
- El firmware no habla el protocolo de
docs/protocol.md: hoy expone una shell de Zephyr con el log en el mismo canal. Todo el flujo del ensayo está implementado y probado contra la placa simulada; falta decidir si al firmware se le agrega un modo máquina o si la UI parsea la shell mientras tanto. - El factor G está sin calibrar (
G_FACTOR_MILLI = 1000en el firmware,POLA_G_FACTOR_MILLIacá): los resultados salen sin corrección instrumental, y la UI lo avisa en la pantalla de resultados. - Falta el modo calibración (modes.md): la
pantalla, el cálculo de G contra un patrón, la confirmación y el registro de
quién calibró contra qué. Depende de
GET G/SET Gen el firmware. - Diagnóstico no está restringido por rol: hoy entra cualquier usuario identificado, y debería pedir supervisor o superior.
- Falta el keepalive del panel mientras el PMT está encendido. Sin él no se puede habilitar el watchdog del firmware, que es lo único que apaga el tubo si la Pi muere.
- Los ensayos no se guardan: al cerrar uno, los resultados quedan en pantalla y se pierden al abrir el siguiente. Falta decidir si se archivan, y si hay que exportarlos — junto con el registro de calibraciones.
- Falta el archivo del logo. Dejando
ui/src/assets/logo.svg(o.png) la pantalla de bienvenida lo toma sola; mientras tanto muestra el monograma deui/src/lib/branding.ts.