Cayenne LPP format: analog data wrong

I’m trying to add analog data from gas sensors using the LPP format

Data I’m sending (serial debug output):

CO2 407.00 TVOC 1.00
MQ135 41 MQ009 74

Added as:

  lpp.addAnalogInput(3, ccsCO2);
  lpp.addAnalogInput(4, ccsTVOC);
  lpp.addAnalogInput(5, rawValueMQ135);
  lpp.addAnalogInput(6, rawValueMQ009);

On TTN console it’s showing as:

  "analog_in_3": -248.36,
  "analog_in_4": 1,
  "analog_in_5": 41,
  "analog_in_6": 74,

So it seems the larger value is somehow converted wrongly. Where do I look further for debugging this?

The library’s code shows that value 407.00 is sent as a 16 bits signed integer, using int16_t val = value * 100, hence as 40,700. That’s too large to fit in a 16 bits signed integer which ranges from -32,768 through 32,767.

In hexadecimal, 40,700 is 0x9EFC which when interpreted as a signed integer is indeed -24,836, which divided by 100 indeed yields -248.36.

It’s quite weird LPP does not provide some generic methods for larger values. Some options:

  • You could (ab)use addTemperature which uses int16_t val = celsius * 10, hence sends 4,070 which fits just fine. Same goes for addBarometricPressure.

  • Or, as you know CO2 values are always positive, you could use addLuminosity which expects an unsigned 16 bits integer, which ranges from 0 through 65,535. If you need the two decimals then you’d have to multiply the float by 100 yourself before adding it to LPP, and divide by 100 after receiving it.

  • Or, just forget about LPP and dive into Working with Bytes and payload functions in TTN Console.

In JavaScript, which needs 4 bytes for proper binary math, sign-extension needs to be applied to get the 4 bytes 0xFFFF9EFC.

1 Like

Thanks, I can see now it’s just the library way of encoding the payload.

The actual LPP definition looks normal, 2 bytes signed

I’ll play around defining my own addAnalog.

I was confused by the AnalogInput conversion but after seeing the post from @arjanvanb I suspect that the Cayenne coders weren’t thinking 10 bit arduino analog inputs when they created the function. So a possible workaround is to save the analogRead value as a float, then divide by 100. Therefore the 10 bit range of (0 to 1023) is converted to (0 to 10.23) which is perfectly fine to work with.

// Prepare upstream data transmission at the next possible time.
    Serial.print(F("Reading sensor..."));
    float x = analogRead(A0); // cast to float but don't divide the integer by 100 here or the number will be rounded off
    x = x / 100; // divide by 100 
    Serial.println(x, 2);
    lpp.addAnalogInput(1, x);
    LMIC_setTxData2(1, lpp.getBuffer(), lpp.getSize(), 0);

In this case the analogRead function read 451 and 507

In my opinion the problem is that addAnalogInput accepts floats, but in the implementation they are binary-converted in signed int, divided by 100 and sent. So if from my ESP32 i send the following values:
// Send our data
int numdati=8;
float f[numdati];
for (int i=0;i<numdati;i++)
Serial.printf(“Invio in LoRaWAN su port %i Valore: %f\n”,i,f[i]);

in my application server I’m getting:

  • 1:327
  • 2:327.66
  • 3:1.96
  • 4:-327.36
  • 5:-327.66
  • 6:327.67
  • 7:-327
  • 8:327.36

In other words, AddAnalogRead can only send values between -327,67 and +327,67 with 2 decimals.