Laird RS1xx payload format

After the RS1xx send a payload informing the firmware update, the payload began to be sent. But the information in the payload isn´t making sense:

{
  "AlarmMsgCount": 31237,
  "BatteryCapacity": "80-100%",
  "BcklogMsgCount": 41988,
  "MsgType": "Send Temp RH Data Notification",
  "Options": "Inform Server to send UT to sensor in the next downlink transmission",
  "humidity": 166.44,
  "temperature": 64.45
}

I mean, the temperature isn´t 64.45 C, the correct is 25.37. The humidity isn´t 166.44, the correct is 66.1

What’s the payload? How’s that payload being decoded? Did you validate that against some documentation? And isn’t that a different question than the topic at hand?

I will send the payload when I be back to home. The documentation is the data Laird RS1xx data dictionary: https://assets.lairdtech.com/home/brandworld/files/RS1xx%20LoRa%20Protocol_v2_2.pdf

This is the decoder that I developed:

function Decoder(Payload) {
	var Param = {
		MsgType:         0      ,
		optVlues:        0      ,
		BatteryCapacity: "none" ,
		BatType:         "none" ,
		humidity:        0      ,
		temperature:     0      ,
		AlarmMsgCount:   0      ,
		BcklogMsgCount:  0      ,
		NumReads:        0      ,
		Tmtstp:          0      ,
		ReadSenPer:      0      ,
		SenAggr:         0      ,
		TmpAlarmEna:     false  ,
		HumAlarmEna:     false  ,
		LED_BLE:         0      ,
		LED_Heartb:      0      ,		
		Year:            0      ,
		Month:           0	    ,
		Day:             0      ,
		VerMaj:		     0      ,
		VerMin:          0	    ,
		PartNum:         0		    
		};
	Param.MsgType         = Payload[0];
	Param.optVlues        = Payload[1];
	  switch (Param.optVlues) {
		  case 0:
			   Param.Options = "Server response is LoRa ack.";
			   break;
		  case 1:
			   Param.Options = "Inform Server to send UT to sensor in the next downlink transmission";
			  break;
		  case 2:
			  Param.Options = "Sensor configuration error.";
			  break;
		  case 4:
			  Param.Options = "Sensor has alarm condition.";
			  break;
		  default:
			  Param.Options = "This value is not supported";
			  break;
	  }
	  switch(Param.MsgType) {
		  case 1:
		  	  Param.humidity    = ((Payload[3] << 8) + Payload[2])/100;
			  Param.temperature = ((Payload[5] << 8) + Payload[4])/100;
			  switch(Payload[6]){
				  case 0:
					   Param.BatteryCapacity = "0-5%";
					   break;
				  case 1:
					   Param.BatteryCapacity = "5-20%";
					  break;
				  case 2:
					  Param.BatteryCapacity = "20-40%";
					  break;
				  case 3:
					  Param.BatteryCapacity = "40-60%";
					  break;
				  case 4:
					  Param.BatteryCapacity = "60-80%";
					  break;
				  case 5:
					  Param.BatteryCapacity = "80-100%";
					  break;
				  default:
					  Param.BatteryCapacity = "This value is not supported";
			  }
			  Param.AlarmMsgCount  = (Payload[8] << 8) + Payload[7];
			  Param.BcklogMsgCount = (Payload[10] << 8) + Payload[9];
			  return {
				  MsgType: "Send Temp RH Data Notification",
				  Options: Param.Options,
				  humidity: Param.humidity,
				  temperature: Param.temperature,
				  BatteryCapacity: Param.BatteryCapacity,
				  AlarmMsgCount: Param.AlarmMsgCount,
				  BcklogMsgCount: Param.BcklogMsgCount
			  };
		  case 2:
		      Param.AlarmMsgCount  = Payload[2];
			  Param.BcklogMsgCount = (Payload[4] << 8) + Payload[3];
			  switch(Payload[5]){
				  case 0:
					  Param.BatteryCapacity = "0-5%";
					  break;
				  case 1:
					  Param.BatteryCapacity = "5-20%";
					  break;
				  case 2:
					  Param.BatteryCapacity = "20-40%";
					  break;
				  case 3:
					  Param.BatteryCapacity = "40-60%";
					  break;
				  case 4:
					  Param.BatteryCapacity = "60-80%";
					  break;
				  case 5:
					  Param.BatteryCapacity = "80-100%";
					  break;
				  default:
					  Param.BatteryCapacity = "This value is not supported";
			  }
			  Param.NumReads    = Payload[6];
			  Param.Tmtstp      =(Payload[10] << 24)+(Payload[9] << 16) +(Payload[8] << 8) + Payload[7];
			  Param.humidity    =((Payload[12] << 8) + Payload[11])/100;
			  Param.temperature =((Payload[14] << 8) + Payload[13])/100;
			  return {
				  MsgType: "Send Temp RH Aggregated Data",
				  Options: Param.optVlues,
				  AlarmMsgCount: Param.AlarmMsgCount,
				  BcklogMsgCount: Param.BcklogMsgCount,
				  BatteryCapacity: Param.BatteryCapacity,
				  NumReads: Param.NumReads,
				  Tmtstp: Param.Tmtstp,
				  humidity: Param.humidity,
				  temperature: Param.temperature
			  };               
		  case 3:
		      Param.Tmtstp      =(Payload[5] << 24)+(Payload[4] << 16) +(Payload[3] << 8) + Payload[2];
			  Param.humidity    =((Payload[7] << 8) + Payload[6])/100;
			  Param.temperature =((Payload[9] << 8) + Payload[8])/100;
			  return {
				  MsgType: "Send Backlog Message Notification",
				  Options: Param.Options,
				  Tmtstp: Param.Tmtstp,
				  humidity: Param.humidity,
				  temperature: Param.temperature
			  };
		  case 4:
		      Param.Tmtstp      =(Payload[6] << 24)+(Payload[5] << 16) +(Payload[4] << 8) + Payload[3];
			  Param.humidity    =((Payload[8] << 8) + Payload[7])/100;
			  Param.temperature =((Payload[10] << 8) + Payload[9])/100;
			  return {
				  MsgType: "Send Backlog Messages Notification",
				  Options: Param.Options,
				  Tmtstp: Param.Tmtstp,
				  humidity: Param.humidity,
				  temperature: Param.temperature
			  };
		  case 5:
			  switch(Payload[2]) {
				  case 1:
					  Param.BatType = "Zinc-Manganese Dioxide (Alkaline).";
					  break;
				  case 2:
					  Param.BatType = "Lithium/Iron Disulfide (Primary Lithium).";
					  break;
				  default:
					  Param.BatType = "Unknow battery type";
			  }
			  Param.ReadSenPer  =(Payload[4] << 8) + Payload[3];
			  Param.SenAggr     = Payload[5];
			  Param.TmpAlarmEna = Payload[6];
			  Param.HumAlarmEna = Payload[7];
			  return {
				  MsgType: "Send Sensor Config Simple Notification",
				  Options: Param.Options,
				  BatType: Param.BatType,
				  ReadSenPer: Param.ReadSenPer,
				  SenAggr: Param.SenAggr,  
				  TmpAlarmEna: Param.TmpAlarmEna,
				  HumAlarmEna: Param.HumAlarmEna
			  };
		  case 6:
			  switch(Payload[2]) {
				  case 1:
					  Param.BatType  = "Zinc-Manganese Dioxide (Alkaline).";
					  break;
				  case 2:
					  Param.BatType  = "Lithium/Iron Disulfide (Primary Lithium).";
					  break;
				  default:
					  Param.BatType  = "Unknow battery type";
			  }
			  Param.ReadSenPer   =(Payload[4] << 8) + Payload[3];
			  Param.SenAggr      = Payload[5];
			  Param.TmpAlarmEna  = Payload[6];
			  Param.HumAlarmEna  = Payload[7];
			  Param.temperature  = ((Payload[9] << 8) + Payload[8])/100;
			  Param.humidity     = ((Payload[11] << 8) + Payload[10])/100;
			  Param.LED_BLE      = (Payload[13] << 8) + Payload[12];
			  Param.LED_Heartb   = (Payload[15] << 8) + Payload[14];			
			  return {
				  MsgType: "Send Sensor Config Advanced Notification",
				  Options: Param.Options,
				  BatType: Param.BatType,
				  ReadSenPer: Param.ReadSenPer,
				  SenAggr: Param.SenAggr,  
				  TmpAlarmEna: Param.TmpAlarmEna,
				  HumAlarmEna: Param.HumAlarmEna,
				  humidity: Param.humidity,
				  temperature: Param.temperature,
				  LED_BLE: Param.LED_BLE,
				  LED_Heartb: Param.LED_Heartb
			  };
		  case 7:
		      Param.Year    = Payload[2];
			  Param.Month   = Payload[3];
			  Param.Day     = Payload[4];
			  Param.VerMaj  = Payload[5];
			  Param.VerMin  = Payload[6];
			  Param.PartNum = Payload[7];
			  return {
				  MsgType: "Send FW Version Notification",
				  Options: Param.Options,
				  Year: Param.Year, 
				  Month: 	Param.Month,
				  Day: Param.Day,
				  VerMaj:	Param.VerMaj,
				  VerMin:	Param.VerMin,
				  PartNum: Param.PartNum
			  };
	  }
  }

