# Payload Decoders

LoRa devices send their readings as bytes instead of a full JSON object in order to save bandwidth. For Datacake to be able to process and store the sent measurements, you need a piece of JavaScript code called a Payload Decoder.

A Payload Decoder takes a buffer (list of bytes) as well as the port of the message and converts them to a normalized object Datacake can understand.

#### Looking for Downlinks?

If you want to send data to your devices, you can define Downlinks (or Payload Encoders). You'll find all the Informations about this here:

{% content-ref url="downlinks" %}
[downlinks](downlinks)
{% endcontent-ref %}

## How to access the Payload Decoder section?

Use the tab bar of the Device View to navigate to the configuration of your Device and scroll down a little until you reach the box "LoRaWAN".&#x20;

![](https://2221155863-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LNA8_b8wmabHgJ0QXoJ%2F-MMF6gTLjswGuPvlOwz4%2F-MMF7PzlXcrq46LtSUEW%2FBildschirmfoto%202020-11-16%20um%2009.16.20.png?alt=media\&token=2005a425-5970-48b4-af33-b2053583d84b)

There you will find a "Show" button in the "Payload Decoder" area at the end of the panel. If you click on it, the Payload Decoder section will be displayed.

![](https://2221155863-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LNA8_b8wmabHgJ0QXoJ%2F-MMF6gTLjswGuPvlOwz4%2F-MMF7hAvrnn-OuEDywPt%2F2020-11-16%2009.12.21.gif?alt=media\&token=d81c7680-a809-49b2-be32-67e598ff59ff)

## How to develop a Payload Decoder on Datacake?

Imagine a LoRa sensor sending a single measurement, which is a float indicating a temperature.

To encode this measurement, one could use the following code:

```c
float temperature = 23.5;
byte payload[1];
payload[0] = round(temperature * 100);
```

In this example, we are first convert the float to an integer without losing precision by multiplying it with 100. To decode it back to a float, we simply divide it by 100 again. The following code is an example, how a Payload Decoder can look on the Datacake platform.

```javascript
function Decoder(payload, port) {
    if(port === 1) {
        return [
            {
                field: "TEMPERATURE",
                value: payload[0] / 100
            }
        ];
    }
}
```

As you can see, a Payload Decoder must contain a function `Decoder` which takes the `payload` and `port` as arguments and returns a list of measurements in the `{field: str, value: any, timestamp?: number}` format.

### Recording multiple measurements

A Payload Decoder can return a list of an arbitrary number of measurements.

```javascript
function Decoder(payload, port) {
    return [
        {
            field: "FIELD1",
            value: true
        },
        {
            field: "FIELD2",
            value: false
        }
    ];
}
```

### Recording with remote Timestamp

When Inserting Data into Datacake you can provide a Timestamp. Usually Datacake auto-creates a timestamp upon writing into the Database. This does happen in serialized write requests so there can be a few seconds in delay in between writing operation that normally are encapsulated within one single message.&#x20;

By providing a timestamp key option in the dictionary of fields, the database uses a foreign timestamp.

```javascript
function Decoder(payload, port) {

    // example for TTSv3 The Things Stack v3 Timestamp
    var ts = rawPayload.uplink_message.settings.timestamp;

    return [
        {
            field: "FIELD1",
            value: true,
            timestamp: ts
        },
        {
            field: "FIELD2",
            value: false,
            timestamp: ts
        }
    ];
}
```

### Recording historic data

Some LoRa devices send an aggregated list of measurements for e.g. the last hour. Each measurement can take an optional `timestamp` parameter which is a UNIX timestamp (seconds since Jan 01 1970 (UTC)).

```javascript
function Decoder(payload, port) {
    return [
        {
            field: "FIELD1",
            value: true,
            timestamp: 1589815300
        },
        {
            field: "FIELD1",
            value: false,
            timestamp: 1589811700
        }
    ];
}
```

## Converting TTN Payload Decoders

You can use JS-Style Payload Decoders that work in your TheThingsNetwork Applications on Datacake. However, they must be adjusted so that the output of these decoders meets the expectations of Datacake.

Mostly a TTN Decoder Looks like the following:

```javascript
/**
 * Ursalink Sensor Payload Decoder
 *
 * definition [channel-id] [channel-type] [channel-data]
 *
 * 01: battery      -> 0x01 0x75 [1byte]  Unit: %
 * 03: temperature  -> 0x03 0x67 [2bytes] Unit: °C
 * ------------------------------------------ EM500-PT100
 */
function Decoder(bytes, port) {
    var decoded = {};

    for (var i = 0; i < bytes.length;) {
        var channel_id = bytes[i++];
        var channel_type = bytes[i++];
        // BATTERY
        if (channel_id === 0x01 && channel_type === 0x75) {
            decoded.battery = bytes[i];
            i += 1;
        }
        // TEMPERATURE
        else if (channel_id === 0x03 && channel_type === 0x67) {
            decoded.temperature = readInt16LE(bytes.slice(i, i + 2)) / 10;
            i += 2;
        } else {
            break;
        }
    }
    return decoded;
}

/* ******************************************
 * bytes to number
 ********************************************/
function readUInt16LE(bytes) {
    var value = (bytes[1] << 8) + bytes[0];
    return value & 0xffff;
}
function readInt16LE(bytes) {
    var ref = readUInt16LE(bytes);
    return ref > 0x7fff ? ref - 0x10000 : ref;
}
```

This Decoder returns the payload structured as follows:&#x20;

```javascript
payload = {
    "temperature": 21.3,
    "battery": 55
};

return payload;
```

Datacake expects this to be:

```javascript
payload = [
    { "field": "TEMPERATURE", "value": 21.3 },
    { "field": "BATTERY", "value": 55 },    
];

return payload;
```

So instead of having a dictionary with a key:value-pair for every field or measurement value (like temperature, battery), Datacake expects an Array to be returned with a Dictionary for each value you would like to forward into Datacake.&#x20;

Each Dictionary inside that Array consists of at least 2 elements:

1. The Field-Identifier (`"field": "TEMPERATURE"`)
2. The actual value of that field (`"value": 21.3`)

Also as Field-Identifiers in Datacake are always in capital letters you have to turn `temperature` into `TEMPERATURE` in order for the payload decoder to match the returned values to the database fields of your device.

Now let's convert this to Datacake.&#x20;

```javascript
/**
 * Ursalink Sensor Payload Decoder
 *
 * definition [channel-id] [channel-type] [channel-data]
 *
 * 01: battery      -> 0x01 0x75 [1byte]  Unit: %
 * 03: temperature  -> 0x03 0x67 [2bytes] Unit: °C
 * ------------------------------------------ EM500-PT100
 */
function Decoder(bytes, port) {
    
    // decoded needs to be an array so change this:
    // var decoded = {};
    // to:
    var decoded = [];

    for (var i = 0; i < bytes.length;) {
        var channel_id = bytes[i++];
        var channel_type = bytes[i++];
        // BATTERY
        if (channel_id === 0x01 && channel_type === 0x75) {
            decoded.push({
                "field": "BATTERY",
                "value": bytes[i]
            });
            i += 1;
        }
        // TEMPERATURE
        else if (channel_id === 0x03 && channel_type === 0x67) {
            decoded.push({
                "field": "TEMPERATURE",
                "value": readInt16LE(bytes.slice(i, i + 2)) / 10;
            });
            i += 2;
        } else {
            break;
        }
    }
    return decoded;
}

/* ******************************************
 * bytes to number
 ********************************************/
function readUInt16LE(bytes) {
    var value = (bytes[1] << 8) + bytes[0];
    return value & 0xffff;
}
function readInt16LE(bytes) {
    var ref = readUInt16LE(bytes);
    return ref > 0x7fff ? ref - 0x10000 : ref;
}
```

## Tips and tricks

### Execution time

A Payload Decoder must not take longer than 100ms (milliseconds) to return, otherwise its execution will be aborted and you will find a note on the Debug tab of the device.

### Access to base64-encoded payload

The base64-encoded payload is available via the global `b64payload`-variable.

### Converting the buffer to a string

You should avoid sending strings over LoRa, since text uses a lot of bytes. You can still decode the buffer to string using:

```javascript
String.fromCharCode.apply(null, payload);
```
