Word swap: el float que venía del revés
Un analizador de red nuevo, un mapa de registros en la mano, media hora de
trabajo. La lectura de potencia activa devolvía 1.7e38. La siguiente,
3.4e-41. El sensor estaba perfectamente: lo que estaba del revés era
el float.
Un número de 32 bits en registros de 16
Modbus es un protocolo de registros de 16 bits. Un float IEEE 754 de precisión simple ocupa 32. La consecuencia es inevitable: cada medida viaja partida en dos registros consecutivos, y el estándar no dice en qué orden. Cada fabricante eligió el suyo.
Con dos decisiones independientes —el orden de los bytes dentro de cada registro y el orden de las dos palabras entre sí— salen cuatro combinaciones posibles, y solo una da el número correcto:
Registros leídos: 0x0000 0x43C8
big-endian, MSW primero (ABCD) -> 4.59e-41 descartado
big-endian, LSW primero (CDAB) -> 400.0 <-- este
little-endian, MSW (BADC) -> 1.13e-40 descartado
little-endian, LSW (DCBA) -> -3.03e+35 descartado
El caso de arriba es real y es el que usan los analizadores de red de Carlo Gavazzi de la serie WM: bytes en big-endian, palabras con la menos significativa primero. Es lo que las bibliotecas suelen llamar word swap. En Python, sin dependencias:
import struct
def leer_float(registros):
"""Dos registros Modbus -> float IEEE 754.
Byte order big-endian, word order LSW primero (word swap).
Cambiar esto rompe TODAS las medidas del equipo, no una.
"""
bajo, alto = registros # el primero es la palabra baja
crudo = struct.pack(">HH", alto, bajo)
return struct.unpack(">f", crudo)[0]
Cómo se averigua sin adivinar
Probar las cuatro y quedarse con la que «parece bien» funciona, pero solo si se elige la medida adecuada como referencia. Y aquí es donde casi todo el mundo se equivoca de variable.
La referencia buena es la tensión. Está siempre presente, su magnitud se conoce de antemano dentro de un pequeño margen (unos 230 V entre fase y neutro, unos 400 V entre fases) y ninguna de las tres decodificaciones incorrectas produce por casualidad un número en ese entorno. Decodificas las cuatro, una da 400 y las otras dan basura: no hay ambigüedad posible.
La referencia mala —y la tentadora— es la potencia. Si haces la prueba con la planta parada, la potencia vale cero, y cero se decodifica igual de bien en los cuatro órdenes. Todos los bytes son ceros: cualquier permutación de ceros sigue siendo cero. La prueba pasa, te vas a casa, y el fallo aparece la primera vez que la planta arranca.
Regla. El orden de palabras se valida contra una magnitud conocida y distinta de cero. Validar con el equipo parado no valida nada.
El signo es otra decisión, y no es del driver
Superado el orden de palabras, queda la segunda trampa. La potencia activa en el punto de conexión tiene signo, y el criterio es una convención: positivo importando, negativo exportando. Salvo que los transformadores de intensidad estén montados al revés, cosa que ocurre con una frecuencia notable y que no siempre se puede corregir físicamente sin cortar el suministro.
La tentación es arreglarlo en el driver, que es donde está el
struct.unpack. Es un error. El driver está validado en otras
instalaciones y es el mismo fichero en todas: meterle el matiz de una planta
concreta significa que a partir de ese día ningún despliegue es seguro sin
acordarse de esta.
El signo invertido es una característica de la instalación, no del equipo, así que vive en la configuración de esa instalación, como un multiplicador explícito que el lazo de control aplica al leer. Una línea en el fichero de configuración, visible, y con el nombre suficientemente feo como para que nadie la confunda con una ganancia.
Y las energías no son floats
Las variables instantáneas —tensión, corriente, potencia, factor de potencia— son floats de dos registros. Los contadores de energía, no: suelen ser enteros sin signo de 64 bits repartidos en cuatro registros, y traen un valor absoluto de fábrica. No arrancan a cero cuando tú empiezas a leerlos; arrancan donde el equipo lleve acumulado desde que se fabricó.
De ahí sale una regla de cálculo que parece obvia escrita y que se incumple constantemente: la energía de un periodo es una diferencia contra una instantánea previa, nunca una lectura directa. El «consumo de hoy» es la lectura de ahora menos la lectura guardada a las 00:00.
Y como esa resta se va a ejecutar todos los días durante años, hay que decidir qué hacer cuando da algo imposible. Se dan dos casos, los dos reales:
- Delta negativo. El contador se ha puesto a cero: alguien lo reinició, o se cambió el equipo. La diferencia no significa nada. Se descarta el intervalo y se toma la lectura nueva como base.
- Delta absurdamente grande. Un salto de decenas de miles de kWh en un día en una instalación que hace cientos. Casi siempre es un equipo sustituido por otro con distinto acumulado, y ocasionalmente una lectura corrupta. También se descarta, con un umbral coherente con el tamaño de la instalación.
Sin esos dos filtros, un cambio de contador mete un pico de decenas de MWh en la serie histórica y contamina para siempre cualquier gráfica anual, cualquier comparativa mensual y cualquier informe que se haya enviado ya.
El poso
- Modbus transporta registros de 16 bits; todo lo demás es un acuerdo entre
el fabricante y tú. Ese acuerdo se documenta en el código, junto al
unpack. - Valida el orden de palabras contra una magnitud conocida y no nula. El cero se lee bien de las cuatro formas: es el mejor falso positivo que existe.
- Los matices de una instalación van en la configuración de esa instalación. Un driver compartido con excepciones deja de ser compartible.
- Un contador acumulado no es una medida: es el extremo de una resta. Y toda resta necesita saber qué hacer cuando el resultado es imposible.
¿Te suena de tu planta? Esto sale de operar instalaciones reales todos los días. Si tienes un problema parecido, cuéntanoslo: la primera conversación es con un ingeniero, no con un comercial.
Hablar con un ingeniero Probar el simulador →
VOLTIA