Without seeing examples of the payload and their expected values, we cannot help you debug the Decoder.

However, the documentation you linked to shows:

image

To me, “Decimal value of temperature measurement” and “Integer value of temperature measurement” suggest you might need something like:

// Unary plus-operator to convert string result to a number
Param.humidity = +(Payload[3] + "." + Payload[2]);

And with:

the temperature isn´t 64.45 C, the correct is 25.37. The humidity isn´t 166.44, the correct is 66.1

Decimal 6445 being 0x192D hexadecimal, the above would get you 0x19 + "." + 0x2D = 25.45 instead. And decimal 16644 being 0x4104, this translates to 65.4, assuming it should not read 65.04 instead? If it should, you’d need:

Param.humidity = Payload[3] + Payload[2] / 100;

(Quite a suggested accuracy in those numbers; I guess the true measurements are not that exact.)

First of all, thank you very much by your effort to help me :slight_smile:

This is one payload example: 01013047281905057A04A4

Here the decoder result:

 {
  "AlarmMsgCount": 31237,
  "BatteryCapacity": "80-100%",
  "BcklogMsgCount": 41988,
  "MsgType": "Send Temp RH Data Notification",
  "Options": "Inform Server to send UT to sensor in the next downlink transmission",
  "humidity": 182.24,
  "temperature": 64.4
}

