Abrir versión de texto

Contenido del artículo
- Qué hay realmente dentro de un enlace ascendente
- 1. Por qué los dispositivos no pueden simplemente enviar JSON
- 2. Dónde coloca ThinkLink la decodificación
- 3. Dónde se ejecuta el script: primero las reglas del sandbox
- 4. Primer script: versión Buffer escrita a mano
- 5. Segundo script: reescritura declarativa con tklHelper
- 6. Respuestas de parámetros: puerto 214 y atributos compartidos
- 7. sub_device : convertir un DTU en N dispositivos
- 8. Tras la decodificación: de los campos al valor de negocio
- 9. Depuración sin console
- 10. Trasladar scripts existentes
- 11. Ocho errores, ordenados por la frecuencia con que aparecen en sitio
- 12. Hacer bien esta capa
- Resumen
El artículo anterior cubrió la incorporación de 500 dispositivos en una sola tarde con la API REST de ThinkLink. Una vez que los dispositivos están en línea, el siguiente problema llega de inmediato: los datos fluyen, pero nadie puede leerlos.
Su consola muestra un enlace ascendente como este:
IQcDDG4PAADWBIsC34IG
Lo que el negocio realmente quiere es: temperatura 23.8 °C, humedad 65.1 %, batería 3.40 V, estado normal, intervalo de reporte 15 minutos. La capa que se sitúa entre esos dos estados es el script decodificador del modelo de dispositivo — el tema de este artículo.
Qué hay realmente dentro de un enlace ascendente
Empiece por desempaquetar esa cadena Base64. Proviene de msg.userdata.payload en el mensaje AS LoRaWAN estándar de ThinkLink — 20 caracteres Base64, 15 bytes:
21 07 03 0C 6E 0F 00 00 D6 04 8B 02 DF 82 06
Mapeado contra el formato de trama privada del sensor de temperatura/humedad KS52:
| Desplazamiento de byte | Crudo | Significado | Decodificación | Resultado |
|---|---|---|---|---|
| 0-2 | 21 07 03 | Etiqueta de trama fija | Comparación directa | Trama válida |
| 3-4 | 0C 6E | Secuencia / reservado | No analizado por este modelo | — |
| 5-6 | 0F 00 | Intervalo de reporte | uint16 LE → 15 (minutos) | 15 min |
| 7 | 00 | Bits de estado | bit0 = falla, bit4 = fuga | Normal, sin fuga |
| 8-9 | D6 04 | Temperatura | uint16 LE → 1238, (1238−1000)/10 | 23.8 °C |
| 10-11 | 8B 02 | Humedad | uint16 LE → 651, ×0.1 | 65.1 % |
| 12 | DF | Voltaje de batería | uint8 → 223, (223×1.6)/254+2.0 | 3.40 V |
| 13-14 | 82 06 | Reservado / checksum | No analizado por este modelo | — |
Quince bytes transportan cinco valores de negocio. Por eso los dispositivos finales LoRaWAN usan binario compacto en lugar de JSON — no porque a los ingenieros les guste la oscuridad, sino porque la interfaz aérea no puede permitirse otra cosa.
1. Por qué los dispositivos no pueden simplemente enviar JSON
Todo el que es nuevo en LoRaWAN pregunta esto: si la capa de aplicación quiere JSON de todos modos, ¿por qué no dejar que el dispositivo envíe {"temperature":23.8,"humidity":65.1}?
La respuesta es el tiempo de aire. Para un radio SX1262 a BW125 kHz, CR 4/5:
| Payload | Tiempo de aire a SF7 | Relativo a 15 bytes |
|---|---|---|
| 15 bytes (binario compacto) | 46.3 ms | 1.0× |
| 120 bytes (JSON con nombres de campo y marca de tiempo) | 199.9 ms | 4.3× |
Añada el factor de dispersión y la brecha se vuelve imposible de ignorar — mismo payload de 15 bytes:
| Factor de dispersión | Tiempo de aire | Relativo a SF7 |
|---|---|---|
| SF7 | 46.3 ms | 1.0× |
| SF10 | 329.7 ms | 7.1× |
| SF12 | 1155.1 ms | 24.9× |
Con un uplink cada 15 minutos (96 al día), un solo dispositivo pasa 4.4 segundos al día en transmisión de alta potencia a SF7 frente a 111 segundos a SF12. Ese es el mecanismo detrás del artículo sobre la duración de la batería de esta serie: cada byte extra que transmites, y cada paso de SF al que retrocedes, se paga con la batería.
También hay un techo estricto. Según los parámetros regionales RP002-1.0.5, la banda CN470 de China permite una carga útil MAC máxima de 15 bytes en su tasa de datos más baja (DR0). La longitud de trama anterior no es casualidad: es lo que impone el límite normativo en las peores condiciones de enlace.
Así pues, el reparto de tareas en LoRaWAN es fijo: el dispositivo comprime todo lo que puede; la plataforma restaura lo que los humanos pueden leer. El script decodificador es toda la carga de trabajo de ese paso de restauración.
Lleva esta guía a tu proyecto
ManThink ofrece productos IoT para conectar dispositivos, desplegar redes y trabajar con datos. Consulta estos recursos de producto junto con las recomendaciones técnicas del artículo:
2. Dónde coloca ThinkLink la decodificación
El módulo responsable de la decodificación en ThinkLink es el modelo de dispositivo. Su función: convertir los datos brutos que llegan de un servidor de red LoRaWAN (NS) —o de cualquier otro protocolo— en telemetría estandarizada (telemetry_data) y atributos compartidos (shared_attrs) para tablas, paneles, alarmas y mapeo de protocolos.
Un modelo de dispositivo tiene cuatro bloques de configuración, y omitir cualquiera de ellos provoca retrabajo:
| Bloque | Propósito | Omisión habitual |
|---|---|---|
| Script de análisis | Convierte tramas binarias en campos con nombre | — |
| Campos de visualización | Define lo que muestra la tabla o tarjeta de la interfaz | Se da por hecho una vez que el script funciona |
| Mapeo Modbus | Expone datos por Modbus TCP | Se descubre que falta durante la integración industrial |
| Campos BACnet | Expone datos por BACnet/IP, genera listas de puntos | Se descubre que falta en la entrega del edificio |
La documentación oficial señala algo que merece citarse: tener un script no significa que el mapeo esté hecho. Este es el elemento de retrabajo más común en proyectos reales: el script funciona bien, luego el integrador del BMS pide una lista de puntos y no se ha definido ningún campo BACnet.
Crear un modelo de dispositivo requiere tanto un nombre (para mostrar) como un ID Name (el identificador que usan el código y las API). Son independientes; rellenar solo el nombre deja problemas atrás.
3. Dónde se ejecuta el script: primero las reglas del sandbox
Los scripts de análisis se ejecutan en un sandbox de vm2, totalmente aislado del proceso principal de Node.js. Cada uno de estos límites determina directamente si tu script funciona:
| Límite | Valor | Consecuencia |
|---|---|---|
| Tiempo de espera de ejecución | 1000 ms | Superarlo aborta y omite este análisis |
console |
No disponible | Sin depuración por impresión: usa los registros del modelo de dispositivo |
async / await |
No compatible | Solo código síncrono |
require / import |
No compatible | Sin paquetes de terceros |
eval |
Deshabilitado | Sin ejecución de código dinámico |
| WebAssembly | Deshabilitado | Sin carga de .wasm |
setTimeout / setInterval |
No disponible | Sin temporizadores ni retrasos |
process / fs / path |
No disponible | Llamarlos lanza ReferenceError |
Buffer es una compilación restringida (SafeBuffer) que expone solo seis métodos estáticos: Buffer.alloc, Buffer.from, Buffer.isBuffer, Buffer.byteLength, Buffer.compare, Buffer.concat. Buffer.allocUnsafe() está bloqueado explícitamente. Los métodos de lectura/escritura de instancia (readUInt8, readUInt16LE, …) funcionan con normalidad.
En conjunto, estas reglas apuntan a una conclusión: un script decodificador debe ser una función pura, síncrona y sin efectos secundarios que reordena bytes. Cualquier cosa que quiera llamar a servicios externos, consultar una base de datos o esperar una respuesta durante la decodificación no pertenece aquí: ese trabajo corresponde a los modelos de disparador y a los modelos RPC.
3.1 Variables inyectadas
No es necesario declarar parámetros; la plataforma inyecta estos:
| Variable | Descripción |
|---|---|
device |
Objeto de dispositivo, congelado con vm.freeze, de solo lectura |
msg |
Cuerpo del mensaje de enlace ascendente sin procesar |
thingModelId |
ID del modelo de thing actual |
noticeAttrs |
Indicadores que señalan qué cambio de atributo desencadenó esta ejecución |
org_params |
Variables de entorno a nivel de inquilino (p. ej. umbrales compartidos) |
subDevices |
Mapa de caché de dispositivos hijos del dispositivo padre |
env / plugins |
Variables de entorno e instancias del plugin (avanzado) |
Buffer |
SafeBuffer restringido |
BufferTKL / PayloadParser / MSparser / Utils / TriggerHelper / RPCHelper |
Todas las clases de tklHelper, disponibles como globales del sandbox sin importación |
Las más relevantes device propiedades para la decodificación:
| Propiedad | Uso |
|---|---|
eui |
EUI del dispositivo, clave primaria globalmente única |
device_type |
NORMAL / SUB_DEVICE / VIRTUAL |
data_from |
"LoRaWAN" u "OTHER" (MQTT directo) |
online / active_time |
Estado en línea y hora del último enlace ascendente |
shared_attrs |
Atributos sincronizados en ambos sentidos (intervalo, umbrales) |
server_attrs |
Atributos solo del lado de la plataforma (umbrales de alarma, EUI vinculados) |
mac_attrs |
Atributos de la capa MAC de LoRaWAN (banda, ADR) |
telemetry_data |
Última instantánea de telemetría por modelo de thing, indexada por thingModelId |
Nota: device está congelado con vm.freeze: las asignaciones no lanzan error, pero tampoco surten efecto. El valor de retorno es el único canal de salida. Las propiedades ausentes devuelven undefined, así que use encadenamiento opcional (?.) en todo el código.
3.2 Qué leer de msg
Para los enlaces ascendentes LoRaWAN, cuatro campos cubren casi todas las necesidades:
| Dato | Acceso |
|---|---|
| Carga útil (Base64) | msg.userdata.payload |
| Puerto | msg.userdata.port |
| RSSI | msg.gwrx[0].rssi |
| SNR | msg.gwrx[0].lsnr |
Para datos JSON que llegan directamente por MQTT, recorra la jerarquía JSON como de costumbre.
3.3 Valor de retorno: cuatro campos, y equivocarse pierde datos
El script debe devolver una estructura fija:
| Campo | Predeterminado | Propósito |
|---|---|---|
telemetry_data |
null | Telemetría para paneles, tendencias, consultas de historial |
shared_attrs |
null | Atributos sincronizados en ambos sentidos (intervalo, dirección, umbrales) |
server_attrs |
null | Metadatos solo del lado de la plataforma |
sub_device |
null | Identificador de dispositivo hijo; p. ej. una dirección RS-485; la plataforma crea el dispositivo hijo y le asigna un EUI |
Cualquier campo puede ser null, pero null significa que este enlace ascendente no produce datos de ese tipo. El error más frecuente en la práctica: las respuestas de parámetros se analizan en telemetry_data, por lo que el intervalo de reporte termina graficado como una medición.
sub_device es el campo más subestimado en los despliegues LoRaWAN; consulte la sección 5.
4. Primer script: versión Buffer escrita a mano
Aquí hay una versión completa sin biblioteca auxiliar, que desempaqueta la trama de 15 bytes campo por campo. Este es el contenido completo de la pestaña del script del analizador (un cuerpo de función sin más; la plataforma lo envuelve y lo llama):
let payload = Buffer.from(msg?.userdata?.payload, "base64"); let port = msg?.userdata?.port || null;
function parseTelemetry(payload) { // port must be 11, frame tag must be 21 07 03, length must be 15 if (port != 11) { return null } if (payload[0] != 0x21 || payload[1] != 0x07 || payload[2] != 0x03) { return null } if (payload.length != 15) { return null }
let telemetry_data = {}; telemetry_data.period_data = payload.readUInt16LE(5); telemetry_data.status = (payload[7] & 0x01) != 0 ? "fault" : "normal"; telemetry_data.leakStatus = (payload[7] & 0x10) != 0 ? true : false; telemetry_data.temperature = Number(((payload.readUInt16LE(8) - 1000) / 10).toFixed(2)); telemetry_data.humidity = Number((payload.readUInt16LE(10) / 10).toFixed(2)); let vbat = payload.readUInt8(12); telemetry_data.vbat = Number(((vbat * 1.6) / 254 + 2.0).toFixed(2)); telemetry_data.rssi = msg.gwrx[0].rssi; telemetry_data.snr = msg.gwrx[0].lsnr; return telemetry_data; }
return { sub_device: null, telemetry_data: parseTelemetry(payload), server_attrs: null, shared_attrs: null };
Introduzca la cadena Base64 desde arriba y obtendrá:
{ period_data: 15, status: "normal", leakStatus: false, temperature: 23.8, humidity: 65.1, vbat: 3.40, rssi: -92, snr: 13.2 }
4.1 Seis decisiones ocultas en ese código
Triple validación. Puerto, etiqueta de trama, longitud: elimine cualquiera de las tres y el script acabará analizando la trama equivocada. El puerto 11 son datos de medición, 214 es respuesta de parámetros, 51 es transporte transparente. El mismo dispositivo envía cosas completamente distintas en los tres; sin una comprobación de puerto está metiendo tres tipos de mensaje en un solo analizador.
El endianness debe venir del manual. La temperatura aquí es readUInt16LE. Escriba readUInt16BE en su lugar y 1238 se convierte en 56,594: una temperatura de 5559.4 °C. No lanza ningún error; simplemente produce sinsentidos en silencio, que normalmente se descubren en la aceptación del cliente.
Los desplazamientos y coeficientes provienen del protocolo del dispositivo, no de adivinanzas. (raw − 1000) / 10 existe porque el dispositivo codifica 0 °C como 1000 para evitar números negativos. (vbat × 1.6) / 254 + 2.0 asigna un valor ADC de 8 bits de vuelta al rango de 2.0–3.6 V. Ambos provienen del manual del dispositivo.
Ajuste toFixed en Number(). toFixed devuelve una cadena; devolverla directamente hace que el front end trate el valor como texto, lo que rompe la ordenación y las comparaciones de umbral.
Incluye RSSI y SNR. Se encuentran en msg.gwrx[0], no en el payload. Incluir la calidad del enlace en la telemetría significa que cuando alguien informa "el dispositivo X está saltando", puedes comprobar primero la cobertura y evitar una visita al sitio.
Devuelve null en caso de fallo de análisis, no lances una excepción. Null significa "sin telemetría de este uplink" y la plataforma lo omite; lanzar una excepción escribe un error e interrumpe el flujo. Para tramas corruptas ocasionales o tramas en otros puertos, null es la semántica correcta.
5. Segundo script: reescritura declarativa con tklHelper
La versión escrita a mano se lee bien, pero cada nuevo modelo de dispositivo repite la misma validación y el mismo desplazamiento de bits. La biblioteca tklHelper de ThinkLink abstrae esto en tres bloques declarativos: frameInfo (validez de la trama), appInfo (definiciones de variables), paraInfo (definiciones de parámetros).
La documentación de la biblioteca está sincronizada con la versión 1.01.043 (2026-10-12). En los scripts de thing model, RPC y trigger, sus seis clases (BufferTKL / PayloadParser / MSparser / Utils / TriggerHelper / RPCHelper) son globales del sandbox — no se necesita import.
5.1 frameInfo — validación de la trama
| Campo | Predeterminado | Descripción |
|---|---|---|
port |
-1 | Número de puerto; si es positivo, las tramas que no coinciden se descartan; -1 desactiva la comprobación |
dataLen |
-1 | Longitud esperada de la trama; si es positiva, las que no coinciden se descartan |
status |
-1 | Desplazamiento del byte de estado; si ≥ 0, se lee como uint8 en tdata.status |
battery |
-1 | Desplazamiento del byte de batería; si ≥ 0, se convierte mediante (v×1.6)/254+2.0 en tdata.battery |
rssi |
false | Si es true, lee RSSI/SNR de msg.gwrx[0] |
tagList |
[] | Comprobaciones de etiqueta de trama, cada { index, tag }; si no coinciden, se descarta la trama |
subDevice |
{ index: -1, type: "uint8" } |
Posición y tipo de la dirección del dispositivo hijo; un valor de 0 descarta la trama |
Para nuestro ejemplo:
let frameInfo = { port: 11, dataLen: 15, rssi: true, tagList: [ { index: 0, tag: 0x21 }, { index: 1, tag: 0x07 }, { index: 2, tag: 0x03 } ] };
La triple validación de la sección anterior se convierte en cuatro líneas declarativas.
5.2 appInfo — definiciones de variables
| Parámetro | Descripción |
|---|---|
name / field_name |
Nombre para mostrar / nombre del campo del programa (la clave en tdata) |
unit |
Cadena de unidad |
index |
Desplazamiento de byte inicial |
type |
Cadena de tipo (ver más abajo) |
coefficient |
Multiplicador — un número, o el nombre de otro campo para una multiplicación de segunda pasada |
decimal |
Decimales, predeterminado 0 |
illegal |
Comprobación de validez como ">0x80" o "=0xFF"; si coincide, el campo se descarta silenciosamente |
options |
Mapa de valores, p. ej. { 0: "normal", 1: "fault" } |
postProcess |
Cadena de expresión JS con value como entrada |
Los tipos admitidos son lo bastante amplios como para que rara vez se necesite manipulación de bits:
| Categoría | Cadenas de tipo |
|---|---|
| Enteros | int8 / uint8, int16 / uint16 (big-endian por defecto), int16le / uint16le, int24 / uint24, int32 / uint32 (con variantes le), serie int40–int64 |
| Flotantes | floatbe / floatle (IEEE 754 simple), floatcdab (orden de bytes 2-3-0-1, habitual en dispositivos Modbus), intcdab |
| Numéricos codificados | bcdbe<N> / bcdle<N> (BCD), hexbe<N> / hexle<N> (cadena hex) |
| Campos de bits | bitbe<S>-<E> / bitle<S>-<E> — extrae un rango de bits directamente |
| Booleanos | bool<N> (devuelve 1/0), boolean<N> (devuelve true/false) |
| Cadenas | string<N> (ASCII de N bytes) |
Las cadenas de tipo no distinguen mayúsculas de minúsculas. floatcdab y intcdab existen para las trampas de orden de bytes en dispositivos Modbus — muchos medidores almacenan valores de 32 bits como 2-3-0-1, lo que es fácil de equivocar a mano y trivial de declarar.
Los tipos de campo de bits resuelven otro caso habitual: un byte de estado que contiene ocho flags. Usa bitle0-0 para el bit de fallo y bitle4-4 para el bit de fuga en lugar de contar ceros en máscaras como & 0x01 y & 0x10.
5.3 La versión declarativa completa
let frameInfo = { port: 11, dataLen: 15, rssi: true, tagList: [ { index: 0, tag: 0x21 }, { index: 1, tag: 0x07 }, { index: 2, tag: 0x03 } ] };
let appInfo = [ { name: "Status", field_name: "status", unit: "", index: 7, type: "bitle0-0" }, { name: "Leak", field_name: "leakStatus", unit: "", index: 7, type: "bitle4-4" }, { name: "Temperature", field_name: "temperature", unit: "C", index: 8, type: "uint16le", postProcess: "value = (value - 1000) * 0.1; return Number(value.toFixed(1))" }, { name: "Humidity", field_name: "humidity", unit: "%", index: 10, type: "uint16le", coefficient: 0.1, decimal: 1 }, { name: "Battery", field_name: "vbat", unit: "V", index: 12, type: "uint8", postProcess: "return Number(((value * 1.6) / 254 + 2.0).toFixed(2))" } ];
let payParser = new PayloadParser({ device, msg, frameInfo, appInfo }); let tdata = payParser.telemetry(); return { sub_device: null, telemetry_data: tdata, server_attrs: null, shared_attrs: null };
5.4 Qué estilo usar en cada caso
| Escenario | Estilo recomendado | Por qué |
|---|---|---|
| Estructura de trama fija, campos regulares | tklHelper declarativo | Menos de 40 líneas, validación integrada, los cambios de campo son ediciones de tabla |
| Estructura irregular, posiciones de campo dinámicas | Buffer escrito a mano | La configuración declarativa no puede expresar ramas condicionales |
| Una trama que transporta varios sensores | MSparser subclase |
Avanza automáticamente los desplazamientos por ID (véase 5.5) |
| Cálculo entre campos | Escrito a mano o postProcess |
coefficient admite referencias a campos; la lógica compleja sigue necesitando código |
PayloadParser.telemetry() se ejecuta en un orden fijo: validar puerto → dataLen → tagList → subDevice, luego extraer status / battery / rssi y cada appInfo campo, aplicando illegal → coefficient → decimal → postProcess → options, con una segunda pasada para los coeficientes que hacen referencia a otros campos. Conocer este orden es la clave para depurar "por qué no surtieron efecto mis options" (se aplica después de decimal y antes de un postProcessomitido).
5.5 MSparser — varios sensores en una trama
Algunos colectores empaquetan varios puntos de medición en un solo uplink como bloques [ID | field data...] repetidos. MSparser extiende PayloadParser y requiere extraData:
new MSparser({ device, msg, frameInfo, extraData })
Para cada ID que lee, busca user_0x<ID>_<j> en extraData (j = 0..3, hasta cuatro campos por ID) y avanza el desplazamiento automáticamente. frameInfo.idType establece cómo se lee el ID, normalmente "uint16be".
6. Respuestas de parámetros: puerto 214 y atributos compartidos
Los scripts de decodificación no solo gestionan mediciones. La configuración del dispositivo (intervalos, umbrales, offsets de calibración) viaja en el puerto 214, y esas respuestas también deben analizarse — en shared_attrs, no en telemetry_data.
Direcciones de parámetros para el KS52 (COV = umbral de cambio de valor, offset = calibración):
| Parámetro | Dirección (dec / hex) | Tipo y unidad | Se aplica a |
|---|---|---|---|
| COV de temperatura | 144 / 0x90 | uint8 × 0.1 °C | KS52, KS53 |
| COV de humedad | 145 / 0x91 | uint8 × 0.1 %RH | solo KS52 |
| Offset de temperatura | 146 / 0x92 | int8 × 0.1 °C | KS52, KS53 |
| Offset de humedad | 147 / 0x93 | int8 × 0.1 %RH | solo KS52 |
| Intervalo de medición | 142 / 0x8E | uint16 segundos | KS52, KS53 |
Con tklHelper, los parámetros se declaran en paraInfo usando <segment>_<address> claves, donde segment es uno de app / cf / fw / radio / ds / status:
let paraInfo = { app_142: { name: "Measure interval", field_name: "measurePeriod", unit: "s", type: "uint16le" }, app_144: { name: "Temperature COV", field_name: "covTemperature", unit: "C", type: "uint8", coefficient: 0.1, decimal: 1 }, app_145: { name: "Humidity COV", field_name: "covHumidity", unit: "%", type: "uint8", coefficient: 0.1, decimal: 1 }, app_146: { name: "Temperature offset", field_name: "offsetTemperature", unit: "C", type: "int8", coefficient: 0.1, decimal: 1 } };
let payParser = new PayloadParser({ device, msg, frameInfo, appInfo, paraInfo }); let tdata = null, pdata = null; if (port === 214) { pdata = payParser.paras(); } else { tdata = payParser.telemetry(); } return { sub_device: null, telemetry_data: tdata, server_attrs: null, shared_attrs: pdata };
paras() solo funciona en el puerto 214 y devuelve null en caso contrario, por lo que la comprobación del puerto es obligatoria.
6.1 El offset no es COV
Estos dos se confunden habitualmente, y el resultado es "los datos empeoraron tras la calibración":
| Concepto | Qué hace | Unidad |
|---|---|---|
| COV (cambio de valor) | Decide cuándo transmitir — enviar solo si el cambio supera el umbral | 0.1 °C / 0.1 %RH |
| Offset | Calibra el propio valor medido | 0.1 °C / 0.1 %RH |
El offset es 8 bits con signo, rango de −128 a 127, es decir, de −12.8 a +12.7 en pasos de 0.1. Los valores negativos usan complemento a dos: −1.0 °C se escribe como F6. En la lectura de vuelta, a cualquier byte ≥ 128 se le debe restar 256 antes de multiplicar por 0.1 — esto debe gestionarse explícitamente en el decodificador, o cada calibración negativa se convierte silenciosamente en positiva.
Otra lección sobre campos: el offset es un parámetro del lado del dispositivo. No lo compense también en el lado de la plataforma, o el error se duplica.
7. sub_device: convertir un DTU en N dispositivos
sub_device es la decisión de diseño más práctica de ThinkLink para modernizar instalaciones existentes.
La cadena típica: un KC11 (carril DIN, RS-485, AC 85–270 V) o un DTU KC25 informa por LoRaWAN, y una trama contiene datos de varios esclavos RS-485 — medidores eléctricos, medidores de agua, transmisores de temperatura/humedad. El DTU es un único dispositivo LoRaWAN, pero detrás de él hay 8 o 16 medidores físicos.
El decodificador simplemente devuelve:
return { sub_device: "03", telemetry_data: tdata, server_attrs: null, shared_attrs: null };
sub_device lleva la dirección de bus del dispositivo hijo (aquí una dirección RS-485). ThinkLink entonces crea un dispositivo hijo virtual en la gestión de dispositivos y le asigna un EUI único. El resultado:
- El padre (DTU) conserva su identidad LoRaWAN y sus atributos de enlace
- Cada esclavo RS-485 se convierte en un objeto consultable, alarmable y graficable de forma independiente
- Los sistemas aguas arriba nunca necesitan saber qué DTU empaquetó los datos
Esta es la premisa técnica que hace que «conservar los activos cableados existentes y sustituir solo el último salto» funcione de verdad: LoRaWAN resuelve el segmento inalámbrico; la gestión de activos de dispositivos sigue desplegándose por medidor físico.
frameInfo.subDevice (cuando index >= 0, lea la dirección mediante type en tdata.addr; un valor de 0 descarta la trama) funciona junto con este campo para extraer la dirección del esclavo directamente de la trama.
8. Tras la decodificación: de los campos al valor de negocio
Un script en ejecución es el primer paso. Un modelo de dispositivo entregable necesita tres cosas más.
8.1 Campos de visualización
Declare qué campos llegan a la interfaz de usuario. La configuración incluye:
| Elemento | Descripción |
|---|---|
| Nombre del campo | Debe coincidir con la clave en telemetry_data exactamente — distingue mayúsculas y minúsculas |
| Alias | Nombre de visualización en el front-end |
| Unidad | °C, %, V, m³, … |
| Tipo | number / string / boolean / object — elija number para los campos numéricos para que el formato funcione |
| Orden | Orden de visualización en tablas o tarjetas |
| Icono | Opcional, admite SVG incrustado |
Un nombre de campo que no coincide es la causa número uno de «el script tiene datos pero la página está en blanco».
8.2 Alarmas
Las alarmas pertenecen a modelos de disparador, no al modelo de dispositivo. El patrón recomendado gestiona la creación y el borrado en un único script de disparador: por defecto action a «clear» y cambie a «new» solo cuando se cumpla la condición. Así, cada uplink borra las alarmas cuya condición ha vuelto a la normalidad, sin intervención manual.
tklHelper proporciona la TriggerHelper clase base — extiéndala y sobrescriba _alarmProcess(info) en lugar de escribir a mano el objeto de retorno completo del disparador. Utils.LEVEL define cuatro niveles (low / mid / high / urgent) y Utils.ACTION define no / new / clear.
8.3 Mapeo BACnet y Modbus
Este es el paso final en la construcción y en las entregas industriales.
BACnet: defina los campos BACnet en el modelo de dispositivo, con nombres de campo que coincidan con la definición del modelo de dispositivo, además del tipo de objeto (Analog Input, Binary Output, …), unidad, incremento COV, valor predeterminado y RPC vinculado. Luego vaya a Operations → BACnet, haga clic en generación incremental, seleccione el modelo de dispositivo de destino y la plataforma produce una lista de puntos con tipo de objeto, número de instancia, nombre de objeto, descripción, tipo de datos, unidad y permisos de lectura/escritura — lista para entregar al integrador del BMS. Use YABE (Yet Another BACnet Explorer) durante la puesta en marcha para confirmar que los datos se publican correctamente.
Modbus: el mapeo Modbus en el modelo de dispositivo expone los campos de telemetría en direcciones de registro para que un maestro SCADA pueda leerlos por Modbus TCP. El control en sentido inverso se gestiona mediante modelos RPC; RPCHelper ofrece buildmodbusFrame06 (FC06 write single register), buildmodbusFrame10 (FC10 write multiple), buildmodbusFrameRead (FC03/FC04 read), con CRC-16/Modbus integrado.
En una frase: el decodificador decide si los datos se pueden leer; los campos de visualización, las alarmas y el mapeo de protocolos deciden si el negocio puede usarlos.
9. Depuración sin console
El sandbox no tiene console.log, que es el ajuste más difícil al empezar. Cuatro técnicas lo sustituyen:
La pestaña Test en el editor del modelo de dispositivo. Introduzca una carga útil Base64 de ejemplo y el puerto, y vea el resultado del análisis de inmediato. Las pruebas normales requieren que el dispositivo exista; de lo contrario, la plataforma devuelve «Device not found».
El indicador skipDeviceCheck . Cuando solo quiera validar la lógica del script sin un dispositivo real (por ejemplo, al configurar una plantilla NFC), la acción de prueba admite skipDeviceCheck = true. La plataforma entonces omite la búsqueda del EUI e inyecta un dispositivo de marcador de posición sintético (eui «empty», propiedad del inquilino actual), de modo que la prueba se completa aunque no haya ningún dispositivo coincidente.
Registros del modelo de dispositivo. La salida en tiempo de ejecución y las excepciones llegan aquí, sustituyendo a console.
Historial de versiones y restauración. Cada guardado registra una instantánea. Abra History en el editor para inspeccionar versiones, comparar con las vecinas y restaurar — restaurar crea un nuevo registro de versión, así que el historial nunca se pierde. Los modelos RPC, los modelos de dispositivos, los modelos de disparador y los editores de scripts de reenvío comparten esto. Puede iterar libremente; los errores son reversibles.
Un atajo más: consulte primero la biblioteca pública de modelos. En Marketplace plugins → Model Center hay dos pestañas, «Organisation models» y «Public models». Los tipos de dispositivo comunes suelen tener ya un modelo de dispositivo — cópielo y adáptelo en lugar de empezar desde cero.
10. Trasladar scripts existentes
Si su equipo ya escribió scripts de análisis en otra plataforma LoRaWAN, no necesita reescribirlos. Los modelos de dispositivos de ThinkLink ofrecen un modo de compatibilidad con ChirpStack — la interfaz ya está adaptada, así que pegar el código del analizador original funciona tal cual.
Eso importa por la magnitud del coste de migración. Un estudio de la Eclipse Foundation y PW Consulting sitúa el coste de migración en 35%–60% del presupuesto total de migración. Los scripts de decodificación son la parte más delicada de una capa de aplicación LoRaWAN, la más difícil de verificar automáticamente y el lugar más fácil para introducir errores nuevos durante una reescritura. Cuanto más puedas trasladar literalmente, menos incertidumbre arrastras.
Con un coste de ingeniero de aproximadamente USD 50/hora, escribir y poner en marcha scripts de decodificación desde cero para un proyecto de tamaño medio (10 tipos de dispositivo) suele llevar de 40 a 60 horas, es decir, USD 2,100–3,100. Reutilizar los scripts existentes lo reduce a cerca de un tercio. Lo que ahorras no es tiempo de escritura: es tiempo de verificación en sitio, que es el verdadero factor de coste en los proyectos LoRaWAN.
11. Ocho errores, ordenados por la frecuencia con que aparecen en sitio
1. Endianness invertido. 1238 se lee como 56594, dando 5559.4 °C. Sin error, solo sinsentido, normalmente descubierto en la aceptación. Confírmalo en el manual; no lo adivines probando ambas opciones.
2. Olvidar el complemento a dos. Los campos con signo (offsets int8, temperaturas int16) deben tener 256 / 65536 restado cuando el valor bruto es ≥ 128 / ≥ 32768. Sin esto, todo valor negativo se vuelve positivo.
3. toFixed sin Number(). Devuelve una cadena; la ordenación y las comprobaciones de umbral fallan en silencio.
4. Sin comprobación de puerto. Los puertos 11 (medición), 214 (parámetros) y 51 (transparente) llevan estructuras completamente distintas. Sin ramificación, tres tipos de mensaje llegan a un mismo parser.
5. Sin comprobación de longitud. Después de que una actualización de firmware cambie la longitud de trama, o cuando llega una trama de otro modelo, un parser sin comprobación lee fuera de rango. SafeBuffer puede devolver false o lanzar una excepción, pero leer un offset válido pero incorrecto es peor.
6. Respuestas de parámetros escritas en telemetry_data. El intervalo de reporte se representa como una medición y el gráfico de tendencia se arruina. Los parámetros pertenecen a shared_attrs; las mediciones a telemetry_data.
7. Nombre de campo de visualización que no coincide con la salida del script. La causa principal de «los datos existen, la página está en blanco». Los nombres de campo distinguen mayúsculas y minúsculas.
8. Tratar un script que funciona como un trabajo terminado. Los campos de visualización, el mapeo Modbus y los campos BACnet son tres configuraciones separadas; un script no implica un mapeo. Las listas de puntos BACnet deben generarse por separado.
12. Hacer bien esta capa
El reparto de tareas en LoRaWAN es claro e irreversible: el dispositivo final comprime todo lo posible, la red lo transporta, la plataforma lo restaura a significado de negocio.
El decodificador parece la menos vistosa de las tres capas: 15 bytes, 40 líneas de JavaScript, 30 minutos de trabajo. Pero determina la forma final de toda la cadena:
- Decide cuánto dura la batería (codificación más ajustada, menos tiempo en el aire): retoma la celda de 2700 mAh del artículo anterior: pasar de SF7 a SF12 multiplica el tiempo de transmisión por 24.9
- Decide si los sistemas de negocio pueden consumir los datos (los nombres de campo, las unidades y los tipos se fijan aquí)
- Decide si puedes entregar una lista de puntos (el mapeo BACnet / Modbus depende de las mismas definiciones de campo)
- Decide si los activos existentes pueden gestionarse por dispositivo (
sub_deviceconvierte 16 medidores detrás de un DTU en 16 objetos independientes)
La línea de productos de ManThink está construida en torno a esta cadena: los sensores KS51 / KS52 / KS53 / KS61 producen tramas compactas, las pasarelas GD61x / GDI51 / GDI52 / GD73 / GDO51 las transportan, KC11 / KC25 / KC21 incorporan los medidores RS-485 existentes, y ThinkLink se encarga de la decodificación, las alarmas, el mapeo de protocolos y el reenvío en el lado de la plataforma.
Las tres formas de despliegue de ThinkLink —Cloud (gratis hasta 1,000 dispositivos), TKE on-premises (1,000 dispositivos, procesamiento y almacenamiento de datos ilimitados) y TKG integrado en la pasarela (100 dispositivos, una pasarela es un sistema completo)— comparten los mismos thing models, modelos RPC y reglas de disparo, y pueden migrar entre sí.
Eso importa especialmente para los scripts de decodificación: un thing model que escribes en Cloud se ejecuta sin cambios en TKE o TKG. Los scripts son activos, no accesorios de una forma de despliegue.
Resumen
- El binario compacto en LoRaWAN no es una preferencia, es una necesidad: a SF7, 15 bytes tardan 46.3 ms frente a 199.9 ms para una carga útil JSON de 120 bytes (4.3×); CN470 DR0 limita la carga útil a 15 bytes
- La decodificación en ThinkLink reside en el thing model, ejecutándose en un sandbox vm2: tiempo de espera de 1000 ms, sin console, sin async, sin require, y el valor de retorno como único canal de salida
- Cada campo de retorno tiene su lugar: las mediciones en
telemetry_data, la configuración enshared_attrs, las direcciones hijas ensub_device - tklHelper ofrece un estilo declarativo:
frameInfopara la validación,appInfopara los campos,paraInfopara los parámetros, con tipos de datos 30+ incluido el orden de bytes floatcdab habitual en dispositivos Modbus - Las respuestas de parámetros del puerto 214 necesitan su propia rama; offset y COV son conceptos distintos, y los offsets negativos requieren manejo de complemento a dos
sub_deviceconvierte cada medidor RS-485 detrás de un DTU en un dispositivo independiente: la premisa para modernizar instalaciones existentes- Tras la decodificación, configura los campos de visualización, las reglas de alarma y el mapeo BACnet/Modbus; un script que funciona no es una entrega completada
- Los scripts de parser existentes pueden reutilizarse mediante el modo de compatibilidad; lo que ahorras es sobre todo tiempo de verificación en sitio
El próximo artículo trata sobre modelos de disparo y reglas de alarma: una vez decodificados los datos, cómo asegurarte de que la persona adecuada se entere en el momento en que un valor sale de rango.
Referencias: documentación de ThinkLink — Thing Model https://www.think-link.net/docs/zh/Model/ThingModel.html, tklHelper https://www.think-link.net/docs/zh/Model/tklHelper.html, RPC Model https://www.think-link.net/docs/zh/Model/RPCModel.html, Trigger Model https://www.think-link.net/docs/zh/Model/TriggerModel.html, manual del sensor KS52 https://www.think-link.net/docs/zh/Device/LoRaWANSensor/ks52.html. Cifras de tiempo de aire calculadas con la Semtech LoRa Calculator (SX1262, BW125 kHz, CR 4/5).
ManThink: de los dispositivos a las aplicaciones de datos
¿Planeas una nueva instalación o modernizar un sistema existente? Comparte con ManThink la región, el número de dispositivos, las interfaces de campo y las condiciones de conectividad y alimentación para conversar sobre la selección de productos y la integración.
KS52 Sensor de temperatura, humedad y fugas
KS52 — Mide temperatura y humedad relativa, con sondas de fugas opcionales. La pantalla LCD de bajo consumo permite lectura local y supervisión remota.
ThinkLink
ThinkLink conecta dispositivos IoT con aplicaciones de datos y permite la conexión de dispositivos, la gestión de datos y el desarrollo de aplicaciones.
Consulta tu proyecto con ManThink →
Confirma los modelos, las configuraciones y las condiciones de uso en la documentación oficial y según los requisitos de tu proyecto.
Artículos relacionados
Edificios y climatización · Socios globales · Consulta tu proyecto ↗
Descubre más notas técnicas y experiencias de campo.
Volver a los artículos ↗