This entry is part 4 of 7 in the series Trazabilidad de activos con LoRaWAN e IA generativa

Trazabilidad de activos con LoRaWAN e IA generativa

Trazabilidad de activos con LoRaWAN e IA generativa. Una vuelta de tuerca

Trazabilidad de activos con LoRaWAN e IA generativa. ChirpStack v4 como contenedor ProxMox

Trazabilidad de activos con LoRaWAN e IA generativa. Gateway Heltec HT-M763

Trazabilidad de activos con LoRaWAN e IA generativa. Dispositivos y codificación para ChirpStack

Trazabilidad de activos con LoRaWAN e IA generativa. Desarrollo de la plataforma de seguimiento con IA generativa

Trazabilidad de activos e IA generativa. Más allá de LoRaWAN. Motorola Moto Tag

Trazabilidad de activos e IA generativa. Impresión 3D de carcasas

Una vez solventados los asuntos de la infraestructura LoRaWAN, era el momento de ponerse a pensar en los dispositivos. En este sentido, dado que se trataba de un proyecto de posicionamiento y trazabilidad de activos, el primer punto a considerar era el de disponer de posicionamiento GPS. Y, obviamente, comunicaciones LoRaWAN. En este sentido, tenía a mi disposición tres tipos de dispositivos:

Heltec Cubecell AB01: Dispositivos de prototipado de Heltec, en la gama de Cubecell. Esta gama es una gama estupenda para trabajar con LoRaWAN. Programables como dispositivos Arduino, compatibles con la mayoría de los desarrollos para esta plataforma, y muy económico desde el punto de vista de uso de la energía. Sin embargo, carece de algo importante: el GPS. Sin embargo, esto se puede solucionar de manera sencilla acoplándole un módulo GPS externo, como es el caso del Neo 6M.

Diagrama Cubecell AB01 y el Neo 6M

La conexión con el Neo 6M se realiza usando los puertos 3V3 y GND, en lo referente a alimentación, y GPIO4 y GPIO5 para las comunicaciones E/S del dispositivo. Dista de ser una solución ideal, dado que el CubeCell puede entrar en modo de ahorro de energía, pero el módulo Neo 6M no lo hace. Sin embargo, como solución para prototipado es perfectamente válida, es muy económica, apenas unos pocos euros por cada uno de los dispositivos. Fue el primer dispositivo con el que empecé a trabajar en el proyecto.,

Dispositivos Heltec CubeCell AB02S: De nuevo, de la familia del AB01, pero a diferencia del anterior, integra una pequeña pantalla LCD y, sobre todo, un módulo GPS Air530 o Air530Z (dependiendo de la fecha de fabricación del dispositivo). Su principal ventaja es la integración del módulo GPS, que permite ir a un modo de ahorro de energía de apenas 21 uA en modo deep sleep, pero tiene como inconveniente un precio que ronda la treintena de euros. Sigue sin ser, sin embargo, un importe disparatado.

Heltec AB02S

El dispositivo, como decía, integra un módulo GPS y una antena en la propia placa, pero he podido comprobar que esta antena no es demasiado efectiva, por lo que es necesario contar con una antena GPS externa. Desde el punto de vista de la alimentación, me decidí a usar una alimentación por el puerto integrado que permite suministrarles 3.7v, lo que es perfecto para usar una batería recargable 18650. Así que con una carcasa impresa en 3D, queda algo tan solvente como lo que sigue:

Carcasa 3D con batería 18650 y regulador de carga

Dragino LGT-92: Sin duda, es el dispositivo más interesante. Es un dispositivo acabado, con carcasa externa, pulsador, batería y un acelerómetro de 9 ejes. También es el más caro, rondando los 90 euros, aunque es posible encontrarlo de oferta por importes sensiblemente inferiores.

Dragino LGT-92

Este dispositivo, aparte de ser programable, presenta una solución sólida con una buena antena GPS. Además el fabricante proporciona los codecs para ChirpStack, aunque en su versión 3.

Codificación de los dispositivos e integración con ChirpStack

Dispositivos Heltec

En el caso de los dispositivos Heltec, es preciso pasar por un proceso de programación, que puede realizarse con el IDE de Arduino. En ambos casos, uno de los puntos clave es aprovechar las capacidad de deep sleep de los dispositivos. El AB01, para comunicarse con el GPS, hace uso de la librería TinyGPS++, mientras que el AB02S hace uso de la GPS_Air530.h (o la variante Z si el dispositivo tiene esta versión del módulo).

El punto clave en ambos casos es la preparación de la carga útil para la transmisión de los datos. En mi caso, como partí de la versión AB01 con el Neo para la obtención de información, opté por transmitir una serie de valores básicos: latitud, longitud, altitud, velocidad codificada en km/h y rumbo en grados. Para transmitir estos valores hago uso de 14 bytes, de la siguiente manera:

  • 4 bytes para la latitud
  • 4 bytes para la longitud
  • 2 bytes para la altitud
  • 2 bytes para la velocidad
  • 2 bytes para el rumbo

