Payload Decoder SCD30

I’m complete newbie. Im trying to decode the follwing payload to send to ubidots but I struggling to get the decorder to display correct values.

Physical Payload 400F9C0E0080000001519CBEF331810BC367D2EBB196EB64F541762CE7E4578016A7FBA79AE2044395BD7BD861DB261BF40809

Function Im using is

function Decoder(bytes, port) {
var co2 = ((bytes[0] << 16) | bytes[3] );
var temp = ((bytes[6] << 16) | bytes[9] );
var hum = ((bytes[12] << 16) | bytes[15] );

return {
co2 : (co2),
Temperature : (temp),
humidity : (hum),

Correct values should be co2:1294.27, Temp: 21.358, Hum: 49.30

however i’m getting

“Temperature”: 81,
“co2”: 4194318,
“humidity”: 15925259

How did you code the payload? you have just to revert the operations, but if we do not know how it is coded, we cannot tell how to decode it.
However, looking at the decoder, you are skipping bytes (what about byte[1], byte[2], byte[7]…?)

1 Like

Read measurement Values .pdf (78.9 KB)

Im using c++ to code. I have attached a snippet of the read measurement. I can send you entire code if this is not enough.

Without the data structure of scdSTR we cannot help you.


I have attached the scdSTR.

struct.pdf (73.7 KB)

Please post code as text, not as images. See also How do I format my forum post?

And above all, please respond to:

(And as an aside: that 51 bytes payload is huge for just 3 measurements! Are you sure that’s yours?)

1 Like

Im sending data from mdot to gateway through Auto-ota-example. Example of what i’m sending is below. I don’t know how to just send sensor data.

> { "tmst": 788061212,"chan": 1, "rfch": 0, "freq": 868.3, "stat": 1, "modu": "LORA", "datr": "SF12BW125", "codr": "4/5", "lsnr": 10, "rssi": -42, "opts": "", "size": 42, "fcnt": 3, "cls": 0, "port": 1, "mhdr": "400f9c0e00800300", "appeui": "70-b3-d5-7e-d0-01-9b-ec", "deveui": "00-80-00-00-00-01-05-bc", "ack": false, "adr": true, "gweui": "00-80-00-00-a0-00-1b-90", "seqn": 3, "time": "2019-04-05T14:36:12.343377Z", "payload": "CO2: 984.052 Temp: 22.137 Hum: 52.77 ", "eui": "00-80-00-00-00-01-05-bc", "_msgid": "1942c296.e6bd3d" }

and this is the event info.

    > {
    >   "gw_id": "eui-00800000a0001b90",
    >   "payload": "QA+cDgCBAgACAUj124WtLkxzW5QF7YLZVQZe8SIUi78WbgQkcgc/QzvTASpeQCrvkZaGO+yK+wg=",
    >   "f_cnt": 2,
    >   "lora": {
    >     "spreading_factor": 12,
    >     "bandwidth": 125,
    >     "air_time": 2629632000
    >   },
    >   "coding_rate": "4/5",
    >   "timestamp": "2019-04-05T14:37:19.807Z",
    >   "rssi": -61,
    >   "snr": 10,
    >   "dev_addr": "000E9C0F",
    >   "frequency": 867900000
    > }

Is it possible to just send the sensor data?

Metadata are in the header or added by the network server, you have to check only what is inside the payload. Look at the code of the example you use to understand what it is sending and how.

EDIT: I looked at your struct. You are sending 3 copies of the sensor data in 3 different data types; why? And with a precision beyond the needs , e.g. 16 bits to send a binary flag. Remember that LoRa(WAN) has duty cycle limits, and sending such huge amount of redundant data will reduce the number of times you can send. Anyway, by just reverting what you do to code, you will be able to reconstruct your data.

Which example is that? We’ll need to tell the creator that it’s a bad example:

It seems to me you’re sending text. (Though this is 37 characters, not matching the 42 bytes encrypted application payload that can be extracted from the event info you showed.)

Don’t send text; please read Working with Bytes in the official documentation until you grasp the difference between the number 22.137 and the text “22.137” which is like saying two, two, period, one, three, seven, and which in your example even has some words like t, e, m, p, colon, space as well. If you understand the difference but can’t figure out how to send numbers rather than text: please show us the full code.

I understand what you saying about sending numbers rather than text, however with my very limited experience I don’t know how to change this. I am using example from

My full code is:

#include "dot_util.h"
#include "RadioEvent.h"
#include "scd30.h"
#include "mbed.h"
static uint8_t network_appeui[] = { 0x70, 0xB3, 0xD5, 0x7E, 0xD0, 0x01, 0x9B, 0xEC };
static uint8_t network_appkey[] = { 0xFE, 0x4D, 0x72, 0x02, 0x2A, 0x81, 0x59, 0x38, 0x09, 0xE4, 0xE5, 0xA0, 0x3A, 0x02, 0x8E, 0xEF };

static uint8_t frequency_sub_band = 1;
static lora::NetworkType network_type = lora::PUBLIC_LORAWAN;
static uint8_t join_delay = 5;
static uint8_t ack = 0;
static bool adr = true;
static bool deep_sleep = true;
// deepsleep consumes slightly less current than sleep
// in sleep mode, IO state is maintained, RAM is retained, and application will resume after waking up
// in deepsleep mode, IOs float, RAM is lost, and application will start from beginning after waking up
// if deep_sleep == true, device will enter deepsleep mode

mDot* dot = NULL;
lora::ChannelPlan* plan = NULL;

#define SDA0                    PC_9 //PTE25
#define SCL0                    PA_8 //PTE24
#define LEDON                   0
#define LEDOFF                  1

DigitalOut rled(D1);
DigitalOut gled(D0);                 //also PTE26 (J2-1)
DigitalOut bled(D3);

RawSerial pc(USBTX, USBRX);

scd30 scd(SDA0, SCL0, 100000);                 //// set bus speed to 400 kHz

void initSplash()

// initial the scd30

void initSCD30()
    pc.printf("Initializing SCD30...\r\n");
    // scd.softReset();
    pc.printf(" - SCD30 s/n: ");
    for(int i = 0; i < sizeof(; i++) pc.printf("%c",[i]);



int main()
    int32_t res;
    char data_str[64];                  // Intermediate conversion variable
    // Custom event handler for automatically displaying RX data
    RadioEvent events;

    rled = gled = bled = LEDOFF;//Turnoff LED

    uint8_t result = 0;
    result = scd.getSerialNumber();
//pc.printf("scd30 error %02x\r\n", result);
//pc.printf("scd30 error %02x\r\n", scd30::SCDnoERROR);

    void initSCD30();

//pc.printf("scd30 error %02x\r\n", result);
//pc.printf("scd30 error %02x\r\n", scd30::SCDnoERROR);

    if(result != scd30::SCDnoERROR) {
        //pc.printf("scd30 error %02x\r\n", result);

    int count = 0;

    // pc.printf("Ready...\r\n");

#if defined(TARGET_XDOT_L151CC)


    plan = new lora::ChannelPlan_US915();
#elif CHANNEL_PLAN == CP_AU915
    plan = new lora::ChannelPlan_AU915();
#elif CHANNEL_PLAN == CP_EU868
    plan = new lora::ChannelPlan_EU868();
#elif CHANNEL_PLAN == CP_KR920
    plan = new lora::ChannelPlan_KR920();
#elif CHANNEL_PLAN == CP_AS923
    plan = new lora::ChannelPlan_AS923();
    plan = new lora::ChannelPlan_AS923_Japan();
#elif CHANNEL_PLAN == CP_IN865
    plan = new lora::ChannelPlan_IN865();

    dot = mDot::getInstance(plan);

    // attach the custom events handler

    if (!dot->getStandbyFlag()) {
        logInfo("mbed-os library version: %d", MBED_LIBRARY_VERSION);

        // start from a well-known state
        logInfo("defaulting Dot configuration");

        // make sure library logging is turned on

        // update configuration if necessary
        // in AUTO_OTA mode the session is automatically saved, so saveNetworkSession and restoreNetworkSession are not needed
        if (dot->getJoinMode() != mDot::AUTO_OTA) {
            logInfo("changing network join mode to AUTO_OTA");
            if (dot->setJoinMode(mDot::AUTO_OTA) != mDot::MDOT_OK) {
                logError("failed to set network join mode to AUTO_OTA");
        // in OTA and AUTO_OTA join modes, the credentials can be passed to the library as a name and passphrase or an ID and KEY
        // only one method or the other should be used!
        // network ID = crc64(network name)
        // network KEY = cmac(network passphrase)
        //update_ota_config_name_phrase(network_appeui, network_appkey, frequency_sub_band, network_type, ack);
        update_ota_config_id_key(network_appeui, network_appkey, frequency_sub_band, network_type, ack);

        // configure network link checks
        // network link checks are a good alternative to requiring the gateway to ACK every packet and should allow a single gateway to handle more Dots
        // check the link every count packets
        // declare the Dot disconnected after threshold failed link checks
        // for count = 3 and threshold = 5, the Dot will ask for a link check response every 5 packets and will consider the connection lost if it fails to receive 3 responses in a row
        update_network_link_check_config(3, 5);

        // enable or disable Adaptive Data Rate

        // Configure the join delay

        // save changes to configuration
        logInfo("saving configuration");
        if (!dot->saveConfig()) {
            logError("failed to save configuration");

        // display configuration

    while (true) {
        gled = !gled;
        //  wait_ms(250);
        uint16_t redy = scd.scdSTR.ready;

        //   pc.printf("Ready: %5d scd30::SCDisReady %5d\r\n", redy, scd30::SCDisReady);

        // uint8_t crcc=scd.readMeasurement();

        //pc.printf("Crcc: %5d scd30::SCDnoERROR %5d\r\n", crcc, scd30::SCDnoERROR);

        // pc.printf("%5d  -> CO2: %9.3f   Temp: %7.3f   Hum: %5.2f\r\n",
        //    count, scd.scdSTR.co2f, scd.scdSTR.tempf, scd.scdSTR.humf);

        if(redy == scd30::SCDisReady) {
            uint8_t crcc = scd.readMeasurement();
            //   pc.printf("First if statement has been executed: %c\r\n", redy);
            if(crcc != scd30::SCDnoERROR) {
                pc.printf("ERROR: %d\r\n", crcc);
            } else {
                pc.printf("%5d  -> CO2: %9.3f   Temp: %7.3f   Hum: %5.2f\r\n",
                          count, scd.scdSTR.co2f, scd.scdSTR.tempf, scd.scdSTR.humf);
            if((int)scd.scdSTR.co2f > 10000) {
                //     pc.printf("C02 greater than 10000");
            // pc.printf("C02 (out of the loop)greater than 10000");

            std::vector<uint8_t> data;
            // join network if not joined
            if (!dot->getNetworkJoinStatus()) {

// PushSensor value into data array
            sprintf(data_str, "CO2: %9.3f Temp: %7.3f   Hum: %5.2f ",scd.scdSTR.co2f, scd.scdSTR.tempf, scd.scdSTR.humf);
            for (int i = 0; i<strlen(data_str); i++) {

            // send the data to the gateway
            if ((res = dot->send(data)) != mDot::MDOT_OK) {
                logError("failed to send", res, mDot::getReturnCodeString(res).c_str());
            } else {
                logInfo("successfully sent data to gateway: %s", data_str);

            // in the 868 (EU) frequency band, we need to wait until another channel is available before transmitting again
            osDelay(std::max((uint32_t)5000, (uint32_t)dot->getNextTxMs()));


            // ONLY ONE of the three functions below should be uncommented depending on the desired wakeup method

The example I see on that URL uses:

uint16_t light;
std::vector<uint8_t> tx_data;
// get the latest light sample and send it to the gateway
light = lux.getData();
tx_data.push_back((light >> 8) & 0xFF);
tx_data.push_back(light & 0xFF);
logInfo("light: %lu [0x%04X]", light, light);

The above sends 2 bytes for an unsigned 16 bits integer.

For CO2, I see this part of your struct:

uint16_t co2m; /**< High order 16 bit word of CO2 */
uint16_t co2l; /**< Low order 16 bit word of CO2 */ 
uint32_t co2i; /**< 32 bit int of CO2 */ 
float co2f; /**< float of CO2 concentration */ 

This is populated using the following in your measurement code: | 1, i2cBuff, 18, false);

uint16_t stat = (i2cBuff[0] << 8) | i2cBuff[1];
scdSTR.co2m = stat;
uint8_t dat = scd30::checkCrc2b(stat, i2cBuff[2]);
if(dat == SCDcrcERROR) return SCDcrcERRORv1;

stat = (i2cBuff[3] << 8) | i2cBuff[4];
scdSTR.co2l = stat;
dat = scd30::checkCrc2b(stat, i2cBuff[5]);
if(dat == SCDcrcERROR) return SCDcrcERRORv2; 
scdSTR.co2i = (scdSTR.co2m << 16) | scdSTR.co2l ;
scdSTR.co2f = *(float*)&scdSTR.co2i; 

So, I’d say that only the floats such as co2f hold usable values. And I assume that the CO2 reading is within the range of 0 thru 6553.5, hence fits into a 16 bits unsigned integer when first multiplying by 10 like explained in Working with bytes. For temperature, I’d expect it to be within -327.68 thru 327.67, so fits in a 16 bits signed integer when first multiplying by 100 to keep two decimals (which probably is far beyond the sensor’s accuracy). Finally, humidity probably does not need any decimal, so ranges from 0 through 100, fitting in a single unsigned byte of 0 thru 255.

With the above, I’d assume the following might do to send the unsigned CO2 value in 2 bytes:

// Unsigned; multiply float by 10 to keep one decimal, 0 thru 6553.5
uint16_t co2 = scd.scdSTR.co2f * 10;
// MSB, most significant byte first
tx_data.push_back((co2 >> 8) & 0xFF);
tx_data.push_back(co2 & 0xFF);

Even more, with std::vector<uint8_t> tx_data the vector can only hold bytes (uint8_t being unsigned 8 bits), so I’d assume that pushing something larger (say: 16 bits) will simply ignore whatever does not fit. So, I’d guess this would even suffice:

tx_data.push_back(co2 >> 8);

Temperature should allow for negative values:

// Signed, multiply float by 100 to keep two decimals, -327.68 thru 327.67
int16_t temp = scd.scdSTR.tempf * 100;
tx_data.push_back(temp >> 8);

// Unsigned, ignore any decimals, 0 thru 255:
uint8_t hum = scd.scdSTR.humf;

// send the data to the gateway
if ((res = dot->send(data)) != mDot::MDOT_OK) {
  logError("failed to send", res, mDot::getReturnCodeString(res).c_str());
} else {
  logInfo("successfully sent data to gateway: %s", data_str);

So, the above sends two bytes for CO2, two more for temperature, and one for humidity.

In TTN Console, this would then need:

function Decoder(bytes, port) {
  // MSB, unsigned
  var co2 = (bytes[0]<<8 | bytes[1]) * 10;
  // Sign-extend to 32 bits to support negative values, by shifting 24
  // bits to the left (16 too far), followed by shifting 16 bits to the
  // right, to effectively shift 8 bits:
  var temp = (bytes[2]<<24>>16 | bytes[3]) * 100;
  var hum = bytes[4];

  return {
    co2: co2,
    temperature: temp,
    humidity: hum

All untested…


This topic was automatically closed 60 days after the last reply. New replies are no longer allowed.