Payload Decoders

Payload Decoders in OS2IoT is used to decode the payloads sent from devices. These are often raw bytes to minimize payload size, and not JSON or some other more ‘human readable’ language.

To convert these raw bytes to JSON which will be sent to the data-target, the users of OS2IoT can write payload decoders.

The payload decoders are written in Javascript and executed in a sandbox (https://github.com/patriksimek/vm2) for security and isolation reasons.

The backend expects that the following function prototype to be defined in the payload decoder:

function decode(payload, metadata) {
   // ...
}

The payload parameter is a javascript object containing the payload as received by OS2IoT. The metadata parameter is a javascript object containing the IoTDevice details saved in OS2IoT.

It must return an object, this object will be serialized using JSON.stringify() before it is sent to the data-target.

The user interface provides a testing environment for payload decoder, which can take in data.

Examples of Payload Decoders

Sensit 3.0

Source: https://stackoverflow.com/questions/52534844/decoding-sensit-3-payload-data

// Based on: https://stackoverflow.com/questions/52534844/decoding-sensit-3-payload-data
function decodeSensit(payload) {
  var battery,
    mode,
    humidity,
    temperature,
    light,
    door,
    vibration,
    magnet,
    alert,
    eventCount,
    firmwareVersion,
    parsedData = [],
    obj = {};

  // Byte #0
  var byte = parseInt(payload.slice(0, 2), 16).toString(2);
  while (byte.length < 8) byte = "0" + byte;
  battery = parseInt(byte.slice(0, 5), 2) * 0.05 + 2.7;
  if (battery >= 4.15) {
    battery = 100;
  } else if (battery >= 3.8 && battery < 4.15) {
    battery = Math.round((battery - 3.275) * 114);
  } else if (battery >= 3.6 && battery < 3.8) {
    battery = Math.round((battery - 3.56) * 250);
  } else if (battery > 3 && battery < 3.6) {
    battery = Math.round((battery - 3) * 16);
  }
  battery = battery < 0 ? 0 : battery;

  // Byte #1
  var byte = parseInt(payload.slice(2, 4), 16).toString(2);
  while (byte.length < 8) byte = "0" + byte;

  mode = parseInt(byte.slice(0, 5), 2);
  switch (mode) {
    case 0:
      mode = "Standby";
      break;
    case 1:
      mode = "Temperature & Humidity";
      break;
    case 2:
      mode = "Light";
      break;
    case 3:
      mode = "Door";
      break;
    case 4:
      mode = "Vibration";
      break;
    case 5:
      mode = "Magnet";
      break;
    default:
      mode = "Unknown mode {" + mode + "}";
  }

  alert = Boolean(parseInt(byte.slice(5, 6), 2));

  // Standby mode
  if (mode === "Standby") {
    // Byte #2
    var byte = parseInt(payload.slice(4, 6), 16).toString(2);
    while (byte.length < 8) byte = "0" + byte;
    firmwareVersion = byte;
    // Byte #3
    var byte = parseInt(payload.slice(6, 8), 16).toString(2);
    while (byte.length < 8) byte = "0" + byte;
    firmwareVersion += byte;

    firmwareVersion =
      parseInt(firmwareVersion.slice(0, 4), 2) +
      "." +
      parseInt(firmwareVersion.slice(4, 10), 2) +
      "." +
      parseInt(firmwareVersion.slice(10, 16), 2);
  }

  // Temperature & Humidity
  if (mode === "Temperature & Humidity") {
    // Byte #1
    var byte = parseInt(payload.slice(2, 4), 16).toString(2);
    while (byte.length < 8) byte = "0" + byte;
    temperature = byte.slice(6, 8);
    // Byte #2
    var byte = parseInt(payload.slice(4, 6), 16).toString(2);
    while (byte.length < 8) byte = "0" + byte;
    temperature += byte;

    temperature = ((parseInt(temperature, 2) - 200) / 8).toFixed(2);
    // Byte #3
    humidity = parseInt(payload.slice(6, 8), 16) * 0.5;
  }

  // Light
  if (mode === "Light") {
    // Byte #2
    var byte = parseInt(payload.slice(4, 6), 16).toString(2);
    while (byte.length < 8) byte = "0" + byte;
    light = byte;
    // Byte #3
    var byte = parseInt(payload.slice(6, 8), 16).toString(2);
    while (byte.length < 8) byte = "0" + byte;
    light += byte;

    light = (parseInt(light, 2) / 96).toFixed(2);
  }

  // Door
  if (mode === "Door") {
    // Byte #1
    var byte = parseInt(payload.slice(2, 4), 16).toString(2);
    while (byte.length < 8) byte = "0" + byte;
    door = parseInt(byte.slice(6, 8), 2);
    switch (door) {
      case 0:
        door = "The calibration of the Door mode has not been done";
        break;
      case 1:
        door = "Unused value";
        break;
      case 2:
        door = "Door is closed";
        break;
      case 3:
        door = "Door is open";
        break;
      default:
        door = "Unknown door status {" + door + "}";
    }
  }

  // Vibration
  if (mode === "Vibration") {
    // Byte #1
    var byte = parseInt(payload.slice(2, 4), 16).toString(2);
    while (byte.length < 8) byte = "0" + byte;
    vibration = parseInt(byte.slice(6, 8), 2);
    switch (vibration) {
      case 0:
        vibration = "No vibration detected";
        break;
      case 1:
        vibration = "A vibration is detected";
        break;
      case 2:
        vibration = "Unused value";
        break;
      case 3:
        vibration = "Unused value";
        break;
      default:
        vibration = "Unknown vibration status {" + vibration + "}";
    }
  }

  // Magnet
  if (mode === "Magnet") {
    // Byte #1
    var byte = parseInt(payload.slice(2, 4), 16).toString(2);
    while (byte.length < 8) byte = "0" + byte;
    magnet = parseInt(byte.slice(6, 8), 2);
    switch (magnet) {
      case 0:
        magnet = "No magnet detected";
        break;
      case 1:
        magnet = "A magnet is detected";
        break;
      case 2:
        magnet = "Unused value";
        break;
      case 3:
        magnet = "Unused value";
        break;
      default:
        magnet = "Unknown magnet status {" + magnet + "}";
    }
  }

  // Event count (Door - Vibration - Magnet)
  if (mode === "Door" || mode === "Vibration" || mode === "Magnet") {
    // Byte #2
    var byte = parseInt(payload.slice(4, 6), 16).toString(2);
    while (byte.length < 8) byte = "0" + byte;
    eventCount = byte;
    // Byte #3
    var byte = parseInt(payload.slice(6, 8), 16).toString(2);
    while (byte.length < 8) byte = "0" + byte;
    eventCount += byte;
    eventCount = parseInt(eventCount, 2);
  }

  // Store objects in parsedData array
  obj = {};
  obj.key = "mode";
  obj.value = mode;
  obj.type = "string";
  obj.unit = "";
  parsedData.push(obj);
  obj = {};
  obj.key = "firmwareVersion";
  obj.value = firmwareVersion;
  obj.type = "string";
  obj.unit = "";
  parsedData.push(obj);
  obj = {};
  obj.key = "temperature";
  obj.value = temperature;
  obj.type = "number";
  obj.unit = "°C";
  parsedData.push(obj);
  obj = {};
  obj.key = "humidity";
  obj.value = humidity;
  obj.type = "number";
  obj.unit = "%";
  parsedData.push(obj);
  obj = {};
  obj.key = "light";
  obj.value = light;
  obj.type = "number";
  obj.unit = "lux";
  parsedData.push(obj);
  obj = {};
  obj.key = "alert";
  obj.value = alert;
  obj.type = "boolean";
  obj.unit = "";
  parsedData.push(obj);
  obj = {};
  obj.key = "door";
  obj.value = door;
  obj.type = "string";
  obj.unit = "";
  parsedData.push(obj);
  obj = {};
  obj.key = "vibration";
  obj.value = vibration;
  obj.type = "string";
  obj.unit = "";
  parsedData.push(obj);
  obj = {};
  obj.key = "magnet";
  obj.value = magnet;
  obj.type = "string";
  obj.unit = "";
  parsedData.push(obj);
  obj = {};
  obj.key = "eventCount";
  obj.value = eventCount;
  obj.type = "number";
  obj.unit = "";
  parsedData.push(obj);
  obj = {};
  obj.key = "battery";
  obj.value = battery;
  obj.type = "number";
  obj.unit = "%";
  parsedData.push(obj);

  console.log(parsedData);
  return parsedData;
}

function decode(payload, metadata) {
  let res = {};
  res.decoded = decodeSensit(payload.data);
  return res;
}

Elsys ERS

const TYPE_TEMP = 0x01; //temp 2 bytes -3276.8°C -->3276.7°C
const TYPE_RH = 0x02; //Humidity 1 byte  0-100%
const TYPE_ACC = 0x03; //acceleration 3 bytes X,Y,Z -128 --> 127 +/-63=1G
const TYPE_LIGHT = 0x04; //Light 2 bytes 0-->65535 Lux
const TYPE_MOTION = 0x05; //No of motion 1 byte  0-255
const TYPE_CO2 = 0x06; //Co2 2 bytes 0-65535 ppm
const TYPE_VDD = 0x07; //VDD 2byte 0-65535mV
const TYPE_ANALOG1 = 0x08; //VDD 2byte 0-65535mV
const TYPE_GPS = 0x09; //3bytes lat 3bytes long binary
const TYPE_PULSE1 = 0x0a; //2bytes relative pulse count
const TYPE_PULSE1_ABS = 0x0b; //4bytes no 0->0xFFFFFFFF
const TYPE_EXT_TEMP1 = 0x0c; //2bytes -3276.5C-->3276.5C
const TYPE_EXT_DIGITAL = 0x0d; //1bytes value 1 or 0
const TYPE_EXT_DISTANCE = 0x0e; //2bytes distance in mm
const TYPE_ACC_MOTION = 0x0f; //1byte number of vibration/motion
const TYPE_IR_TEMP = 0x10; //2bytes internal temp 2bytes external temp -3276.5C-->3276.5C
const TYPE_OCCUPANCY = 0x11; //1byte data
const TYPE_WATERLEAK = 0x12; //1byte data 0-255
const TYPE_GRIDEYE = 0x13; //65byte temperature data 1byte ref+64byte external temp
const TYPE_PRESSURE = 0x14; //4byte pressure data (hPa)
const TYPE_SOUND = 0x15; //2byte sound data (peak/avg)
const TYPE_PULSE2 = 0x16; //2bytes 0-->0xFFFF
const TYPE_PULSE2_ABS = 0x17; //4bytes no 0->0xFFFFFFFF
const TYPE_ANALOG2 = 0x18; //2bytes voltage in mV
const TYPE_EXT_TEMP2 = 0x19; //2bytes -3276.5C-->3276.5C
const TYPE_EXT_DIGITAL2 = 0x1a; // 1bytes value 1 or 0
const TYPE_EXT_ANALOG_UV = 0x1b; // 4 bytes signed int (uV)
const TYPE_DEBUG = 0x3d; // 4bytes debug

function bin16dec(bin) {
  var num = bin & 0xffff;
  if (0x8000 & num) num = -(0x010000 - num);
  return num;
}

function bin8dec(bin) {
  var num = bin & 0xff;
  if (0x80 & num) num = -(0x0100 - num);
  return num;
}

function base64ToBytes(str) {
  return atob(str)
    .split("")
    .map(function (c) {
      return c.charCodeAt(0);
    });
}

function hexToBytes(hex) {
  for (var bytes = [], c = 0; c < hex.length; c += 2)
    bytes.push(parseInt(hex.substr(c, 2), 16));
  return bytes;
}

function DecodeElsysPayload(data) {
  var obj = new Object();
  for (i = 0; i < data.length; i++) {
    console.log(data[i]);
    switch (data[i]) {
      case TYPE_TEMP: //Temperature
        var temp = (data[i + 1] << 8) | data[i + 2];
        temp = bin16dec(temp);
        obj.temperature = temp / 10;
        i += 2;
        break;
      case TYPE_RH: //Humidity
        var rh = data[i + 1];
        obj.humidity = rh;
        i += 1;
        break;
      case TYPE_ACC: //Acceleration
        obj.x = bin8dec(data[i + 1]);
        obj.y = bin8dec(data[i + 2]);
        obj.z = bin8dec(data[i + 3]);
        i += 3;
        break;
      case TYPE_LIGHT: //Light
        obj.light = (data[i + 1] << 8) | data[i + 2];
        i += 2;
        break;
      case TYPE_MOTION: //Motion sensor(PIR)
        obj.motion = data[i + 1];
        i += 1;
        break;
      case TYPE_CO2: //CO2
        obj.co2 = (data[i + 1] << 8) | data[i + 2];
        i += 2;
        break;
      case TYPE_VDD: //Battery level
        obj.vdd = (data[i + 1] << 8) | data[i + 2];
        i += 2;
        break;
      case TYPE_ANALOG1: //Analog input 1
        obj.analog1 = (data[i + 1] << 8) | data[i + 2];
        i += 2;
        break;
      case TYPE_GPS: //gps
        i++;
        obj.lat =
          (data[i + 0] |
            (data[i + 1] << 8) |
            (data[i + 2] << 16) |
            (data[i + 2] & 0x80 ? 0xff << 24 : 0)) /
          10000;
        obj.long =
          (data[i + 3] |
            (data[i + 4] << 8) |
            (data[i + 5] << 16) |
            (data[i + 5] & 0x80 ? 0xff << 24 : 0)) /
          10000;
        i += 5;
        break;
      case TYPE_PULSE1: //Pulse input 1
        obj.pulse1 = (data[i + 1] << 8) | data[i + 2];
        i += 2;
        break;
      case TYPE_PULSE1_ABS: //Pulse input 1 absolute value
        var pulseAbs =
          (data[i + 1] << 24) | (data[i + 2] << 16) | (data[i + 3] << 8) | data[i + 4];
        obj.pulseAbs = pulseAbs;
        i += 4;
        break;
      case TYPE_EXT_TEMP1: //External temp
        var temp = (data[i + 1] << 8) | data[i + 2];
        temp = bin16dec(temp);
        obj.externalTemperature = temp / 10;
        i += 2;
        break;
      case TYPE_EXT_DIGITAL: //Digital input
        obj.digital = data[i + 1];
        i += 1;
        break;
      case TYPE_EXT_DISTANCE: //Distance sensor input
        obj.distance = (data[i + 1] << 8) | data[i + 2];
        i += 2;
        break;
      case TYPE_ACC_MOTION: //Acc motion
        obj.accMotion = data[i + 1];
        i += 1;
        break;
      case TYPE_IR_TEMP: //IR temperature
        var iTemp = (data[i + 1] << 8) | data[i + 2];
        iTemp = bin16dec(iTemp);
        var eTemp = (data[i + 3] << 8) | data[i + 4];
        eTemp = bin16dec(eTemp);
        obj.irInternalTemperature = iTemp / 10;
        obj.irExternalTemperature = eTemp / 10;
        i += 4;
        break;
      case TYPE_OCCUPANCY: //Body occupancy
        obj.occupancy = data[i + 1];
        i += 1;
        break;
      case TYPE_WATERLEAK: //Water leak
        obj.waterleak = data[i + 1];
        i += 1;
        break;
      case TYPE_GRIDEYE: //Grideye data
        var ref = data[i + 1];
        i++;
        obj.grideye = [];
        for (var j = 0; j < 64; j++) {
          obj.grideye[j] = ref + data[1 + i + j] / 10.0;
        }
        i += 64;
        break;
      case TYPE_PRESSURE: //External Pressure
        var temp =
          (data[i + 1] << 24) | (data[i + 2] << 16) | (data[i + 3] << 8) | data[i + 4];
        obj.pressure = temp / 1000;
        i += 4;
        break;
      case TYPE_SOUND: //Sound
        obj.soundPeak = data[i + 1];
        obj.soundAvg = data[i + 2];
        i += 2;
        break;
      case TYPE_PULSE2: //Pulse 2
        obj.pulse2 = (data[i + 1] << 8) | data[i + 2];
        i += 2;
        break;
      case TYPE_PULSE2_ABS: //Pulse input 2 absolute value
        obj.pulseAbs2 =
          (data[i + 1] << 24) | (data[i + 2] << 16) | (data[i + 3] << 8) | data[i + 4];
        i += 4;
        break;
      case TYPE_ANALOG2: //Analog input 2
        obj.analog2 = (data[i + 1] << 8) | data[i + 2];
        i += 2;
        break;
      case TYPE_EXT_TEMP2: //External temp 2
        var temp = (data[i + 1] << 8) | data[i + 2];
        temp = bin16dec(temp);
        if (typeof obj.externalTemperature2 === "number") {
          obj.externalTemperature2 = [obj.externalTemperature2];
        }
        if (typeof obj.externalTemperature2 === "object") {
          obj.externalTemperature2.push(temp / 10);
        } else {
          obj.externalTemperature2 = temp / 10;
        }
        i += 2;
        break;
      case TYPE_EXT_DIGITAL2: //Digital input 2
        obj.digital2 = data[i + 1];
        i += 1;
        break;
      case TYPE_EXT_ANALOG_UV: //Load cell analog uV
        obj.analogUv =
          (data[i + 1] << 24) | (data[i + 2] << 16) | (data[i + 3] << 8) | data[i + 4];
        i += 4;
        break;
      default:
        //somthing is wrong with data
        i = data.length;
        break;
    }
  }
  return obj;
}

function decode(payload, metadata) {
  let res = {};
  res.decoded = DecodeElsysPayload(base64ToBytes(payload.data));
  return res;
}