SDS011 contrib, how to set the working period

Hi everyone,

I'm a newbie with node red. Actually, i'i'm trying to configure my SDS011 sensor node in my rapsberry pi3, you can find it here: https://flows.nodered.org/node/node-red-contrib-sds011

The sensor works fine, I see the data. But it takes data every second, (seems to work in continuos mode). I want to change this situation changing the work period in about 5 minutes.

To do this, i read:

After reading the part 5 of the document, i tried to understand which part of the code is necesary to set the period. Probably, the answer is the function "setWorkingPeriod":

// set working period
// period: 0 for continues
//         n for work 30 sec and sleep n*60-30 secs.
function setWorkingPeriod(period, deviceID=deviceId) {
  period = ('00'+(period).toString(16)).substr(-2);
  let data = '0801'+period+'00000000000000000000'+deviceID.toString(16).padStart(4, 0);
  let crc = ('00'+(calcCrcFromData(new Buffer(data, 'hex'))).toString(16)).substr(-2);
  let buffer = new Buffer('AAB4'+data+crc+'AB', 'hex');
  sendBuffer(buffer);
}

So, i tried to put an Inject item (Payload = text, topic = setWorkingPeriod(5, deviceID)) on my board, but ativating it doesn't works.

any helps? Thanks.

Here the code:

const SerialPort = require('serialport');

let node;
let serial;
let deviceId;