…codificado de la siguiente manera:

      // Codificación simple: multiplica por 1e6 para enviar como entero 4 bytes cada uno
      int32_t lat_enc = (int32_t)(lat * 1e6);
      int32_t lng_enc = (int32_t)(lng * 1e6);
      int16_t alt_enc = (int16_t)gps.altitude.meters();  // Altitud en metros
      uint16_t spd_enc = (uint16_t)(gps.speed.kmph() * 100); // Velocidad x100 para decimales
      uint16_t crs_enc = (uint16_t)(gps.course.deg() * 100); // Curso/dirección en grados multiplicada por 100 (dos decimales)


      appDataSize = 14;  
      appData[0] = (lat_enc >> 24) & 0xFF;
      appData[1] = (lat_enc >> 16) & 0xFF;
      appData[2] = (lat_enc >> 8) & 0xFF;
      appData[3] = lat_enc & 0xFF;
      appData[4] = (lng_enc >> 24) & 0xFF;
      appData[5] = (lng_enc >> 16) & 0xFF;
      appData[6] = (lng_enc >> 8) & 0xFF;
      appData[7] = lng_enc & 0xFF;
      appData[8] = (alt_enc >> 8) & 0xFF;
      appData[9] = alt_enc & 0xFF;
      appData[10] = (spd_enc >> 8) & 0xFF;
      appData[11] = spd_enc & 0xFF;
      appData[12] = (crs_enc >> 8) & 0xFF;
      appData[13] = crs_enc & 0xFF;

Esto implica que, a la hora de crear el perfil de dispositivos en ChirpStack v4, es preciso definir una función de decodificación, que convierta la codificación anterior a un JSON con los valores obtenidos:

function decodeUplink(input) {
  const bytes = input.bytes;
  if (bytes.length < 8) {
    return {
      data: {},
      errors: ["Payload demasiado corto"]
    };
  }

  // Reconstruir enteros 32 bits big-endian para lat y lng
  const lat_enc = (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3];
  const lng_enc = (bytes[4] << 24) | (bytes[5] << 16) | (bytes[6] << 8) | bytes[7];
  
  
  // Altitud (2 bytes, big-endian, signed)
  const alt_enc = (bytes[8] << 8) | bytes[9];
  
  // Velocidad (2 bytes, big-endian, unsigned)
  const spd_enc = (bytes[10] << 8) | bytes[11];

  // Curso (2 bytes, big-endian, unsigned)
  const crs_enc = (bytes[12] << 8) | bytes[13];



  // Convertir a signed (considerando complemento a dos)
  const lat = lat_enc >> 0; 
  const lng = lng_enc >> 0;
  const alt = (alt_enc & 0x8000) ? alt_enc - 0x10000 : alt_enc; // Convierte a signed int16
  const spd = spd_enc / 100.0;  //# convertido a km/h con decimales
  const crs = crs_enc / 100.0;  //# grados con decimales

  return {
    data: {
      latitud: lat / 1e6,
      longitud: lng / 1e6,
      altitud: alt,
      velocidad: spd,
      rumbo: crs,
    },
    errors: [],
    warnings: []
  };
}

…lo que produce el siguiente resultado al decodificarlo en ChirpStack:

JSON procesado por ChirpStack

Dispositivos Dragino

En el caso de Dragino, la solución es bastante más sencilla a priori. Los dispositivos ya vienen codificados para funcionar desde el principio, lo único necesario es definir el perfil de los dispositivos en ChirpStack, y el fabricante proporciona los codecs. Sin embargo, en el caso de ChirpStack sólo son compatibles para la v3. En mi caso, y con la ayuda de Perplexity, he codificado una versión para v4:

function decodeUplink(input) {
  var bytes = input.bytes;
  var fPort = input.fPort;

  // Aquí puedes usar tu lógica para decodificar los bytes según el puerto o tipo de mensaje
  var data = {
    latitud: (bytes[0]<<24 | bytes[1]<<16 | bytes[2]<<8 | bytes[3]) / 1000000,
    longitud: (bytes[4]<<24 | bytes[5]<<16 | bytes[6]<<8 | bytes[7]) / 1000000,
    ALARM_status: (bytes[8] & 0x40) > 0,
    BatV: ((bytes[8] & 0x3f)<<8 | bytes[9]) / 1000,
    MD: { "0": "Disable", "1": "Move", "2": "Collide", "3": "Custom" }[bytes[10]>>6],
    LON: (bytes[10] & 0x20) ? "ON" : "OFF",
    FW: 150 + (bytes[10] & 0x1f),
    Roll: (bytes[11]<<24>>16 | bytes[12]) / 100,
    Pitch: (bytes[13]<<24>>16 | bytes[14]) / 100
  };

  return { data: data }; // Es importante devolver un objeto con la propiedad data
}

Con todo esto, los dispositivos están listos para su uso en ChirpStack, con lo que estamos listos para el siguiente punto del proyecto: el desarrollo de la aplicación de trazabilidad de activos.

Trazabilidad de activos con LoRaWAN e IA generativa

Trazabilidad de activos con LoRaWAN e IA generativa. Gateway Heltec HT-M763 Trazabilidad de activos con LoRaWAN e IA generativa. Desarrollo de la plataforma de seguimiento con IA generativa

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.