El ioTracker 3, de ioThings, es un dispositivo IoT provisto de conectividad LoRaWAN para lograr la transmisión de datos, así como Wi-Fi y Bluetooth para fines de configuración. Tiene varios sensores integrados para medir temperatura, humedad, luz y diversas tecnologías de localización de precisión, incluyendo GPS, Galileo y triangulación LoRa, entre otros.
Requisitos
Un dispositivo ioTracker 3
Una cuenta activa en la consola de ioTracker
Una cuenta activa en la consola de The Things Network
Un dispositivo Android con Bluetooth
1. Configura el ioTracker
Activa Bluetooth en tu dispositivo Android.
Lanza la tienda de aplicaciones.
Busca “ioTracker Configurator” e instálalo.
Toma tu ioTracker 3, presiona y mantén el botón hasta que el LED comience a parpadear en azul, luego presiona y suelta brevemente. Esto hará que el ioTracker sea detectable por la interfaz Bluetooth de tu dispositivo Android.
Consejo Profesional: No sueltes el botón inmediatamente en la segunda pulsación, debes mantenerlo presionado durante al menos medio segundo.
Lanza la aplicación “IoTracker Configurator” .
Toca en “INICIAR SESIÓN” e ingresa tus credenciales de cuenta de ioTracker.
Podrás ver tu dispositivo listado. Toca en él.
Haz clic en las "opciones LoRaWAN".
Busca el ícono de lápiz en la esquina superior derecha de la pantalla y haz clic en él.
Copia la siguiente información y asegúrate de no editarla. Será necesaria en los próximos pasos:
DevEUI
AppEUI
AppKey
Cambia la región LoRaWAN de acuerdo a tus necesidades tocando el menú desplegable y seleccionando la opción apropiada. Puedes averiguar qué banda de frecuencia está permitida en tu región aquí.
Haz clic en el botón “Actualizar” para guardar la configuración de la región LoRaWAN.
Cierra la aplicación.
Consejo Profesional: En realidad puedes editar el AppEui y establecer cualquier valor que desees, sin embargo, este debe coincidir con esta configuración al registrar el dispositivo en el LNS.
2. Registra el ioTracker en TTN LNS
Ve a la consola de TTN e inicia sesión.
Ve a la pestaña “Aplicaciones” y haz clic en el botón “+ Agregar aplicación” .
Dale a tu aplicación un nombre significativo, ID de aplicación y descripción.
Haz clic en “Crear aplicación”.
Haz clic en “+ Agregar dispositivo final”.
Selecciona “Ingresar detalles del dispositivo final manualmente”.
Ve al menú desplegable “Plan de frecuencia” y elige el plan de frecuencia de acuerdo a tu región. En este caso, la región en la que nos encontramos permite la banda US915 para aplicaciones LoRaWAN.
Ve a “Versión de LoRaWAN” y allí elige “Especificación de LoRaWAN 1.0.4” del menú desplegable.
Ve al menú desplegable “Versión de parámetros regionales” y selecciona “RP002 Parámetros regionales 1.0.0”.
Desplázate hacia abajo hasta la sección “Información de aprovisionamiento” y establece el valor de “JoinEUI” al de AppEUI del dispositivo obtenido en la primera sección. Si no editaste su valor, debería ser todo ceros. Luego haz clic en el botón “Confirmar” una vez que esté habilitado.
Los siguientes ajustes se volverán editables, hazlo de la siguiente manera:
DevEUI: Establece este valor de acuerdo al DevEUI del dispositivo obtenido en la primera sección.
AppKey: Establece este valor de acuerdo al AppKey del dispositivo obtenido en la primera sección.
ID del dispositivo final: Asigna a este campo un nombre significativo que te ayude a identificarlo fácilmente, como “io-tracker-01”, “io-tracker-home” o cualquier cosa que te convenga.
Haz clic en “Registrar dispositivo final”.
Se te redirigirá a una nueva página donde se mostrarán el “ID del dispositivo final” y la configuración de LoRaWAN.
3. Configura el Formateador de Payload
Haz clic en el menú desplegable “Formateadores de payload”.
Haz clic en “Uplink”.
Ve a la sección “Tipo de formateador” y selecciona “Formateador Javascript personalizado”.
Ve a la sección “Código del formateador”, elimina todo el código allí y pega lo siguiente:
Código del formateador de payload:
"use strict";/* eslint no-bitwise: ["error", { "allow": ["&", "<<", ">>", "|"] }] *//* eslint no-plusplus: "off" */function Decoder(bytes) { var decoded = {}; var index = 0; function toSignedChar(byte) { return (byte & 127) - (byte & 128); } function toSignedShort(byte1, byte2) { var sign = byte1 & 1 << 7; var x = (byte1 & 0xFF) << 8 | byte2 & 0xFF; if (sign) { return 0xFFFF0000 | x; } return x; } function toUnsignedShort(byte1, byte2) { return (byte1 << 8) + byte2; } function toSignedInteger(byte1, byte2, byte3, byte4) { return (byte1 << 24) | (byte2 << 16) | (byte3 << 8) | byte4; } function bytesToHexString(bytes){ if (!bytes){ return null; } bytes = new Uint8Array(bytes); var hexBytes = []; for (var i = 0; i < bytes.length; ++i) { var byteString = bytes[i].toString(16); if (byteString.length < 2){ byteString = "0" + byteString; } hexBytes.push(byteString); } return hexBytes.join(""); } function substring(source, offset, length) { var buffer = new Uint8Array(length); for(var i = 0; i < length; i++) { buffer[i] = source[offset+i]; } return bytesToHexString(buffer); } function parseBluetoothBeacons00() { var beaconStatus = bytes[index++]; var beaconType = beaconStatus & 0x03; var rssiRaw = beaconStatus >> 2; var rssi = 27 - rssiRaw * 2; var beacon = void 0; switch (beaconType) { case 0x00: beacon = { type: 'ibeacon', rssi: rssi, uuid: substring(bytes, index, 2), major: substring(bytes, index + 2, 2), minor: substring(bytes, index + 4, 2) }; index += 6; return beacon; case 0x01: beacon = { type: 'eddystone', rssi: rssi, instance: substring(bytes, index, 6) }; index += 6; return beacon; case 0x02: beacon = { type: 'altbeacon', rssi: rssi, id1: substring(bytes, index, 2), id2: substring(bytes, index + 2, 2), id3: substring(bytes, index + 4, 2) }; index += 6; return beacon; case 0x03: beacon = { type: 'fullbeacon', rssi: rssi, id1: substring(bytes, index, 2), id2: substring(bytes, index + 2, 2), id3: substring(bytes, index + 4, 2) }; index += 6; return beacon; default: return null; } } function parseBluetoothBeacons01() { var beaconStatus = bytes[index++]; var beaconType = beaconStatus & 0x03; var rssiRaw = beaconStatus >> 2; var rssi = 27 - rssiRaw * 2; var beacon = void 0; switch (beaconType) { case 0x00: beacon = { type: 'ibeacon', rssi: rssi, uuid: substring(bytes, index, 16), major: substring(bytes, index + 16, 2), minor: substring(bytes, index + 18, 2) }; index += 20; return beacon; case 0x01: beacon = { type: 'eddystone', rssi: rssi, namespace: substring(bytes, index, 10), instance: substring(bytes, index + 10, 6) }; index += 16; return beacon; case 0x02: beacon = { type: 'altbeacon', rssi: rssi, id1: substring(bytes, index, 16), id2: substring(bytes, index + 16, 2), id3: substring(bytes, index + 18, 2) }; index += 20; return beacon; case 0x03: beacon = { type: 'fullbeacon', rssi: rssi, id1: substring(bytes, index, 16), id2: substring(bytes, index + 16, 2), id3: substring(bytes, index + 18, 2) }; index += 20; return beacon; default: return null; } } function parseBluetoothBeacons02() { var beaconStatus = bytes[index++]; var beaconType = beaconStatus & 0x03; var slotMatch = beaconStatus >> 2 & 0x07; var rssiRaw = bytes[index++] & 63; var rssi = 27 - rssiRaw * 2; var beacon = void 0; switch (beaconType) { case 0x00: beacon = { type: 'ibeacon', rssi: rssi, slot: slotMatch, major: substring(bytes, index, 2), minor: substring(bytes, index + 2, 2) }; index += 4; return beacon; case 0x01: beacon = { type: 'eddystone', rssi: rssi, slot: slotMatch, instance: substring(bytes, index, 6) }; index += 6; return beacon; case 0x02: beacon = { type: 'altbeacon', rssi: rssi, slot: slotMatch, id2: substring(bytes, index, 2), id3: substring(bytes, index + 2, 2) }; index += 4; return beacon; case 0x03: beacon = { type: 'fullbeacon', rssi: rssi, slot: slotMatch, id2: substring(bytes, index, 2), id3: substring(bytes, index + 2, 2) }; index += 6; return beacon; default: return null; } } var headerByte = bytes[index++]; decoded.uplinkReasonButton = !!(headerByte & 1); decoded.uplinkReasonMovement = !!(headerByte & 2); decoded.uplinkReasonGpio = !!(headerByte & 4); decoded.containsGps = !!(headerByte & 8); decoded.containsOnboardSensors = !!(headerByte & 16); decoded.containsSpecial = !!(headerByte & 32); decoded.crc = bytes[index++]; decoded.batteryLevel = bytes[index++]; if (decoded.containsOnboardSensors) { var sensorContent = bytes[index++]; decoded.sensorContent = { containsTemperature: !!(sensorContent & 1), containsLight: !!(sensorContent & 2), containsAccelerometerCurrent: !!(sensorContent & 4), containsAccelerometerMax: !!(sensorContent & 8), containsWifiPositioningData: !!(sensorContent & 16), buttonEventInfo: !!(sensorContent & 32), containsExternalSensors: !!(sensorContent & 64), containsBluetoothData: false }; var hasSecondSensorContent = !!(sensorContent & 128); if (hasSecondSensorContent) { var sensorContent2 = bytes[index++]; decoded.sensorContent.containsBluetoothData = !!(sensorContent2 & 1); decoded.sensorContent.containsRelativeHumidity = !!(sensorContent2 & 2); decoded.sensorContent.containsAirPressure = !!(sensorContent2 & 4); decoded.sensorContent.containsManDown = !!(sensorContent2 & 8); decoded.sensorContent.containsTilt = !!(sensorContent2 & 16); } if (decoded.sensorContent.containsTemperature) { decoded.temperature = toSignedShort(bytes[index++], bytes[index++]) / 100; } if (decoded.sensorContent.containsLight) { var value = (bytes[index++] << 8) + bytes[index++]; var exponent = value >> 12 & 0xFF; decoded.lightIntensity = ((value & 0x0FFF) << exponent) / 100; } if (decoded.sensorContent.containsAccelerometerCurrent) { decoded.accelerometer = { x: toSignedShort(bytes[index++], bytes[index++]) / 1000, y: toSignedShort(bytes[index++], bytes[index++]) / 1000, z: toSignedShort(bytes[index++], bytes[index++]) / 1000 }; } if (decoded.sensorContent.containsAccelerometerMax) { decoded.maxAccelerationNew = toSignedShort(bytes[index++], bytes[index++]) / 1000; decoded.maxAccelerationHistory = toSignedShort(bytes[index++], bytes[index++]) / 1000; } if (decoded.sensorContent.containsWifiPositioningData) { var wifiInfo = bytes[index++]; var numAccessPoints = wifiInfo & 7; var wifiStatus = ((wifiInfo & 8) >> 2) + ((wifiInfo & 16) >> 3); var containsSignalStrength = wifiInfo & 32; var wifiStatusDescription = void 0; switch (wifiStatus) { case 0: wifiStatusDescription = 'success'; break; case 1: wifiStatusDescription = 'failed'; break; case 2: wifiStatusDescription = 'no_access_points'; break; default: wifiStatusDescription = "unknown (" + wifiStatus + ")"; } decoded.wifiInfo = { status: wifiStatusDescription, statusCode: wifiStatus, accessPoints: [] }; for (var i = 0; i < numAccessPoints; i++) { var macAddress = [bytes[index++].toString(16), bytes[index++].toString(16), bytes[index++].toString(16), bytes[index++].toString(16), bytes[index++].toString(16), bytes[index++].toString(16)]; var signalStrength = void 0; if (containsSignalStrength) { signalStrength = toSignedChar(bytes[index++]); } else { signalStrength = null; } decoded.wifiInfo.accessPoints.push({ macAddress: macAddress.join(':'), signalStrength: signalStrength }); } } if (decoded.sensorContent.containsExternalSensors) { var type = bytes[index++]; switch (type) { case 0x0A: decoded.externalSensor = { type: 'battery', batteryA: toUnsignedShort(bytes[index++], bytes[index++]), batteryB: toUnsignedShort(bytes[index++], bytes[index++]) }; break; case 0x65: decoded.externalSensor = { type: 'detectSwitch', value: bytes[index++] }; break; } } if (decoded.sensorContent.containsBluetoothData) { var bluetoothInfo = bytes[index++]; var numBeacons = bluetoothInfo & 7; var bluetoothStatus = bluetoothInfo >> 3 & 0x03; var addSlotInfo = bluetoothInfo >> 5 & 0x03; var bluetoothStatusDescription = void 0; switch (bluetoothStatus) { case 0: bluetoothStatusDescription = 'success'; break; case 1: bluetoothStatusDescription = 'failed'; break; case 2: bluetoothStatusDescription = 'no_access_points'; break; default: bluetoothStatusDescription = "unknown (" + bluetoothStatus + ")"; } decoded.bluetoothInfo = { status: bluetoothStatusDescription, statusCode: bluetoothStatus, addSlotInfo: addSlotInfo, beacons: [] }; for (var _i = 0; _i < numBeacons; _i++) { var beacon; switch (addSlotInfo) { case 0x00: beacon = parseBluetoothBeacons00(); break; case 0x01: beacon = parseBluetoothBeacons01(); break; case 0x02: beacon = parseBluetoothBeacons02(); break; default: return {errors: ['Invalid addSlotInfo type']}; } if (beacon === null) { return {errors: ['Invalid beacon type']}; } decoded.bluetoothInfo.beacons.push(beacon); } } if (decoded.sensorContent.containsRelativeHumidity) { decoded.relativeHumidity = toUnsignedShort(bytes[index++], bytes[index++]) / 100; } if (decoded.sensorContent.containsAirPressure) { decoded.airPressure = (bytes[index++] << 16) + (bytes[index++] << 8) + bytes[index++]; } if (decoded.sensorContent.containsManDown) { var manDownData = (bytes[index++]); var manDownState = (manDownData & 0x0f); var manDownStateLabel; switch(manDownState) { case 0x00: manDownStateLabel = 'ok'; break; case 0x01: manDownStateLabel = 'sleeping'; break; case 0x02: manDownStateLabel = 'preAlarm'; break; case 0x03: manDownStateLabel = 'alarm'; break; default: manDownStateLabel = manDownState+''; break; } decoded.manDown = { state: manDownStateLabel, positionAlarm: !!(manDownData & 0x10), movementAlarm: !!(manDownData & 0x20) }; } if (decoded.sensorContent.containsTilt) { decoded.tilt = { currentTilt: toUnsignedShort(bytes[index++], bytes[index++]) / 100, currentDirection: Math.round(bytes[index++] * (360/255)), maximumTiltHistory: toUnsignedShort(bytes[index++], bytes[index++]) / 100, DirectionHistory: Math.round(bytes[index++] * (360/255)), }; } } if (decoded.containsGps) { decoded.gps = {}; decoded.gps.navStat = bytes[index++]; decoded.gps.latitude = toSignedInteger( bytes[index++], bytes[index++], bytes[index++], bytes[index++] ) / 10000000; decoded.gps.longitude = toSignedInteger( bytes[index++], bytes[index++], bytes[index++], bytes[index++] ) / 10000000; decoded.gps.altRef = toUnsignedShort(bytes[index++], bytes[index++]) / 10; decoded.gps.hAcc = bytes[index++]; decoded.gps.vAcc = bytes[index++]; decoded.gps.sog = toUnsignedShort(bytes[index++], bytes[index++]) / 10; decoded.gps.cog = toUnsignedShort(bytes[index++], bytes[index++]) / 10; decoded.gps.hdop = bytes[index++] / 10; decoded.gps.numSvs = bytes[index++]; } return decoded;}// Exportar función (para implementaciones y para pruebas)if (typeof module !== 'undefined' && module.exports !== null) { module.exports = Decoder;}
Haz clic en “Guardar cambios”
4. Configura la Integración de TTN a Ubidots
Ve a Ubidots e inicia sesión en tu cuenta.
Dirígete a la sección “Dispositivos” → “Plugins”.
Crea un nuevo plugin haciendo clic en el signo "+" en la esquina superior derecha.
Completa los campos correspondientes, como el nombre del plugin, token, etc.
Una vez creado el plugin, dirígete a su sección “Decodificador”.
Copia la “URL de Endpoint HTTPs”.
Desplázate hacia abajo hasta la sección “Función de Decodificación”.
Elimina todo el código allí y pega lo siguiente:
function format_payload(args){ var payload = args["uplink_message"]["decoded_payload"]; var ubidotsPayload = {}; var batteryLevel = payload["batteryLevel"] * (100 / 254); ubidotsPayload["batteryLevel"] = batteryLevel; ubidotsPayload["lightIntensity"] = payload["lightIntensity"]; ubidotsPayload["temperature"] = payload["temperature"]; if(payload["containsGps"] === true){ var lat = payload["gps"]["latitude"]; var lng = payload["gps"]["longitude"]; ubidotsPayload["position"] = { "value":1, "context":{ "lat":lat, "lng":lng, } }; } if(payload["sensorContent"]["containsTilt"] === true) { var tilt = payload["tilt"]["currentTilt"]; ubidotsPayload["tilt"] = tilt; } return ubidotsPayload; }module.exports = { format_payload };
Haz clic en el “GUARDAR Y HACER VISIBLE” para guardar los cambios.
Regresa a la consola de TTN.
Haz clic en el menú desplegable “Integraciones”.
Haz clic en “Webhooks”.
Busca Ubidots entre la lista de diferentes plataformas.
Completa estos campos de la siguiente manera:
ID de Webhook: Establece un nombre significativo, como “ioTracker-integration” o cualquier cosa que consideres adecuada para tus propósitos.
ID de Plugin: Es la última porción de la “URL de Endpoint HTTPs” de tu plugin de Ubidots después de la última “/”. Es decir, si la URL de tu endpoint es “https://dataplugin.ubidots.com/api/web-hook/lN4s2dlb4IgPgpp4Xeoq02stXcE=”, entonces el ID de tu plugin debería ser “lN4s2dlb4IgPgpp4Xeoq02stXcE=”
Token de Ubidots: Tu Token de Ubidots.
Haz clic en “Crear Webhook de Ubidots”.
5. Visualiza Datos en Ubidots
Regresa a Ubidots → sección “Dispositivos” y podrás ver un nuevo dispositivo creado cuyo nombre es el “ID del dispositivo” que configuraste en TTN LNS, en este caso, se muestra “io-tracker3” .