Let me review my decoder logic

And what exact values are you expecting for that payload?

I copied the following from the Laird Node-RED example:

msg.humidity = bArray[idx++] / 100 + bArray[idx++];
msg.temp = convertTempUnits(bArray[idx++], bArray[idx++]);

…along with:

// Convert the two byte sensor data format to a signed number
// 
// tInt: the integer portion of the temp  
// tDec: the fractional portion of the temp  
// 
function convertTempUnits( tDec ,  tInt ){
    // the integer portion is a signed two compliment value convert it to a signed number
    if( tInt > 127 ){
        tInt -= 256
    }
    // the fractional portion of the number is unsigned and represents the part of the temp
    // after the base 10 decimal point
    let t = tInt + (tDec * Math.sign(tInt) / 100);

    // if the global flag for using degreesFahenheit is set convert the units
    if(global.get("degreesFahrenheit")){
        t = t * 1.8 + 32; 
    }
    return t; 
}

As the “otto” JavaScript parser as used by TTN does not support all of the above, you can modify this for use in a decoder function:

// Convert the two byte sensor data format to a signed number
// 
// tInt: the integer portion of the temperature  
// tDec: the fractional portion of the temperature  
function convertTempUnits(tDec, tInt) {
  // the integer portion is a signed two's complement value; convert it
  // to a signed number
  if (tInt > 127) {
    tInt -= 256;
  }
  // the fractional portion of the number is unsigned and represents the 
  // part of the temperature after the base 10 decimal point
  return tInt + (tDec * (tInt < 0 ? -1 : 1) / 100);
}

…and use it with:

// Humidity cannot be negative
var humidity = bytes[2]/100 + bytes[3];
var temperature = convertTempUnits(bytes[4], bytes[5]);

Of course, for use in your function Decoder(Payload), use Payload[2] and the like, rather than bytes[2] which matches the more commonly used function signature of function Decoder(bytes, port).

Also, you might want to validate this code:

The above, <<-shifting byte 4, suggests LSB. However, the order of the bytes in the Laird code suggests MSB:

var backLogMsb  = bArray[idx++];
var backLogLsb  = bArray[idx++];

Finally, just for future reference, see the full JavaScript source for the Laird Node-RED decoder below. I also noticed a funny conversion for the battery in their code:

