El Solar Bluetooth Gateway de Lansitec permite la recopilación remota de datos de balizas Bluetooth a través de redes LoRaWAN. Diseñado para entornos de bajo consumo, utiliza una batería alimentada por energía solar para operar de forma autónoma y transmitir datos de sensores a plataformas en la nube, como Ubidots.
Requisitos
Lansitec Solar Bluetooth Gateway
Gateway LoRaWAN
Cuenta en The Things Industries (TTI)
1. Configura el Gateway LoRaWAN
Aunque las imágenes de esta guía muestran un gateway LoRaWAN de RAK Wireless, el proceso de integración no se limita a dispositivos RAK. Puedes usar cualquier gateway LoRaWAN que soporte reenvío de paquetes a The Things Industries. El requisito clave es que el gateway esté correctamente configurado para reenviar datos a tu instancia de TTI. Mientras esa condición se cumpla, el resto de la integración —registro del dispositivo, decodificación y reenvío de datos a Ubidots— será igual. |
El primer paso es configurar tu gateway LoRaWAN para reenviar datos a The Things Industries.
Inicia sesión en la interfaz web de tu gateway.
Ve a Configuración de red.
Activa el Modo de Reenvío de Paquetes.
Ingresa los parámetros de reenvío.
Haz clic en Guardar y aplicar.
Tu gateway ahora está listo para reenviar paquetes de datos.
2. Registra el Gateway en The Things Industries (TTI)
Inicia sesión en la Consola de The Things Stack.
Presiona Registrar gateway.
Ingresa el Gateway EUI y los demás campos requeridos.
Haz clic en Registrar gateway.
Una vez guardado, tu gateway aparecerá como Desconectado hasta que reciba su primer paquete.
3. Agrega el Solar Bluetooth Gateway como un Dispositivo
Ve a Aplicaciones en TTI y selecciona tu aplicación.
Ve a la sección Dispositivos finales y haz clic en Registrar dispositivo final.
En JoinEUI, pega el App EUI del dispositivo (se encuentra en la hoja de datos o etiqueta).
Completa los campos restantes: DevEUI, AppKey, etc.
Haz clic en Registrar dispositivo final.
Tu Solar Bluetooth Gateway ahora está registrado como un dispositivo en TTI.
4. Agrega el Formateador de Payload en TTI
Para decodificar los datos de subida, se requiere un formateador personalizado en JavaScript:
Dentro del dispositivo registrado, ve a Payload Formatters.
Selecciona Custom Javascript Formatter.
Pega el script del Solar Bluetooth Gateway:
// Decode uplink function.
//
// Input is an object with the following fields:
// - bytes = Byte array containing the uplink payload, e.g. [255, 230, 255, 0]
// - fPort = Uplink fPort.
// - variables = Object containing the configured device variables.
//
// Output must be an object with the following fields:
// - data = Object representing the decoded payload.
function decodeUplink(input) {
// bytes
var bytes = input.bytes;
// type
var uplinkType = (bytes[0] >> 4) & 0x0f;
switch (uplinkType) {
case 0x01:
return { data: decodeRegistration(bytes) };
case 0x02:
return { data: decodeHeartbeat(bytes) };
case 0x03:
return { data: decodeDeviceReportRule(bytes) };
case 0x05:
return { data: decodeWaterLevelDetection(bytes) };
case 0x08:
return { data: decodeDeviceType1(bytes) };
case 0x09:
return { data: decodeDeviceType2(bytes) };
case 0x0a:
return { data: decodeDeviceType3(bytes) };
case 0x0e:
return { data: decodeMultiDeviceTypeMessage(bytes) };
case 0x0f:
return { data: decodeAcknowledgment(bytes) };
default:
return null;
}
}
// type: 0x1 Registration
function decodeRegistration(bytes) {
var data = {};
data.type = "Registration";
// adr
data.adr = ((bytes[0] >> 3) & 0x1) == 0 ? "OFF" : "ON";
// mode
data.mode = bytes[0] & 0x07;
// loRaWANBand
var loRaWANBandValue = bytes[1];
if (loRaWANBandValue == 0x00) {
data.loRaWANBand = "KR920";
} else if (loRaWANBandValue == 0x01) {
data.loRaWANBand = "AU915";
} else if (loRaWANBandValue == 0x04) {
data.loRaWANBand = "CN470";
} else if (loRaWANBandValue == 0x08) {
data.loRaWANBand = "AS923";
} else if (loRaWANBandValue == 0x10) {
data.loRaWANBand = "EU433";
} else if (loRaWANBandValue == 0x20) {
data.loRaWANBand = "EU868";
} else if (loRaWANBandValue == 0x40) {
data.loRaWANBand = "US915";
}
// power
data.power = ((bytes[2] >> 3) & 0x1f) + "dBm";
// continuousBleReceiveEnable
data.continuousBleReceiveEnable = ((bytes[2] >> 1) & 0x1) == 0 ? "Disable" : "Enable";
// dr
data.dr = (bytes[3] >> 4) & 0x0f;
// positionReportInterval
data.positionReportInterval = (((bytes[4] << 8) & 0xff00) | (bytes[5] & 0xff)) * 5 + "s";
// heartbeatInterval
data.heartbeatInterval = bytes[6] * 30 + "s";
// bleReceivingDuration
data.bleReceivingDuration = bytes[7] + "s";
// networkReconnectionInterval
data.networkReconnectionInterval = bytes[8] * 5 + "min";
return data;
}
// type: 0x2 Heartbeat
function decodeHeartbeat(bytes) {
var data = {};
// type
data.type = "Heartbeat";
// battery
var batteryValue = bytes[1];
if (batteryValue > 100) {
data.battery = bytes[1] / 100 + 1.5 + "V";
} else {
data.battery = bytes[1] + "%";
}
// rssi
data.rssi = bytes[2] * -1 + "dBm";
// snr
data.snr = (((bytes[3] << 8) & 0xff00) | (bytes[4] & 0xff)) / 100 + "dB";
// version
data.version = ((bytes[5] << 8) & 0xff00) | (bytes[6] & 0xff);
// chargeState
var chargeStateValue = bytes[7] & 0xff;
if (chargeStateValue == 0x00) {
data.chargeState = "Not charging";
} else if (chargeStateValue == 0x50) {
data.chargeState = "Charging";
} else if (chargeStateValue == 0x60) {
data.chargeState = "Charging completed";
}
return data;
}
// type: 0x3 DeviceReportRule
function decodeDeviceReportRule(bytes) {
var data = {};
data.type = "DeviceReportRule";
data.deviceTypeQuantity = bytes[1] & 0xff;
data.deviceTypeId = (bytes[2] >> 4) & 0x0f;
data.filterAndDataBlockQuantity = bytes[2] & 0x0f;
var filterBlock = [];
var dataBlock = [];
var macBlock = [];
var index = 3;
for (let i = 0; i < data.filterAndDataBlockQuantity; i++) {
var ruleType = bytes[index++] & 0xff;
var startAddress = bytes[index++] & 0xff;
var endAddress = bytes[index++] & 0xff;
var filter = {};
if (ruleType == 1) {
filter.ruleType = "FilterBlock";
filter.startAddress = byteToHex(startAddress);
filter.endAddress = byteToHex(endAddress);
var len = endAddress - startAddress;
var filterValue = "";
for (let j = 0; j < len + 1; j++) {
filterValue += byteToHex(bytes[index + j]);
}
filter.value = filterValue;
index = index + (endAddress - startAddress + 1);
filterBlock.push(filter);
} else if (ruleType == 2) {
filter.ruleType = "DataBlock";
filter.startAddress = byteToHex(startAddress);
filter.endAddress = byteToHex(endAddress);
dataBlock.push(filter);
} else if (ruleType == 3) {
filter.ruleType = "MACBlock";
filter.startAddress = byteToHex(startAddress);
filter.endAddress = byteToHex(endAddress);
macBlock.push(filter);
}
}
data.filterBlock = filterBlock;
data.dataBlock = dataBlock;
data.macBlock = macBlock;
return data;
}
// type: 0x5 WaterLevelDetection
function decodeWaterLevelDetection(bytes) {
var data = {};
data.type = "WaterLevelDetection";
data.waterLevel = (((bytes[1] << 8) & 0xff00) | (bytes[2] & 0xff)) + "mm";
return data;
}
// type: 0x8 DeviceType1
function decodeDeviceType1(bytes) {
var data = {
type: "DeviceType1",
number: bytes[0] & 0x0f
};
var index = 1;
for (let i = 0; i < data.number; i++) {
var major = ((bytes[index] << 8) | bytes[index + 1]).toString(16).toUpperCase().padStart(4, "0");
var minor = ((bytes[index + 2] << 8) | bytes[index + 3]).toString(16).toUpperCase().padStart(4, "0");
var rssi = bytes[index + 4] - 256 + "dBm";
data["beacon" + (i + 1)] = major + minor;
data["rssi" + (i + 1)] = rssi;
index = index + 5;
}
return data;
}
// type: 0x9 DeviceType2
function decodeDeviceType2(bytes) {
var data = {
type: "DeviceType2",
number: bytes[0] & 0x0f
};
var index = 1;
for (let i = 0; i < data.number; i++) {
var major = ((bytes[index] << 8) | bytes[index + 1]).toString(16).toUpperCase().padStart(4, "0");
var minor = ((bytes[index + 2] << 8) | bytes[index + 3]).toString(16).toUpperCase().padStart(4, "0");
var rssi = bytes[index + 4] - 256 + "dBm";
data["beacon" + (i + 1)] = major + minor;
data["rssi" + (i + 1)] = rssi;
index = index + 5;
}
return data;
}
// type: 0xa DeviceType3
function decodeDeviceType3(bytes) {
var data = {
type: "DeviceType3",
number: bytes[0] & 0x0f
};
var index = 1;
for (let i = 0; i < data.number; i++) {
var major = ((bytes[index] << 8) | bytes[index + 1]).toString(16).toUpperCase().padStart(4, "0");
var minor = ((bytes[index + 2] << 8) | bytes[index + 3]).toString(16).toUpperCase().padStart(4, "0");
var rssi = bytes[index + 4] - 256 + "dBm";
data["beacon" + (i + 1)] = major + minor;
data["rssi" + (i + 1)] = rssi;
index = index + 5;
}
return data;
}
// type: 0xe MultiDeviceTypeMessage
function decodeMultiDeviceTypeMessage(bytes) {
var data = {
type: "MultiDeviceTypeMessage",
number: bytes[0] & 0x0f
};
for (let i = 0; i < data.number; i++) {
var index = 1 + 6 * i;
var deviceTypeId = bytes[index];
var major = ((bytes[index + 1] << 8) | bytes[index + 2]).toString(16).toUpperCase().padStart(4, "0");
var minor = ((bytes[index + 3] << 8) | bytes[index + 4]).toString(16).toUpperCase().padStart(4, "0");
var rssi = bytes[index + 5] - 256 + "dBm";
data["deviceTypeId" + (i + 1)] = deviceTypeId;
data["beacon" + (i + 1)] = major + minor;
data["rssi" + (i + 1)] = rssi;
index = index + 6;
}
return data;
}
// type: 0xf Acknowledgment
function decodeAcknowledgment(bytes) {
var data = {};
data.type = "Acknowledgment";
data.result = (bytes[0] & 0x0f) == 0 ? "Success" : "Failure";
data.msgId = (bytes[1] & 0xff).toString(16).toUpperCase();
return data;
}
function byteToHex(str) {
return str.toString(16).toUpperCase().padStart(2, "0");
}
Haz clic en Guardar cambios.
Este formateador analiza mensajes como el estado del latido, el registro del dispositivo, lecturas BLE y más.
5. Crea un Webhook para Enviar Datos a Ubidots
Dentro de tu aplicación TTI, ve a Webhooks.
Haz clic en + Agregar webhook.
Elige Ubidots de la lista de socios.
Llena los campos requeridos:
ID del Webhook: nombre en minúscula (ej. solar_ble_gateway)
ID del Plugin: La última parte de la URL del plugin de Ubidots (después del último /). Por ejemplo, si la URL del endpoint es “
https://dataplugin.ubidots.com/api/web-hook/lN4s2dlb4IgPgpp4Xeoq02stXcE=
”, entonces tu ID de plugin es “lN4s2dlb4IgPgpp4Xeoq02stXcE=
”Token de Ubidots: el token de tu cuenta en Ubidots
Desplázate hacia abajo y haz clic en Crear Webhook de Ubidots.
6. Crea el Plugin y el Decodificador en Ubidots
Ahora, configuremos el receptor en Ubidots.
Ve a Dev Center → Plugins.
Haz clic en el botón “+” y elige The Things Stack.
Llena los campos requeridos (tipo de dispositivo y token de Ubidots).
Una vez creado el plugin, edítalo y ve a su sección de Decoder.
Elimina el decodificador por defecto y pega este en su lugar:
// Ubidots decoder
async function formatPayload(args) {
var ubidots_payload = {};
// Log received data for debugging purposes:
// console.log(JSON.stringify(args));
// Get RSSI and SNR variables using gateways data:
var gateways = args['uplink_message']['rx_metadata'];
for (const i in gateways) {
var gw = gateways[i];
var gw_eui = gw['gateway_ids']['eui'];
var gw_id = gw['gateway_ids']['gateway_id'];
// Build RSSI and SNR variables
ubidots_payload['rssi-' + gw_id] = {
value: gw['rssi'],
context: {
channel_index: gw['channel_index'],
channel_rssi: gw['channel_rssi'],
gw_eui: gw_eui,
gw_id: gw_id,
uplink_token: gw['uplink_token']
}
};
ubidots_payload['snr-' + gw_id] = gw['snr'];
}
// Get Fcnt and Port variables:
ubidots_payload['f_cnt'] = args['uplink_message']['f_cnt'];
ubidots_payload['f_port'] = args['uplink_message']['f_port'];
// Get uplink's timestamp
ubidots_payload['timestamp'] = new Date(
args['uplink_message']['received_at']
).getTime();
// If you're already decoding in TTS using payload formatters,
// then uncomment the following line to use "uplink_message.decoded_payload".
// PROTIP: Make sure the incoming decoded payload is an Ubidots-compatible JSON
// (See https://ubidots.com/docs/hw/#sending-data)
var decoded_payload = args['uplink_message']['decoded_payload'];
// By default, this plugin uses "uplink_message.frm_payload" and sends it to
// the decoding function "decodeUplink".
// For more vendor-specific decoders, check out:
// https://github.com/TheThingsNetwork/lorawan-devices/tree/master/vendor
// let bytes = Buffer.from(args['uplink_message']['frm_payload'], 'base64');
// var decoded_payload = decodeUplink(bytes)['data'];
// Merge decoded payload into Ubidots payload
// Object.assign(ubidots_payload, decoded_payload);
Object.keys(decoded_payload).forEach(key => {
const value = decoded_payload[key];
if (typeof value === 'number') {
ubidots_payload[key] = value; // Direct numeric value
} else if (!isNaN(parseFloat(value))) {
ubidots_payload[key] = parseFloat(value); // e.g., "46%" or "10dB"
} else {
// Non-numeric string values (e.g., battery state)
ubidots_payload[key] = {
value: 1,
context: { label: value }
};
}
});
return ubidots_payload;
}
// function decodeUplink(bytes) {
// // Decoder for the RAK1906 WisBlock Environmental Sensor
// // https://store.rakwireless.com/products/rak1906-bme680-environment-sensor
// var decoded = {};
// if (bytes[0] == 1) {
// // If received data is of Environment Monitoring type
// decoded.temperature = (bytes[1] << 8 | (bytes[2])) / 100;
// decoded.humidity = (bytes[3] << 8 | (bytes[4])) / 100;
// decoded.pressure = (bytes[8] | (bytes[7] << 8) | (bytes[6] << 16) | (bytes[5] << 24)) / 100;
// decoded.gas = bytes[12] | (bytes[11] << 8) | (bytes[10] << 16) | (bytes[9] << 24);
// }
// return { data: decoded };
// }
module.exports = { formatPayload };Guarda los cambios haciendo clic en Guardar y publicar.
7. Visualiza los Datos en Ubidots
Después de completar los pasos anteriores, los datos comenzarán a aparecer en tu cuenta de Ubidots.
Ve a Dispositivos.
Busca tu Solar Bluetooth Gateway por nombre o ID.
Haz clic en el dispositivo para ver los datos entrantes.
Ahora puedes construir tableros, widgets y alertas utilizando estos datos.