module.exports = function(RED) {
  function sds011Sensor(config) {
    RED.nodes.createNode(this, config);
    node = this;
    deviceId = 0xFFFF;
    serial = new SerialPort(config.port, {
      baudRate: 9600,
      parser: serialParser()
    });
    serial.on('open', function() {
      node.log('Serial port is open');
      node.status({fill: 'green', shape: 'dot', text: 'connected'});
    });
    serial.on('error', function(err) {
      if (err) {
        node.error('Serial port error', err);
      }
      node.status({fill: 'red', shape: 'ring', text: 'error'});
    });
    serial.on('close', function() {
      node.log('Serial port is closed');
      node.status({fill: 'red', shape: 'ring', text: 'disconnected'});
    });
    let parser = serialParser();
    serial.on('data', function(data) {
      parser(data);
    });

    node.on('input', function(msg) {
      let command;
      let parameter;
      if (msg.command === undefined) {
        command = msg.payload;
      } else {
        command = msg.command;
        parameter = msg.parameter;
      }
      node.log('Command: ' + command + ' Parameter: ' + parameter);
      switch (command) {
        case 'setDataReportingMode':
          setDataReportingMode(parameter);
          break;
        case 'setActive':
          setDataReportingMode('active');
          break;
        case 'setQuery':
          setDataReportingMode('query');
          break;
        case 'getDataReportingMode':
          getDataReportingMode();
          break;
        case 'queryData':
          queryData();
          break;
        case 'setDeviceId':
          setDeviceId(parameter);
          break;
        case 'setStatus':
          setStatus(parameter);
          break;
        case 'sleep':
          setStatus('sleep');
          break;
        case 'work':
          setStatus('work');
          break;
        case 'getStatus':
          getStatus();
          break;
        case 'setWorkingPeriod':
          setWorkingPeriod(parameter);
          break;
        case 'setContinuousMode':
          setWorkingPeriod(5);
          break;
        case 'getWorkingPeriod':
          getWorkingPeriod();
          break;
        case 'checkFirmwareVersion':
          checkFirmwareVersion();
          break;
        default:
          node.error('Unknown command: ' + msg.payload);
          break;
      }

      // get the device id of the sensor
      checkFirmwareVersion(deviceId);
    });

    node.on('close', function() {
      serial.close(function(err) {
        if (err) {
          node.error('Serial close error', err);
        }
      });
    });
  }
  RED.nodes.registerType('rpi-sds011', sds011Sensor);
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//#############################################################################################################################//
//#                                                                                                                           #//
//# documentation can be found at https://cdn.sparkfun.com/assets/parts/1/2/2/7/5/Laser_Dust_Sensor_Control_Protocol_V1.3.pdf #//
//#                                                                                                                           #//
//#############################################################################################################################//
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

function sendBuffer(buffer) {
  serial.write(buffer, 'hex');
}

// set data reporting mode
// mode: 'active' / 'query'
// deviceID: 0xFFFF for all peripherals
function setDataReportingMode(mode, deviceID=deviceId) {
  let data = '0201'+(mode==='active'?'00':'01')+'00000000000000000000'+deviceID.toString(16).padStart(4, 0);
  let crc = ('00'+(calcCrcFromData(new Buffer(data, 'hex'))).toString(16)).substr(-2);
  let buffer = new Buffer('AAB4'+data+crc+'AB', 'hex');
  sendBuffer(buffer);
}

// get data reporting mode
// deviceID: 0xFFFF for all peripherals
function getDataReportingMode(deviceID=deviceId) {
  let data = '02000000000000000000000000'+deviceID.toString(16).padStart(4, 0);
  let crc = ('00'+(calcCrcFromData(new Buffer(data, 'hex'))).toString(16)).substr(-2);
  let buffer = new Buffer('AAB4'+data+crc+'AB', 'hex');
  sendBuffer(buffer);
}

// query data command
function queryData(deviceID=deviceId) {
  let data = '04000000000000000000000000'+deviceID.toString(16).padStart(4, 0);
  let crc = ('00'+(calcCrcFromData(new Buffer(data, 'hex'))).toString(16)).substr(-2);
  let buffer = new Buffer('AAB4'+data+crc+'AB', 'hex');
  sendBuffer(buffer);
}

// set device ID
function setDeviceId(newDeviceID, deviceID=deviceId) {
  let data = '0500000000000000000000'+newDeviceID.toString(16).padStart(4, 0)+deviceID.toString(16).padStart(4, 0);
  let crc = ('00'+(calcCrcFromData(new Buffer(data, 'hex'))).toString(16)).substr(-2);
  let buffer = new Buffer('AAB4'+data+crc+'AB', 'hex');
  sendBuffer(buffer);
}

// set sleep and work
// mode: 'sleep' / 'work'
function setStatus(mode, deviceID=deviceId) {
  let data = '0601'+(mode==='sleep'?'00':'01')+'00000000000000000000'+deviceID.toString(16).padStart(4, 0);
  let crc = ('00'+(calcCrcFromData(new Buffer(data, 'hex'))).toString(16)).substr(-2);
  let buffer = new Buffer('AAB4'+data+crc+'AB', 'hex');
  sendBuffer(buffer);
}

// get sleep and work
function getStatus(deviceID=deviceId) {
  let data = '06000000000000000000000000'+deviceID.toString(16).padStart(4, 0);
  let crc = ('00'+(calcCrcFromData(new Buffer(data, 'hex'))).toString(16)).substr(-2);
  let buffer = new Buffer('AAB4'+data+crc+'AB', 'hex');
  sendBuffer(buffer);
}

// set working period
// period: 0 for continues
//         n for work 30 sec and sleep n*60-30 secs.
function setWorkingPeriod(period, deviceID=deviceId) {
  period = ('00'+(period).toString(16)).substr(-2);
  let data = '0801'+period+'00000000000000000000'+deviceID.toString(16).padStart(4, 0);
  let crc = ('00'+(calcCrcFromData(new Buffer(data, 'hex'))).toString(16)).substr(-2);
  let buffer = new Buffer('AAB4'+data+crc+'AB', 'hex');
  sendBuffer(buffer);
}

// get working period
function getWorkingPeriod(deviceID=deviceId) {
  let data = '08000000000000000000000000'+deviceID.toString(16).padStart(4, 0);
  let crc = ('00'+(calcCrcFromData(new Buffer(data, 'hex'))).toString(16)).substr(-2);
  let buffer = new Buffer('AAB4'+data+crc+'AB', 'hex');
  sendBuffer(buffer);
}

// check formware version
function checkFirmwareVersion(deviceID=deviceId) {
  let data = '07000000000000000000000000'+deviceID.toString(16).padStart(4, 0);
  let crc = ('00'+(calcCrcFromData(new Buffer(data, 'hex'))).toString(16)).substr(-2);
  let buffer = new Buffer('AAB4'+data+crc+'AB', 'hex');
  sendBuffer(buffer);
}

function getNextBeginning(buffer) {
  let start = buffer.indexOf('AA', 'hex');
  if (start === -1) {
    return new Buffer(0);
  }
  return buffer.slice(start);
}

function serialParser() {
  let serialData = new Buffer(0);
  return function(buffer) {
    serialData = Buffer.concat([serialData, buffer]);

    // find message beginning
    if (serialData[0] != 0xAA) {
      serialData = getNextBeginning(serialData);
    }
    // check buffer length
    if (serialData.length < 10) {
      node.debug('buffer is not long enough: ' + buffer.length);
      return;
    }
    // check checksum and tail
    if (!checkCrc(serialData) || (serialData[9] != 0xAB)) {
      node.debug('wrong checksum/tail: ' + buffer[9]);
      serialData = serialData.slice(10);
      return;
    }
    parseMessage(serialData);
    serialData = new Buffer(0);
  }
};

// Message format:
//   0 HEADER AA
//   1 COMMAND
//   2 DATA
//   3 DATA
//   4 DATA
//   5 DATA
//   6 DATA
//   7 DATA
//   8 Check-sum
//   9 TAIL AB
function parseMessage(buffer) {
  deviceId = buffer[6] << 8 | buffer[7]
  let ret = {};
  // check command
  switch (buffer[1]) {
    case 0xC0:
      // PM values
      //   2 DATA PM2.5 Low byte
      //   3 DATA PM2.5 High byte
      //   4 DATA PM10 Low byte
      //   5 DATA PM10 High byte
      //   6 DATA Device ID Low byte
      //   7 DATA Device ID High byte

      // Extract PM values
      // PM2.5 (ug/m3) = ((PM2.5 High byte *256) + PM2.5 low byte) / 10
      // PM10 (ug/m3) = ((PM10 high byte*256) + PM10 low byte) / 10// PM2.5:
      let pm2_5 = (buffer[2] | (buffer[3] << 8)) / 10.0;
      let pm10 = (buffer[4] | (buffer[5] << 8)) / 10.0;

      sendPMValues(pm2_5, pm10);
      break;
    case 0xC5:
      switch (buffer[2]) {
        case 2:
          // data reporting mode
          //   2 DATA 2
          //   3 DATA 0: query the current mode
          //          1: set reporting mode
          //   4 DATA 0: report active mode
          //          1: report query mode
          //   5 DATA 0
          //   6 DATA Device ID Low byte
          //   7 DATA Device ID High byte

          if (buffer[4] === 0) {
            sendInfo('Sensor is in active mode');
          } else {
            sendInfo('Sensor is in query mode');
          }
          break;
        case 5:
          // set device ID
          //   2 DATA 5
          //   3 DATA 0
          //   4 DATA 0
          //   5 DATA 0
          //   6 DATA New Device ID Low byte
          //   7 DATA New Device ID High byte

          sendInfo('Sensor has a new Device ID: 0x' + buffer[6].toString(16) + buffer[7].toString(16));
          break;
        case 6:
          // set sleep and work
          //   2 DATA 6
          //   3 DATA 0: query the current mode
          //          1: set mode
          //   4 DATA 0: sleep
          //          1: work
          //   5 DATA 0
          //   6 DATA Device ID Low byte
          //   7 DATA Device ID High byte

          if (buffer[4] === 0) {
            sendInfo('Sensor is in sleep mode');
          } else {
            sendInfo('Sensor is in work mode');
          }
          break;
        case 7:
          // check firmware version
          //   2 DATA 7
          //   3 DATA Firmware version year
          //   4 DATA Firmware version month
          //   5 DATA Firmware version day
          //   6 DATA Device ID Low byte
          //   7 DATA Device ID High byte

          sendInfo('Firmware: ' + buffer[5] + '.' + buffer[4] + '.' + buffer[3]);
          break;
        case 8:
          // set working period
          //   2 DATA 8
          //   3 DATA 0: query the current mode
          //          1: set mode
          //   4 DATA 0: continues(default)
          //          1-30minute: work 30 secs and sleep n*60-30 secs
          //   5 DATA 0
          //   6 DATA Device ID Low byte
          //   7 DATA Device ID High byte

          if (buffer[4] === 0) {
            sendInfo('Sensor is in continuous mode');
          } else {
            sendInfo('Sensor works 30 seconds and sleeps for ' + (buffer[4]-1).toString() + ' minutes and 30 seconds.');
          }
          break;
        default:
          break;
      }
      break;
    default:
      sendInfo('received a unknown command: ' + buffer[1].toString(16));
      return;
  }
  return ret;
}

function calcCrcFromData(data) {
  let crc = data.reduce(function(prev, curr) {
    return prev + curr;
  });
  crc &= 0xFF;
  return crc;
}

function calcCrcFromBuffer(buffer) {
  let crc = calcCrcFromData(buffer.slice(2, 8));
  return crc;
}

function checkCrc(buffer) {
  let calcCrc = calcCrcFromBuffer(buffer);
  return calcCrc === buffer[8];
}

function sendPMValues(pm2_5, pm10) {
  let time = new Date();
  node.send([
    {payload: pm2_5, title: 'PM2.5 value', topic: 'PM2_5', description: 'PM2.5 value in ug/m3', time: time},
    {payload: pm10, title: 'PM10 value', topic: 'PM10', description: 'PM10 value in ug/m3', time: time},
    null
  ]);
}

function sendInfo(msg) {
  let time = new Date();
  node.log(msg);
  node.send([ null, null, {payload: msg, time: time, deviceId: deviceId} ]);
}

The readme for that mode says it is no longer maintained and to use this fork instead https://github.com/jannikbecher/node-red-contrib-sds011

You may find that one already works.

Thanks for your answer.

Does it mean that I have to use the package.json included in taht link? Or is something deeper?

Actually this one looks better - as someone has started fixing things....

optiprime/ node-red-contrib-sds011

to install it you can go into your user directory (usually ~/.node-red) and do

npm i  optiprime/node-red-contrib-sds011

probably best talk directly to the author of that node if you need extra support.

Thanks. Before install this one, should I uninstall the actual node that i have in my directory?

always a good idea :slight_smile:

Ok, i tried, but if i inject the command, it returns "Unknown command":

Here's how I build the injection: image

Here's the results. I'm curious about that "error" message at the bottom of the node.

The node info shows that the command need to be in the payload - not the topic.

Right :slight_smile:
I've tried with a string, but it doesn't work.

So, I tried with JSON inputs. I've made a few tests:

{
    "command:": "setWorkingPeriod(5)",
}
{
    "command:": "setWorkingPeriod(parameter)",
    "parameter:": 5
}
{
    "command:": "setWorkingPeriod()",
    "parameter:": 5
}

Here's an example.

The output is always the same, Unknown command: [object Object].

try contacting the most recent author direct - I have know knowledge of this node other than trying to point you in the right direction.

I'm trying... but he doesn't leave a contact in his github profile :frowning:

Not helpful..... but looking at the code - hhttps://github.com/optiprime/node-red-contrib-sds011/blob/master/sds011-node/sds011-node.js#L106

you can see that msg.payload should be an object with a command and parameter properties so
msg.payload = { command:"setWorkingPeriod", parameter:5 } ought to do it.

and look at that code for the other parameters, etc.

Thanks, after some attempts, finally i got it :slight_smile:

Next step: taking the output object and write it inside an excel file...
Can you suggest some general guidelines to reach this purpose?

Many thanks for your precious help :slight_smile:

you can use the csv node and provide it a template of column names and it will produce strings that you can feed to a file out node. but you may need to use the change node to move things around a bit first...

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