// if the message contains battery capacity forward it 
// otherwise do nothing
if(typeof msg.batCapacity !== 'undefined')
{
    msg.batCapacity *= 20;
    return msg;
}
Node-RED "payload decoder" node source code (click to expand)
// the raw payload is a base64 encoded version of the binary data sent by the RS1xx sensor
if( msg.payload.payload_raw ){
    
var bArray = Buffer.from(msg.payload.payload_raw, 'base64');
var idx = 0;

// These two values are common across all packets 
var msgType = bArray[idx++];
var options  = bArray[idx++];

  switch(msgType) {
    case 1:  // handle the single temp and humidity reading
        msg.humidity = bArray[idx++] / 100 + bArray[idx++];
        msg.temp     = convertTempUnits(bArray[idx++], bArray[idx++]);
        msg.batCapacity = bArray[idx++];
        break;
    case 2:  // handle the aggregate temp and humidity reading 
        handleMsgType_2( msg );
        break;
    case 5:  // handle the simpleConfig message 
        var batteryType = bArray[idx++];
        var readPeriod = bArray[idx++] * 256 + bArray[idx++];
        var sensorAggregate = bArray[idx++]
        // the aggregate packet does not contain the read interval so we need to get 
        // it from the simple config and save it to the flow context for use later
        flow.set('readPeriod', readPeriod * 1000);
        flow.set('sensorAggregate', sensorAggregate)
        break;
    default:
        break;
  }
  
 return msg;
}

// Convert the two byte sensor data format to a signed number
// 
// tInt: the integer portion of the temp  
// tDec: the fractional portion of the temp  
// 
function convertTempUnits( tDec ,  tInt ){

    // the integer portion is a signed two compliment value convert it to a signed number
    if( tInt > 127 ){
        tInt -= 256
    }

    // the fractional portion of the number is unsigned and represents the part of the temp 
    // after the base 10 decimal point
    let t = tInt + (tDec * Math.sign(tInt) / 100);
    
    // if the global flag for using degreesFahenheit is set convert the units
    if(global.get("degreesFahrenheit")){
        t = t * 1.8 + 32; 
    }
    return t; 
}

// message type two is an aggreegate temperature and humidity reading 
// the timestamp is the time of the last temp / rh reading in the array
function handleMsgType_2( msg ) {

  var alarms      = bArray[idx++];
  var backLogMsb  = bArray[idx++];
  var backLogLsb  = bArray[idx++];
  var batCapacity = bArray[idx++];
  var valueCount  = bArray[idx++];
  var timestamp   = (bArray[idx++] * 256 * 256 * 256)  + (bArray[idx++] * 256 * 256) + (bArray[idx++] * 256) + bArray[idx++];

  var queuedData = [];    // array to hold the data while we are waiting to plot it 

  var temp; 
  var humidity;
  var reading = {};

  // queue the aggregate message temp/rh values so that we can display them over time rather than
  // bursting them all at once 
  for (var ii = 0; ii <  valueCount; ii++){
    let   h    = bArray[idx++] / 100 + bArray[idx++];
    let t = convertTempUnits(bArray[idx++] , bArray[idx++] );
    reading = { humidity: h, temp: t}
    queuedData.push(reading);

  } 
  
  // loop counter for queued messages 
  ii = 0;

  // timer id from the previous setInterval( ) if any
  timerId = flow.get('timerId');
  
  // if we have a current setInterval, free it
  if(timerId){
     clearInterval(timerId);
  }

  // this is the setInterval( ) callback function 
  function  sendData() {
    var msg = {payload:queuedData[ii]};
    if(ii < valueCount)
    {
      msg.humidity = queuedData[ii].humidity;
      msg.temp = queuedData[ii].temp;
      msg.batCapacity = batCapacity;
      node.send(msg);
      ii++;
    }
 }

 // send once as soon as we get data 
 sendData();

 // if we don't have a specified read interval use something 
 var interval = flow.get('readPeriod') || 5000;

 // set up the rest of the data to be sent at the read interval 
 timerId = setInterval(sendData, interval);

 // save the timer id so we can cancel this scheduled repeat when we get more data 
 flow.set('timerId', timerId); 

}

To develop my Decoder algorithm, I based on Node-RED Decoder algorithm. May be I did some mistake, but the values of temperature and humidity that I am seeing in the App Smartphone with bluetooth connection with the RS1xx, it is different of Decoder Output:

  • List item

Payload [5] = 19 conversion => 25 C
Payload[4] = 28 conversion => 0.40C
The result is 25.40C

Sorry, I don’t understand what you’re trying to say. The math in your example is correct; hexadecimal 0x19 equals decimal 25, and 0x28 is decimal 40. But I’ve no idea why you’re posting that. To debug we need the payload and the expected result; please don’t post only half of the details each time!

That said, surely the following is wrong, according to the documentation you linked to, and according to the very different Node-RED code I found:

This basically is the same as:

