Decoding a LPP Packet from TTN in IoTFactory.eu

Hello guys
I´m testing the platftom iotfactory.eu and have a lot of sensors based on sodaq explorer boards reporting thru TTN and MQTT to iotfactory.
Now I need to program a javascript decoder for decode an Cayenne LPP payload.

Has someone programmed this converter before?

The example code is very basic, and really i do not figure how to do this.

JavaScript function

You can create your own device type to decode your payloads using Javascript.

The function receives the variable “payload” which is the data received on the platform.
The function you’ll write must return an array of measurements. Each measurement must be an object with the following properties:

  • type : string that identify the measurement. It is required. You can find a list of specific type format that is used in the “Documents” tab.
  • value : the decoded value.
  • unit : string. It is optional. You can find a list of specific type format that is used in the “Documents” tab.
  • id : string. It is optional. You can use it to identify each measurements in case you have multiple measurements of the same type.

It is recommended that you order your measurements by priority (from more to less important).
After setting up your device type, you just need to go to “Devices” module, select your device and change its type.

function ( payload ){
/* Process payload and extract measurements in format (type, value, unit) */
	
	return [
	    {
	        "type" : "temperature",
	        "value" : 22.5,
	        "unit" : "°C"
	    },
	    {
	        "type" : "humidity",
	        "value" : 10,
	        "unit" : "%"
	    },
	    {
	        "type" : "wind_speed",
	        "value" : 30,
	        "unit" : "kmh"
	    }
];

Any help will be welcomed.

Thanks in advance.

I am seeing the post Cayenne LPP Decoder in NodeJS but I don’t have an idea how to adapt it to my need.

Perhaps this code will help you? Its a Cayenee LPP decoded in Javascript, the first byte is the identifier, the Channel ID which i found useful when grouping sensor output to a channel.

2 Likes

Thanks Jeryl, i will try to test this coded, seems to be near to the solution that i need.
Thanks you very much.

1 Like

Here is the code ported to the IoTFactory Platform.
Under Device Types->Config you require a JS code to parse the payload from an MQTT Server to the internal IoTFactory Format (JSON).

THanks to Stephane Demingongo from IoTFactory for such adaptation, here is the code working:

/**
 * Byte stream to fixed-point decimal word.
 *
 * @param stream: array of bytes.
 * @return: word of the bytes in big-endian format.
 */
function arrayToDecimal(stream, is_signed = false, decimal_point = 0) {
  var value = 0;
  for (var i = 0; i < stream.length; i++) {
    if (stream[i] > 0xff) throw "Byte value overflow!";
    value = (value << 8) | stream[i];
  }

  if (is_signed) {
    edge = 1 << (stream.length * 8); // 0x1000..
    max = (edge - 1) >> 1; // 0x0FFF.. >> 1
    value = value > max ? value - edge : value;
  }

  if (decimal_point) {
    value /= Math.pow(10, decimal_point);
  }

  return value;
}

/**
 * Cayenne Low-power Payload Decoder
 *
 * @param {string} payload hexadecimal.
 * @return: JSON
 */
function decode(payload) {
  payload = String.prototype.match.apply(payload, [/.{1,2}/g]);

  payload = payload.map(function(hex) {
    return parseInt(hex, 16);
  });

  /**
   * @reference https://github.com/myDevicesIoT/cayenne-docs/blob/master/docs/LORA.md
   *
   * Type                 IPSO    LPP     Hex     Data Size   Data Resolution per bit
   *  Digital Input       3200    0       0       1           1
   *  Digital Output      3201    1       1       1           1
   *  Analog Input        3202    2       2       2           0.01 Signed
   *  Analog Output       3203    3       3       2           0.01 Signed
   *  Illuminance Sensor  3301    101     65      2           1 Lux Unsigned MSB
   *  Presence Sensor     3302    102     66      1           1
   *  Temperature Sensor  3303    103     67      2           0.1 °C Signed MSB
   *  Humidity Sensor     3304    104     68      1           0.5 % Unsigned
   *  Accelerometer       3313    113     71      6           0.001 G Signed MSB per axis
   *  Barometer           3315    115     73      2           0.1 hPa Unsigned MSB
   *  Gyrometer           3334    134     86      6           0.01 °/s Signed MSB per axis
   *  GPS Location        3336    136     88      9           Latitude  : 0.0001 ° Signed MSB
   *                                                          Longitude : 0.0001 ° Signed MSB
   *                                                          Altitude  : 0.01 meter Signed MSB
   */

  var sensor_types = {
    0: { size: 1, name: "Digital Input", signed: false, decimal_point: 0 },
    1: { size: 1, name: "Digital Output", signed: false, decimal_point: 0 },
    2: { size: 2, name: "Analog Input", signed: true, decimal_point: 2 },
    3: { size: 2, name: "Analog Output", signed: true, decimal_point: 2 },
    101: {
      size: 2,
      name: "Illuminance Sensor",
      signed: false,
      decimal_point: 0
    },
    102: { size: 1, name: "Presence Sensor", signed: false, decimal_point: 0 },
    103: {
      size: 2,
      name: "temperature",
      signed: true,
      decimal_point: 1,
      unit: "°C"
    },
    104: { size: 1, name: "humidity", signed: false, decimal_point: 1, unit: '%' },
    113: { size: 6, name: "accelerometer", signed: true, decimal_point: 3 },
    115: { size: 2, name: "barometer", signed: false, decimal_point: 1 },
    134: { size: 6, name: "gyrometer", signed: true, decimal_point: 2 },
    136: {
      size: 9,
      name: "position",
      id: "gps",
      signed: false,
      decimal_point: [4, 4, 2]
    }
  };

  var sensors = [];
  var i = 0;
  while (i < payload.length) {
    s_no = payload[i++];
    s_type = payload[i++];
    if (typeof sensor_types[s_type] == "undefined") {
      console.error("Sensor type error!: {}", s_type);
      break;
    }
    s_size = sensor_types[s_type].size;
    s_name = sensor_types[s_type].name;
    s_id = sensor_types[s_type].id;
    s_unit = sensor_types[s_type].unit;

    switch (s_type) {
      case 0: // Digital Input
      case 1: // Digital Output
      case 2: // Analog Input
      case 3: // Analog Output
      case 101: // Illuminance Sensor
      case 102: // Presence Sensor
      case 103: // Temperature Sensor
      case 104: // Humidity Sensor
      case 113: // Accelerometer
      case 115: // Barometer
      case 134: // Gyrometer
        s_value = arrayToDecimal(
          payload.slice(i, i + s_size),
          (is_signed = sensor_types[s_type].signed),
          (decimal_point = sensor_types[s_type].decimal_point)
        );
        break;
      case 136: // GPS Location
        s_value = {
          latitude: arrayToDecimal(
            payload.slice(i + 0, i + 3),
            (is_signed = sensor_types[s_type].signed),
            (decimal_point = sensor_types[s_type].decimal_point[0])
          ),
          longitude: arrayToDecimal(
            payload.slice(i + 3, i + 6),
            (is_signed = sensor_types[s_type].signed),
            (decimal_point = sensor_types[s_type].decimal_point[1])
          ),
          altitude: arrayToDecimal(
            payload.slice(i + 6, i + 9),
            (is_signed = sensor_types[s_type].signed),
            (decimal_point = sensor_types[s_type].decimal_point[2])
          )
        };
        break;
    }

    let s = {
      type: s_name,
      value: s_value,
    };

    if (s_id) {
      s.id = s_id;
    } else {
      s.id = 'sensor ' + s_no;
    }

    if (s_unit) {
      s.unit = s_unit;
    }

    sensors.push(s);
    i += s_size;
  }

  return sensors;
}

return decode(payload);
1 Like

I find a bug in the original code. The humidity was divided by 10, instead of divide by 2, so is necesary to add this line of code:

  if (s_type == 104) { s_value = s_value * 5}; // Humidity Data Resolution is 0,5% per bit, not 0,1% per bit

This code segment :

  case 134: // Gyrometer
    s_value = arrayToDecimal(
      payload.slice(i, i + s_size),
      (is_signed = sensor_types[s_type].signed),
      (decimal_point = sensor_types[s_type].decimal_point)
    );
    break;

need to be replaced with this other

  case 134: // Gyrometer
    s_value = arrayToDecimal(
      payload.slice(i, i + s_size),
      (is_signed = sensor_types[s_type].signed),
      (decimal_point = sensor_types[s_type].decimal_point)
    );
  if (s_type == 104) { s_value = s_value * 5}; // Humidity Data Resolution is 0,5% per bit, not 0,1% per bit
    break;

With this modification, the value for humidity is decoded correctly.

2 Likes

Thanks for reporting back! I noticed you also notified the original author; great. That’s just another reason to always add attribution to the original author in the comments of one’s derived code, so other users can follow the chain to report errors too… (Hint!)

Aside: rather than adding a single exception, one could also replace the decimals handling. Like maybe make arrayToDecimal use some resolution parameter, rather than decimal_point:

function arrayToDecimal(stream, is_signed = false, resolution = 1) {
  ...
  value *= resolution;
  // Ensure the JavaScript IEEE 754 floating point calculations don't
  // return too many decimals, like for 111 * 0.1 = 11.100000000000001.
  // Unary plus-operator to cast string result of toFixed to number.
  return +value.toFixed(10);
}

…along with fixing all definitions to match the table from the comments:

  var sensor_types = {
    0: {size: 1, name: "Digital Input", signed: false, resolution: 1},
    1: {size: 1, name: "Digital Output", signed: false, resolution: 1},
    2: {size: 2, name: "Analog Input", signed: true, resolution: 0.01}
    ...
    104: {size: 1, name: "humidity", signed: false, resolution: 0.5, unit: '%'},
    ...

…and the invocations, such as:

  while (i < payload.length) {
    ...
    switch (s_type) {
      ...
      case 115: // Barometer
      case 134: // Gyrometer
        s_value = arrayToDecimal(
          payload.slice(i, i + s_size),
          sensor_types[s_type].signed,
          sensor_types[s_type].resolution
        );
        break;
  ...

As for the additional use of toFixed, beware that JavaScript’s 0.1 cannot be represented in an exact JavaScript IEEE 754 floating point, yielding funny results like 0.1 * 111= 11.100000000000001, while 111 / 10 = 11.1 and 0.01 * 111 = 1.11 are just fine.

Finally: lacking named parameters in JavaScript, using is_signed in (is_signed = sensor_types[s_type].signed) really just creates a temporary variable that is not used anywhere in the code, so can be omitted.

Enjoy!