Payload sparen im TTN

ttn logo

Wir sollen Geld sparen und jetzt auch noch Payload? Ja, bitte, denn durch weniger Payload, erzeugt ihr auch weniger Airtime. Damit ist bleibt mehr Platz für alle über. Andererseits könnt ich aber auch mehr Nachrichten übermitteln.

Aufgrund der Verschlüsselung im LoRaWAN, werden mindestens immer zwei Byte übertragen. Ausgehend von einem Overhead von 13 Byte, macht es also keinen unterschied ob ihr eine netto Payload von zwei oder drei Byte verschickt, da die Airtime für die gesamte Payload von 15 oder 16 Byte gleich bleibt.

Es gibt zu dem Thema schon mehrere Anleitung wie diese, aber es kann ja nicht genug geben.

Wir senden nur noch Bytes als Payload

Um Payload zu sparen, senden wir nur noch Bytes, dieses spart Platz und dank dem Payload Decoder im TTN, bekommen wir unsere Daten sogar sauber aus dem TTN Backend.

Ein Byte kann 256 Werte annehmen, von 0 bis 255. Wir werden jetzt mal 100 in ein Byte wandeln un versenden. Der Code in der Arduino IDE würe in etwa so aussehen. Die Integer Variable meinwert, wird in das Byte measurement umgewandelt.

Encode:

uint8_t measurement[1];

void do_send(osjob_t* j){
int meinwert = 100;
measurement[0] = meinwert;

if (LMIC.opmode & OP_TXRXPEND) {
Serial.println(F("OP_TXRXPEND, not sending"));
} else {

LMIC_setTxData2(1, (uint8_t*) measurement, sizeof(measurement), 0);
Serial.println(F("Packet queued"));
}

}

Über den Payload Decoder in TTN, kann dieses wieder in unsere 100 umgewandelt werden. In dem Tab Data eurer Applikation würdet ihr dann measurement.wert = 100 sehen.

Decode:

function Decoder(b, port) {

var meinwert = (b[0]);

return {
measurement: {
wert: meinwert
},
};
}

In Node-RED würdet ihr mittels ttn uplink Node mittels folgender Funktion an eure Daten kommen.

Payload:

msg.payload_fields.measurement.wert;

Das war jetzt einfach, aber leider beschränken sich unsere Messwerte nicht auf den Bereich zwischen 0 und 255, also geht es weiter.

Feste Basis

Messungen des Luftdrucks sind eine gute Beispiel hierfür. An meinem Wohnort schwankt der Luftdruck maximal zwischen 950 und 1050 hPa. Die Werte liegen zwar über meinem Byte-Maximum von 255, schwanken aber nur um 100. Ich nehme also die 950 als minimalen Wert an und beziehe meine Messwerte darauf. Ich werde also von jeder Messung 950 abziehen und später wieder im TTN Decoder hinzurechnen. Somit habe ich wieder Werte zwischen 0 und 100, welche ich als ein Byte verschicken kann.

Encode:

uint8_t measurement[1];

void do_send(osjob_t* j){
int luftdruck = druck;
measurement[0] = luftdruck - 950;

if (LMIC.opmode & OP_TXRXPEND) {
Serial.println(F("OP_TXRXPEND, not sending"));
} else {

LMIC_setTxData2(1, (uint8_t*) measurement, sizeof(measurement), 0);
Serial.println(F("Packet queued"));
}

}

Über den Payload Decoder in TTN, fügen wir wieder die fehlenden 950 hinzu.

Decode:

function Decoder(b, port) {

var meinwert = b[0] + 950;

return {
measurement: {
luftdruck: meinwert
},
};
}

In Node-RED würdet ihr mittels ttn uplink Node mittels folgender Funktion an eure Daten kommen.

Payload:

msg.payload_fields.measurement.luftdruck;

Ok, das ist auch nicht schwer. Jetzt haben wir aber auch nicht ganze Zahlen, so müssen wir also etwas tiefer in die mathematische Trickkiste greifen.

Runden

Als reales Beispiel nehmen wir hier mal die Luftfeuchtigkeit. Viele Fühler liefern Werte für die Luftfeuchtigkeit mit einer Nachkommastelle, diese benötigen wir aber generell nicht, es reichen Werte zwischen 0 und 100. Somit reicht es einfach den Messwert zu runden.

Encode:

uint8_t measurement[1];

void do_send(osjob_t* j){
int hum = round(humidity);
measurement[0] = hum;

if (LMIC.opmode & OP_TXRXPEND) {
Serial.println(F("OP_TXRXPEND, not sending"));
} else {

LMIC_setTxData2(1, (uint8_t*) measurement, sizeof(measurement), 0);
Serial.println(F("Packet queued"));
}

}

Über den Payload Decoder bekommen wir wieder unseren Wert.

Decode:

function Decoder(b, port) {

var meinwert = b[0] ;

return {
measurement: {
hum: meinwert
},
};
}

In Node-RED würdet ihr mittels ttn uplink Node mittels folgender Funktion an eure Daten kommen.

Payload:

msg.payload_fields.measurement.hum;

Multiplizieren