Param.humidity = (Payload[3] * 256 + Payload[2])/100; 
Param.temperature = (Payload[5] * 256 + Payload[4])/100;

I am trying to explain it is, if the payload is correct, the problem is in the decoder algorithm :slight_smile:

So I took whatever I found earlier in this thread and I cleaned it up. Result below.

// Convert the two byte sensor data format to a signed number
// 
// tInt: the integer portion of the temperature  
// tDec: the fractional portion of the temperature  
function convertTempUnits(tDec, tInt) {
  // the integer portion is a signed two's complement value; convert it
  // to a signed number
  if (tInt > 127) {
    tInt -= 256;
  }
  // the fractional portion of the number is unsigned and represents the 
  // part of the temperature after the base 10 decimal point
  return tInt + (tDec * (tInt < 0 ? -1 : 1) / 100);
}

function asTempAndRh(decoded, bytes) {
  
  decoded.Humidity = bytes[2]/100.0 + bytes[3];
  decoded.Temperature = convertTempUnits(bytes[4], bytes[5]);

  switch( bytes[6] ) {
    case 0:
      decoded.BatteryCapacity = "0-5%";
      break;
    case 1:
      decoded.BatteryCapacity = "5-20%";
      break;
    case 2:
      decoded.BatteryCapacity = "20-40%";
      break;
    case 3:
      decoded.BatteryCapacity = "40-60%";
      break;
    case 4:
      decoded.BatteryCapacity = "60-80%";
      break;
    case 5:
      decoded.BatteryCapacity = "80-100%";
      break;
    default:
      decoded.BatteryCapacity = "This value is not supported";
  }

  decoded.AlarmMsgCount  = ((bytes[7]<<8)>>>0) + bytes[8];
  decoded.BacklogMsgCount = ((bytes[9]<<8)>>>0) + bytes[10];

}

function asTempAndRhAggregate(decoded, bytes) {

  decoded.AlarmMsgCount  = bytes[2];
  decoded.BacklogMsgCount = ((bytes[3]<<8)>>>0) + bytes[4];

  switch( bytes[4] ) {
    case 0:
      decoded.BatteryCapacity = "0-5%";
      break;
    case 1:
      decoded.BatteryCapacity = "5-20%";
      break;
    case 2:
      decoded.BatteryCapacity = "20-40%";
      break;
    case 3:
      decoded.BatteryCapacity = "40-60%";
      break;
    case 4:
      decoded.BatteryCapacity = "60-80%";
      break;
    case 5:
      decoded.BatteryCapacity = "80-100%";
      break;
    default:
      decoded.BatteryCapacity = "This value is not supported";
  }

  var numberOfReadings = bytes[5];

  // 2015-01-01T00:00:00+00:00 = 1420070400
  
  var timestamp = ((bytes[6]<<24)>>>0) + ((bytes[7]<<16)>>>0) + ((bytes[8]<<8)>>>0) + bytes[9];
  timestamp = timestamp + 1420070400; // 1 Jan 2015 to 1 Jan 1970
  //decoded.Time = new Date(timestamp*1000);
  decoded.timestamp = timestamp;

  decoded.readings = [];
  for(var i=0; i<numberOfReadings; i++) {
    var offset = 10 + (i*4);
    var sample = {};

    if(offset+1>bytes.length-1) continue;
    sample['Humidity'] = bytes[offset]/100.0 + bytes[offset+1];
    if(offset+3>bytes.length-1) continue;
    sample['Temperature'] = convertTempUnits(bytes[offset+2], bytes[offset+3]);

    decoded.readings.push(sample);
  }

}

function asBacklogTempAndRh(decoded, bytes) {
  var timestamp = ((bytes[2]<<24)>>>0) + ((bytes[3]<<16)>>>0) + ((bytes[4]<<8)>>>0) + bytes[5];
  timestamp = timestamp + 1420070400; // 1 Jan 2015 to 1 Jan 1970
  //decoded.Time = new Date(timestamp*1000);
  decoded.timestamp = timestamp;

  decoded.Humidity = bytes[6]/100.0 + bytes[7];
  decoded.Temperature = convertTempUnits(bytes[8], bytes[9]);

}

