Decoder-Bibliothek
Damit deine Uplinks auf der Karte erscheinen, müssen die GPS-Koordinaten als
latitude / longitude
im Decoded Payload stehen. Hier findest du fertige Payload-Formatter zum Einfügen in die TTS Console:
End Devices → [Gerät] → Payload formatters → Uplink → Custom Javascript.
Dragino LGT-92 (GPS-Tracker)
Klassiker für Asset-Tracking. Payload enthält Position, Höhe, Batterie, Beschleunigung.function decodeUplink(input) {
var b = input.bytes;
var data = {};
// Lat / Lon (signed int32, /1e6)
var lat = (b[0]<<24) | (b[1]<<16) | (b[2]<<8) | b[3];
if (lat & 0x80000000) lat -= 0x100000000;
var lon = (b[4]<<24) | (b[5]<<16) | (b[6]<<8) | b[7];
if (lon & 0x80000000) lon -= 0x100000000;
data.latitude = lat / 1e6;
data.longitude = lon / 1e6;
// Battery (10-bit, in volt)
data.battery = (((b[8] & 0x3F) << 8) | b[9]) / 1000;
data.alarm = (b[8] & 0x40) ? true : false;
// Optional: roll / pitch / hdop / altitude (only when GPS valid & full payload)
if (b.length >= 11) {
data.motion = ["Disable","Move","Collide","User"][(b[10] >> 6) & 0x03];
}
if (b.length >= 19) {
var roll = (b[11]<<8) | b[12]; if (roll & 0x8000) roll -= 0x10000;
var pitch = (b[13]<<8) | b[14]; if (pitch & 0x8000) pitch -= 0x10000;
var alt = (b[17]<<8) | b[18]; if (alt & 0x8000) alt -= 0x10000;
data.roll = roll / 100;
data.pitch = pitch / 100;
data.altitude = alt;
data.hdop = ((b[15]<<8) | b[16]) / 100;
}
return { data: data };
}
Dragino LHT65 (Temperatur & Luftfeuchte)
Klassischer Indoor/Outdoor-Sensor mit internem SHT-Sensor plus optionalem externen Anschluss. Liefert TempC_SHT und Hum_SHT — wird vom Server automatisch erkannt.// Dragino LHT65 — Temperatur + Feuchte (intern) plus externer Sensor.
// Standard-FPort: 2. 11 Byte Payload.
function decodeUplink(input) {
var b = input.bytes;
if (b.length < 7) return { data: {} };
// Battery (14-bit) + status (2-bit)
var batValue = ((b[0] & 0x3F) << 8) | b[1];
var batV = batValue / 1000;
var batStatus = (b[0] >> 6) & 0x03;
var batLabel = ["Ultra Low","Low","OK","Good"][batStatus];
// Internal SHT temperature (int16 / 100)
var t = (b[2] << 8) | b[3];
if (t & 0x8000) t -= 0x10000;
var TempC_SHT = t / 100;
// Internal humidity (uint16 / 10)
var Hum_SHT = (((b[4] << 8) | b[5])) / 10;
// External sensor type
var extId = b[6] & 0x7F;
var extLabel = {
1: "Temperature Sensor", 4: "Interrupt Sensor", 5: "ADC + Interrupt",
6: "Temp DS18B20 + Counter", 7: "Counter", 8: "Liquid Level",
9: "Temperature + Counter"
}[extId] || "Unknown";
var data = {
BatV: batV,
Bat_status: batStatus,
Bat_label: batLabel,
TempC_SHT: TempC_SHT,
Hum_SHT: Hum_SHT,
Ext: extId,
Ext_sensor: extLabel,
};
// External temperature (DS18B20) if ext = 1 or 6 or 9
if (extId === 1 || extId === 6 || extId === 9) {
var ext = (b[7] << 8) | b[8];
if (ext & 0x8000) ext -= 0x10000;
var TempC_DS = ext / 100;
if (TempC_DS !== 327.67) data.TempC_DS = TempC_DS; // 327.67 = "no sensor"
}
// Canonical aliases — so the Coverage-Map kann nach Temperatur /
// Luftfeuchte färben ohne weitere Konfiguration.
data.temperature = TempC_SHT;
data.humidity = Hum_SHT;
data.battery = batV;
return { data: data };
}
Wenn dein bestehender Formatter schon TempC_SHT und Hum_SHT ausgibt, brauchst du nichts zu ändern — der Server erkennt diese Keys automatisch und färbt die Karte entsprechend.
RAK7200 / RAK7201 (WisTrio Tracker)
RAK Wireless Tracker mit GPS, Beschleunigungs- & Magnetfeldsensor. Sendet Cayenne-LPP (siehe unten) — du kannst auch den Cayenne-Decoder nutzen.// Vereinfachter Decoder für RAK7200 GPS-Frames (FPort 8)
function decodeUplink(input) {
var b = input.bytes;
if (input.fPort !== 8 || b.length < 11) return { data: {} };
var data = {};
data.gps_valid = (b[0] === 0x09);
var lat = (b[1]<<24) | (b[2]<<16) | (b[3]<<8) | b[4];
if (lat & 0x80000000) lat -= 0x100000000;
var lon = (b[5]<<24) | (b[6]<<16) | (b[7]<<8) | b[8];
if (lon & 0x80000000) lon -= 0x100000000;
data.latitude = lat / 1e4 / 100; // RAK uses 0.0001 deg per LSB
data.longitude = lon / 1e4 / 100;
// Speed (km/h) and direction (deg)
if (b.length >= 13) {
data.speed = (b[9]<<8 | b[10]) / 100;
data.heading = (b[11]<<8 | b[12]) / 100;
}
return { data: data };
}
Seeed SenseCAP T1000 (Asset-Tracker)
Kompakter Multi-Mode-Tracker (GPS + WiFi + BLE). Payload ist TLV-basiert. Funktioniert für T1000-A / T1000-B / T1000-C / T1000-E.// Seeed SenseCAP T1000 — vereinfachter Decoder.
// Extrahiert GPS-Position, Batterie, Temperatur und Motion-Status.
// Für vollständige Sensor-Decoder siehe
// https://github.com/Seeed-Solution/SenseCAP-Decoder
function decodeUplink(input) {
var b = input.bytes;
var data = {};
if (b.length < 1) return { data: data };
// Frame-Header: erstes Byte enthält Event-Mask & Power-Type
var header = b[0];
data.battery_event = !!(header & 0x80);
data.motion = !!(header & 0x40);
// Ab Byte 1: aneinandergereihte TLV-Blöcke [type(1)][value(varlen)]
var i = 1;
function s16(o) { var v=(b[o]<<8)|b[o+1]; return v&0x8000 ? v-0x10000 : v; }
function s32(o) {
var v = (b[o]<<24)|(b[o+1]<<16)|(b[o+2]<<8)|b[o+3];
return v >> 0; // signed in JS
}
while (i < b.length) {
var t = b[i++];
switch (t) {
case 0x01: { // GPS: lat(4) + lon(4) + acc(1)
if (i + 9 > b.length) return { data: data };
data.latitude = s32(i) / 1e7;
data.longitude = s32(i + 4) / 1e7;
data.accuracy = b[i + 8];
i += 9; break;
}
case 0x02: { // WiFi MAC list (3-4 BSSIDs à 6+1 Byte) — skip
var count = b[i++];
i += count * 7;
break;
}
case 0x03: { // BLE MAC list
var n = b[i++];
i += n * 7;
break;
}
case 0x04: { // Battery in %
data.battery = b[i++]; break;
}
case 0x05: { // Temperature × 10 (signed int16)
data.temperature = s16(i) / 10; i += 2; break;
}
case 0x06: { // Humidity × 10 (uint16)
data.humidity = ((b[i]<<8) | b[i+1]) / 10; i += 2; break;
}
case 0x07: { // Light (uint16)
data.light = (b[i]<<8) | b[i+1]; i += 2; break;
}
case 0x08: { // Air pressure / 10
data.pressure = ((b[i]<<8) | b[i+1]) / 10; i += 2; break;
}
default: return { data: data, warnings: ["unknown TLV 0x" + t.toString(16) + " at " + (i-1)] };
}
}
return { data: data };
}
Tracker im SenseCAP-App auf Plain LoRaWAN-Modus umstellen (nicht "SenseCAP Cloud"), sonst gehen die Uplinks nicht in deine TTS-App.
RAK7200 v2 (WisTrio Tracker, neue Firmware)
Die neue Firmware sendet ein erweitertes Frame mit GPS, Beschleunigung, Temperatur und Batterie. Standard-FPort: 8.// RAK7200 v2 — neuer Frame mit GPS + Temp + Bat (FPort 8).
function decodeUplink(input) {
var b = input.bytes;
if (input.fPort !== 8 || b.length < 11) return { data: {} };
var data = { gps_valid: (b[0] === 0x09) };
// Lat / Lon — int32, /1e7 (neue Firmware)
var lat = (b[1]<<24) | (b[2]<<16) | (b[3]<<8) | b[4];
if (lat & 0x80000000) lat -= 0x100000000;
var lon = (b[5]<<24) | (b[6]<<16) | (b[7]<<8) | b[8];
if (lon & 0x80000000) lon -= 0x100000000;
data.latitude = lat / 1e7;
data.longitude = lon / 1e7;
// Battery (uint16 mV)
if (b.length >= 11) data.battery = ((b[9]<<8) | b[10]) / 1000;
// Optional: temperature (int16 / 10) + accelerometer
if (b.length >= 13) {
var t = (b[11]<<8) | b[12];
if (t & 0x8000) t -= 0x10000;
data.temperature = t / 10;
}
return { data: data };
}
Browan TBHV110 (Healthy Home Sensor)
Indoor-Sensor mit Temperatur, Luftfeuchte, VOC, Licht und Bewegungs-Counter. Sendet ohne GPS — Position kommt von den empfangenden Gateways.// Browan TBHV110 (Healthy Home Sensor).
// Payload: 10 byte fix. FPort: 103.
function decodeUplink(input) {
var b = input.bytes;
if (b.length < 10) return { data: {} };
// Byte 0: status — Bit 0..3 base-ID, Bit 4..7 battery (×0.1V + 1.8)
var batRaw = (b[0] >> 4) & 0x0F;
var batV = 1.8 + batRaw * 0.1;
// Byte 1..2: temperature, signed int16 / 10
var t = (b[1]<<8) | b[2];
if (t & 0x8000) t -= 0x10000;
// Byte 3: humidity 0..100 %
var hum = b[3];
// Byte 4..5: VOC index (uint16)
var voc = (b[4]<<8) | b[5];
// Byte 6..7: IAQ index (uint16)
var iaq = (b[6]<<8) | b[7];
// Byte 8..9: light / brightness lux (uint16)
var light = (b[8]<<8) | b[9];
return {
data: {
battery: batV,
temperature: t / 10,
humidity: hum,
voc: voc,
iaq: iaq,
light: light,
},
};
}
Adeunis Field Test Device (FTD)
Klassisches Test-Tool für Coverage-Messungen. Sendet Status, Temperatur und GPS-Position (BCD-codiert). FPort 1 oder 2.// Adeunis FTD (Field Test Device).
// Status byte am Anfang sagt, welche Felder folgen.
function decodeUplink(input) {
var b = input.bytes;
if (b.length < 1) return { data: {} };
var status = b[0];
var i = 1;
var data = {
has_temp: !!(status & 0x80),
has_gps: !!(status & 0x40),
trigger_acc: !!(status & 0x20),
trigger_push: !!(status & 0x10),
has_uplink_cnt: !!(status & 0x08),
has_downlink_cnt: !!(status & 0x04),
has_battery: !!(status & 0x02),
has_rssi_snr: !!(status & 0x01),
};
if (data.has_temp && i < b.length) {
var t = b[i++];
if (t & 0x80) t -= 0x100;
data.temperature = t;
}
if (data.has_gps && i + 8 <= b.length) {
// Lat / Lon in BCD: ddmmmmmmh (degrees, decimal-minutes ×1e4, hemisphere)
function bcd(o, len) {
var s = "";
for (var k = 0; k < len; k++) {
s += ((b[o + k] >> 4) & 0x0F).toString() + (b[o + k] & 0x0F).toString();
}
return s;
}
var latStr = bcd(i, 4);
var lonStr = bcd(i + 4, 4);
// Format: DDMMmmmmH — degrees + minutes.fraction + N/S, E/W hemisphere
var latDeg = parseInt(latStr.substr(0, 2), 10);
var latMin = parseFloat(latStr.substr(2, 2) + "." + latStr.substr(4, 4));
var latHem = latStr.substr(8, 1) === "0" ? 1 : -1; // 0=N, 1=S
var lonDeg = parseInt(lonStr.substr(0, 3), 10);
var lonMin = parseFloat(lonStr.substr(3, 2) + "." + lonStr.substr(5, 3));
var lonHem = lonStr.substr(8, 1) === "0" ? 1 : -1; // 0=E, 1=W
data.latitude = latHem * (latDeg + latMin / 60);
data.longitude = lonHem * (lonDeg + lonMin / 60);
i += 8;
if (i < b.length) data.gps_quality = b[i++];
}
if (data.has_uplink_cnt && i < b.length) data.uplink_count = b[i++];
if (data.has_downlink_cnt && i < b.length) data.downlink_count = b[i++];
if (data.has_battery && i + 2 <= b.length) {
data.battery = ((b[i]<<8) | b[i + 1]) / 1000;
i += 2;
}
if (data.has_rssi_snr && i + 2 <= b.length) {
data.rssi = -b[i]; data.snr = b[i + 1];
if (data.snr & 0x80) data.snr -= 0x100;
i += 2;
}
return { data: data };
}
Adeunis FTD funktioniert auch ohne GPS-Fix (nur Status-Frames). Diese landen nicht auf der Karte — das ist normal.
ELSYS (ERS / EMS / ELT generisch)
ELSYS-Sensoren nutzen TLV-Format. Decoder erkennt Temperatur, Luftfeuchte, Licht, Bewegung, CO₂, Batterie und GPS.// ELSYS generic — TLV-Decoder für ERS, ELT, EMS Familie.
function decodeUplink(input) {
var b = input.bytes, i = 0, data = {};
function u16(o) { return (b[o]<<8) | b[o + 1]; }
function s16(o) { var v = u16(o); return v & 0x8000 ? v - 0x10000 : v; }
function u32(o) {
return (b[o]<<24 >>> 0) + (b[o + 1]<<16) + (b[o + 2]<<8) + b[o + 3];
}
while (i < b.length) {
var t = b[i++];
switch (t) {
case 0x01: data.temperature = s16(i) / 10; i += 2; break; // °C
case 0x02: data.humidity = b[i++]; break; // %
case 0x03: { // accelerometer: x, y, z (int8)
var x = b[i] & 0x80 ? b[i] - 256 : b[i];
var y = b[i + 1] & 0x80 ? b[i + 1] - 256 : b[i + 1];
var z = b[i + 2] & 0x80 ? b[i + 2] - 256 : b[i + 2];
data.accel = { x: x, y: y, z: z }; i += 3; break;
}
case 0x04: data.light = u16(i); i += 2; break; // lux
case 0x05: data.motion = b[i++]; break; // PIR count
case 0x06: data.co2 = u16(i); i += 2; break; // ppm
case 0x07: data.battery = u16(i) / 1000; i += 2; break; // V
case 0x08: data.analog1 = u16(i); i += 2; break;
case 0x09: { // GPS: lat int32 / 10000, lon int32 / 10000
var lat = u32(i); if (lat & 0x80000000) lat -= 0x100000000;
var lon = u32(i + 4); if (lon & 0x80000000) lon -= 0x100000000;
data.latitude = lat / 10000;
data.longitude = lon / 10000;
i += 8; break;
}
case 0x0A: data.pulse1 = u16(i); i += 2; break;
case 0x0B: data.pulse1_abs = u32(i); i += 4; break;
case 0x0E: data.temp_ext = s16(i) / 10; i += 2; break;
case 0x0F: data.occupancy = b[i++]; break;
case 0x14: data.pressure = u32(i) / 1000; i += 4; break; // hPa
default:
return {
data: data,
warnings: ["unknown ELSYS type 0x" + t.toString(16)],
};
}
}
return { data: data };
}
Cayenne LPP (Standard-Format)
Generisches Low-Power-Payload-Format, das viele Tracker nutzen (RAK, Adeunis, ELSYS, …). Erkennt automatisch GPS, Temperatur, Spannung, etc.function decodeUplink(input) {
var b = input.bytes;
var data = {};
var i = 0;
while (i + 2 <= b.length) {
var ch = b[i++];
var t = b[i++];
switch (t) {
case 0x00: data["digital_in_" + ch] = b[i++]; break;
case 0x01: data["digital_out_" + ch] = b[i++]; break;
case 0x02: { // analog input
var v = (b[i]<<8) | b[i+1]; if (v & 0x8000) v -= 0x10000;
data["analog_in_" + ch] = v / 100; i += 2; break;
}
case 0x03: { // analog output
var v = (b[i]<<8) | b[i+1]; if (v & 0x8000) v -= 0x10000;
data["analog_out_" + ch] = v / 100; i += 2; break;
}
case 0x65: data["luminosity_" + ch] = (b[i]<<8) | b[i+1]; i += 2; break;
case 0x66: data["presence_" + ch] = b[i++]; break;
case 0x67: { // temperature
var v = (b[i]<<8) | b[i+1]; if (v & 0x8000) v -= 0x10000;
data["temperature_" + ch] = v / 10; i += 2; break;
}
case 0x68: data["humidity_" + ch] = b[i++] / 2; break;
case 0x71: i += 6; break; // accelerometer
case 0x73: data["barometer_" + ch] = ((b[i]<<8) | b[i+1]) / 10; i += 2; break;
case 0x74: data["voltage_" + ch] = ((b[i]<<8) | b[i+1]) / 100; i += 2; break;
case 0x86: i += 6; break; // gyrometer
case 0x88: { // gps
var lat = (b[i]<<16) | (b[i+1]<<8) | b[i+2]; if (lat & 0x800000) lat -= 0x1000000;
var lon = (b[i+3]<<16) | (b[i+4]<<8) | b[i+5]; if (lon & 0x800000) lon -= 0x1000000;
var alt = (b[i+6]<<16) | (b[i+7]<<8) | b[i+8]; if (alt & 0x800000) alt -= 0x1000000;
data.latitude = lat / 10000;
data.longitude = lon / 10000;
data.altitude = alt / 100;
i += 9; break;
}
default: return { data: data, warnings: ["unknown type 0x" + t.toString(16) + " at " + (i-1)] };
}
}
return { data: data };
}
So baust du den Decoder in TTS ein
- TTS Console öffnen → Applications → deine App
- End Devices → dein Gerät → Tab Payload formatters
- Bei Uplink: Formatter type → Custom JavaScript formatter
- Den Code von oben einfügen → Save changes
- Beim nächsten Uplink prüfen: Live data-Tab muss "latitude" und "longitude" im decoded payload anzeigen
- Dann landet der Uplink automatisch auf der Coverage-Karte
Decoder für andere Geräte findest du auch im offiziellen lorawan-devices Repository .