Bei der Temperatur sieht es anders aus, hier interessieren die Messwerte mit einer Nachkommastelle schon. Hier multiplizieren wir, bei einer Nachkommastelle, mit 10, um eine ganze Zahl zu erhalten.

Encode:

uint8_t measurement[1];

void do_send(osjob_t* j){
int temp = temperatur * 10;
measurement[0] = temp;

if (LMIC.opmode & OP_TXRXPEND) {
Serial.println(F("OP_TXRXPEND, not sending"));
} else {

LMIC_setTxData2(1, (uint8_t*) measurement, sizeof(measurement), 0);
Serial.println(F("Packet queued"));
}

}

Über den Payload Decoder in TTN dividieren wir wieder durch 10.

Decode:

function Decoder(b, port) {

var meinwert = b[0]  / 10;

return {
measurement: {
temp: meinwert
},
};
}

In Node-RED würdet ihr mittels ttn uplink Node mittels folgender Funktion an eure Daten kommen.

Payload:

msg.payload_fields.measurement.temp;

Jetzt mit zwei Bytes (Word)

Ok, das vorangegangene Beispiel war für die Temperatur nicht ganz korrekt, jetzt machen wir es richtig, damit wir auch größere Werte übermitteln können. Dazu wird der Wert auf zwei Bytes aufgeteilt. Gleichzeitig multiplizieren wir noch mit 10 um eine ganze Zahl zu bekommen. So erhalten wir 65536 mögliche positive Werte. Sollten negative Werte vorkommen, sind es 32768.

Encode:

uint8_t measurement[2];

void do_send(osjob_t* j){
int temp = temperatur * 10;
measurement[0] = temp >> 8;
measurement[1] = temp & 0xFF;

if (LMIC.opmode & OP_TXRXPEND) {
Serial.println(F("OP_TXRXPEND, not sending"));
} else {

LMIC_setTxData2(1, (uint8_t*) measurement, sizeof(measurement), 0);
Serial.println(F("Packet queued"));
}

}

Über den Payload Decoder in TTN, bekommen wir wieder unsere Temperatur.

Decode:

function Decoder(bytes, port) {

var meinwert = ((bytes[0] << 8) | bytes[1]) / 10;

return {
measurement: {
temp: meinwert
},
};
}

In Node-RED würdet ihr mittels ttn uplink Node mittels folgender Funktion an eure Daten kommen.

Payload:

msg.payload_fields.measurement.temp;

Mehrere Werte übermittlen

Nun wollen wir aber Temperatur und Luftdruck zusammen zu übermitteln.

Encode:

uint8_t measurement[3];

void do_send(osjob_t* j){
int temp = temperatur * 10;
int hum = round(humidity);
measurement[0] = temp >> 8;
measurement[1] = temp & 0xFF;
measurement[2] = hum;

if (LMIC.opmode & OP_TXRXPEND) {
Serial.println(F("OP_TXRXPEND, not sending"));
} else {

LMIC_setTxData2(1, (uint8_t*) measurement, sizeof(measurement), 0);
Serial.println(F("Packet queued"));
}

}

Über den Payload Decoder in TTN, trennen wir die Werte wieder.

Decode:

function Decoder(bytes, port) {

var meinwert = ((bytes[0] << 8) | bytes[1]) / 10;
var meinwert2 = bytes[2];

return {
measurement: {
temp: meinwert,
hum : meinwert2
},
};
}

In Node-RED würdet ihr mittels ttn uplink Node mittels folgender Funktion an eure Daten kommen.

Payload:

msg.payload_fields.measurement.temp;
msg.payload_fields.measurement.hum;

Noch Größer

Ok, es geht natürlich noch mehr. Das beispiel hier stammt vom TTN Mapper, damit Koordinaten übermittelt werden können.

Encode:

uint8_t measurement[3];

void do_send(osjob_t* j){
int32_t lat = flat * 10000;
measurement[0] = lat;
measurement[1] = lat >> 8;
measurement[2] = lat >> 16;

if (LMIC.opmode & OP_TXRXPEND) {
Serial.println(F("OP_TXRXPEND, not sending"));
} else {

LMIC_setTxData2(1, (uint8_t*) measurement, sizeof(measurement), 0);
Serial.println(F("Packet queued"));
}

}

Über den Payload Decoder in TTN, bekommen wir wieder den ursprünglichen Wert.

Decode:

function Decoder(b, port) {

var meinwert = (b[0] | b[1]<<8 | b[2]<<16 | (b[2] & 0x80 ? 0xFF<<24 : 0)) / 10000;

return {
measurement: {
lat: meinwert
},
};
}

In Node-RED würdet ihr mittels ttn uplink Node mittels folgender Funktion an eure Daten kommen.

Payload:

msg.payload_fields.measurement.lat;

Damit sollten wir die meisten Fälle erschlagen haben und alles an Daten verschicken können, was wir so brauchen. Und auch hier gilt, viele Wege führen nach Rom.

Eine weitere Möglichkeit ist es das Cayenne LPP Format zu verwenden, dann seit ihr aber auch auf das Cayenne Dashboard angewiesen.