function asBacklogTempAndRhAggregate(decoded, bytes) {
  var numberOfReadings = bytes[2];

  // 2015-01-01T00:00:00+00:00 = 1420070400
  
  decoded.readings = [];
  for(var i=0; i<numberOfReadings; i++) {
    var offset = 3 + (i*8);
    var sample = {};

    if(offset+3>bytes.length-1) continue;
    var timestamp = ((bytes[offset]<<24)>>>0) + ((bytes[offset+1]<<16)>>>0) + ((bytes[offset+2]<<8)>>>0) + bytes[offset+3];
    timestamp = timestamp + 1420070400; // 1 Jan 2015 to 1 Jan 1970
    //decoded.Time = new Date(timestamp*1000);
    sample['timestamp'] = timestamp;

    if(offset+5>bytes.length-1) continue;
    sample['Humidity'] = bytes[offset+4]/100.0 + bytes[offset+5];
    if(offset+7>bytes.length-1) continue;
    sample['Temperature'] = convertTempUnits(bytes[offset+6], bytes[offset+7]);

    decoded.readings.push(sample);
  }
}

function asSensorConfigSimple(decoded, bytes) {
  switch(bytes[2]) {
    case 1:
      decoded.BatteryType = "Zinc-Manganese Dioxide (Alkaline).";
      break;
    case 2:
      decoded.BatteryType = "Lithium/Iron Disulfide (Primary Lithium).";
      break;
    default:
      decoded.BatteryType = "Unknown battery type";
  }
  decoded.ReadSensorPeriod = ((bytes[3] << 8)>>>0) + bytes[4];
  decoded.SensorAggregate = bytes[5];
  decoded.TempAlarmEnabled = (bytes[6]==1);
  decoded.HumidityAlarmEnabled = (bytes[7]==1);
}

function asSensorConfigAdvanced(decoded, bytes) {
  switch(bytes[2]) {
    case 1:
      decoded.BatteryType  = "Zinc-Manganese Dioxide (Alkaline).";
      break;
    case 2:
      decoded.BatteryType  = "Lithium/Iron Disulfide (Primary Lithium).";
      break;
    default:
      decoded.BatteryType  = "Unknown battery type";
  }
  decoded.ReadSensorPeriod = ((bytes[3]<<8)>>>0) + bytes[4];
  decoded.SensorAggregate = bytes[5];
  decoded.TempAlarmsEnabled = (bytes[6]==1);
  decoded.HumidityAlarmsEnabled = (bytes[7]==1);
  decoded.TempAlarmLimitLow = bytes[8];
  decoded.TempAlarmLimitHigh = bytes[9];
  decoded.HumidityAlarmLimitLow = bytes[10];
  decoded.HumidityAlarmLimitHigh = bytes[11];
  decoded.LED_BLE = ((bytes[12]<<8)>>>0) + bytes[13];
  decoded.LED_Heartbeat = ((bytes[14]<<8)>>>0) + bytes[15];
}

function asFwVersion(decoded, bytes) {
  decoded.VersionYear  = bytes[2];
  decoded.VersionMonth = bytes[3];
  decoded.VersionDay   = bytes[4];
  decoded.VersionMajor = bytes[5];
  decoded.VersionMinor = bytes[6];
  decoded.PartNumber   = ((bytes[7]<<24)>>>0) + ((bytes[8]<<16)>>>0) + ((bytes[9]<<8)>>>0) + bytes[10];
}

function Decoder(bytes, port) {
  // Decode an uplink message from a buffer
  // (array) of bytes to an object of fields.
  var decoded = {};

  decoded.MsgType = bytes[0];
  options = bytes[1];

  decoded.Options = "";
  if(options & 0x01) {
    decoded.Options += "Server response is LoRa ack. ";
  }
  if(options & 0x02) {
    decoded.Options += "Inform Server to send UT to sensor in the next downlink transmission. ";
  }
  if(options & 0x04) {
    decoded.Options += "Sensor configuration error. ";
  }
  if(options & 0x08) {
    decoded.Options += "Sensor has alarm condition. ";
  }

  decoded.Options = decoded.Options.trim();
  if(decoded.Options === "") {
    delete decoded.Options;
  }


  switch(decoded.MsgType) {
    case 0x01:
      asTempAndRh(decoded, bytes);
      break;
    case 0x2:
      asTempAndRhAggregate(decoded, bytes);
      break;
    case 0x03:
      asBacklogTempAndRh(decoded, bytes);
      break;
    case 0x04:
      asBacklogTempAndRhAggregate(decoded, bytes);
      break;
    case 0x05:
      asSensorConfigSimple(decoded, bytes);
      break;
    case 0x06:
      asSensorConfigAdvanced(decoded, bytes);
      break;
    case 0x07:
      asFwVersion(decoded, bytes);
      break;
  }

  return decoded;

}