11 Gedanken zu „Payload sparen im TTN“

  1. Hallo Björn,
    ich beschäftige mich seit ein paar Wochen mit TTN und dein Blog ist sehr, sehr hilfreich.
    Vielen Dank!
    Eine Anmerkung hab ich:
    Mir ist aufgefallen, dass du bei dem „Word“ Beispiel sowie bei „Mehrere Werte übermitteln“
    im „Payload Decoder“ den Array measurement[] bei 1 anfängst zu zählen. Es sollte bei Null losgehen. Desweiteren übergibst du im Funktionsaufruf ein b und nutzt dann weiter bytes[].

    Antworten
    • Hi,

      wir Verpacken unsere Integer Temperaturwerte in zwei Byte. Mit temp & 0xFF generieren wir das erste Byte und mit >> 8 das zweite. Ein drittes Byte würden wir mit >> 16 generieren usw.
      Wenn man sich also unseren Wert als folge von 0 und 1 darstellt ist es so:
      [1 0 1 0 1 0 1 0] [0 1 0 1 0 1 0 1]
      Mit >> 8
      [1 0 1 0 1 0 1 0]

      Mit & 0xFF
      [0 1 0 1 0 1 0 1]

      Grüße,
      Björn

      Antworten
  2. >> n bedeutet „bitwise rightshift“ bit für bit nach um den Wert n nach rechts verschieben,
    << n das gleiche nach links
    & ist bitwise and (logisch und) – kennt man evtl aus der Schule Physik/Elektrotechnik 1&1= 1 alles andere ergibt 0
    | ist bitwise or (logisch oder) 0 | 0 =0 alles andere 1

    für den DHT22: habe ich einfach die 5 byte die der liefert an thingsboard geschickt. laut Datenblatt sind die ersten 2 Bytes high /low-byte der Luftfeuchte und die byte 3 u 4 für die temperatur (signed) dass bedeutet dass dem dritten Byte bei negativen Temperaturen 0x80 (1000 0000) zugefügt wobei wir durch & 0x7F (0111 1111 maskiert) wieder zum unsigned Wert des Highbytes gelangen und durch << 8 |lowbyte dann das lowbyte hinzufügen. Der Code sieht dann so aus:

    unction Decoder(bytes, port) {
    // Decode an uplink message from a buffer
    // (array) of bytes to an object of fields.
    var decoded = {};
    // temperature
    decoded.degreesC = ((bytes[2] & 0x7f ) << 8 | bytes[3])/10;
    // humidity
    decoded.humidity = (bytes[0] << 8 | bytes[1])/10;
    return decoded;
    }

    Antworten
  3. >> n bedeutet „bitwise rightshift“ bit für bit nach um den Wert n nach rechts verschieben,
    << n das gleiche nach links
    & ist bitwise and (logisch und) – kennt man evtl aus der Schule Physik/Elektrotechnik 1&1= 1 alles andere ergibt 0
    | ist bitwise or (logisch oder) 0 | 0 =0 alles andere 1

    für den DHT22: habe ich einfach die 5 byte die der liefert an thingsboard geschickt. laut Datenblatt sind die ersten 2 Bytes high /low-byte der Luftfeuchte und die byte 3 u 4 für die temperatur (signed) dass bedeutet dass dem dritten Byte bei negativen Temperaturen 0x80 (1000 0000) zugefügt wobei wir durch & 0x7F (0111 1111 maskiert) wieder zum unsigned Wert des Highbytes gelangen und durch << 8 |lowbyte dann das lowbyte hinzufügen. Der Code sieht dann so aus:

    function Decoder(bytes, port) {
    // Decode an uplink message from a buffer
    // (array) of bytes to an object of fields.
    var decoded = {};
    // temperature
    decoded.degreesC = ((bytes[2] & 0x7f ) << 8 | bytes[3])/10;
    // humidity
    decoded.humidity = (bytes[0] << 8 | bytes[1])/10;
    return decoded;
    }

    Antworten
  4. Hallo,
    leider zeigt er bei mir alles Falsch an:
    measurement[0] = temp >> 8;
    measurement[1] = temp & 0xFF;

    Also &amp Satt nur & usw. Liegt das an mir oder geht auch anderen so?

    Antworten
  5. Hallo,
    vielen Dank für den mal wieder SUPER BEITRAG.
    Dank dir hab ich endlich halbwegs kapiert.
    Allerdings habe ich ein Problem.
    Ich bekomme keine Negativ Werte übertragen.
    Ich habe 2 werte die auch ins Negative gehen können.
    Das sind Temperaturen bis -30°C und Leistung +1000 bis – 800mA.
    wie mache ich das ?
    Daran scheitere ich immer wieder.
    Danke

    Antworten
    • Moin,

      ich muss ja zugeben, ich hab es selber nicht ganz kapiert. D u kannst es mal damit versuchen:

      var temp = ((bytes[0] & 0x80 ? 0xFFFF<<16 : 0) | bytes[0]<<8 | bytes[1]) / 100; Damit klappt es bei mir jedenfalls mit der Temperatur. Grüße, Björn

      Antworten

Schreibe einen Kommentar