ESP8266 With DSM501a Dust Sensor Custom Sensor Component

I have DSM501a Sensor try to use it with ESP8266 according to this article and datasheet
Unfortunately, I can’t get stable or reasonable reading
What do I miss?

sensor:
  - platform: pulse_counter
    name:  Line 4 (PM2.5)
    pin: GPIO12
    unit_of_measurement: "ug/m3"
    update_interval: 1s
    count_mode: 
      rising_edge : DISABLE
      falling_edge: INCREMENT 

  - platform: pulse_counter
    name:  Line 2 (PM1.0)
    pin: GPIO14
    unit_of_measurement: "ug/m3"
    update_interval: 1s
    count_mode: 
      rising_edge : DISABLE
      falling_edge: INCREMENT

From a quick look, the article suggests you need to sum the duration of low pulse?

Whereas the pulse counter will just count pulses?

Maybe you need this?

@Mahko_Mahko
thanks for your reply Pulse Width Sensor don’t have an option for a low pulse

" The pulse_width sensor allows you to measure how long a given digital signal is HIGH."
like

    count_mode: 
      rising_edge : DISABLE
      falling_edge: INCREMENT

Low is the inverse of high, right? It should be easy enough to work out the inverse if you have any reliable detection and you know the duration.
What results are you getting when you use it?

Screenshot (13)

more stable reading

I think if you invert your pins then I believe your high and low pulses will swap places. And that might solve that aspect.

pin:
      number: GPIOXX
      inverted: true

My interpretation of the calculation required is that you need to “calculate the % of low time in a period and then multiply it by 100” to get the “low ratio”.

Whereas I think the pulse width sensor will send each width value. So there’s prob more processing to do.

There’s also the duty cycle sensor which might come in handy. But I think it might keep accumulating so you might need to figure out how to reset it.

There’s various filters for moving average type calculations that might be helpful.

Once you’ve got the low ratio I believe you still need to use the numbers in the chart to convert it to the right units. You’d probably use a calibrate linear filter for that.

@Mahko_Mahko Actually I tried many methods but no one was close at less for me
finally, I decided to use Custom Sensor Component I tested This code below it works perfectly without ESPhome

int pin = 14;//DSM501A input D8
unsigned long duration;
unsigned long starttime;
unsigned long endtime;
unsigned long sampletime_ms = 30000;
unsigned long lowpulseoccupancy = 0;
float ratio = 0;
float concentration = 0; 

void setup() {                
  Serial.begin(115200);
  pinMode(pin,INPUT);
  delay(10); 
}
 
 
void loop() {
   
 duration = pulseIn(pin, LOW);
  lowpulseoccupancy += duration;
  endtime = millis();
  if ((endtime-starttime) > sampletime_ms)
  {
    ratio = (lowpulseoccupancy-endtime+starttime + sampletime_ms)/(sampletime_ms*10.0);  // Integer percentage 0=>100
    concentration = 1.1*pow(ratio,3)-3.8*pow(ratio,2)+520*ratio+0.62; // using spec sheet curve mg/m3
    lowpulseoccupancy = 0;
    starttime = millis();

    Serial.print("lowpulseoccupancy:");
    Serial.print(lowpulseoccupancy);
    Serial.print("    ratio:");
    Serial.print(ratio);
    Serial.print("    DSM501A:");
    Serial.println(concentration);

  }

}

After that tried to modify it as Custom Sensor Component as this DSM501A.h

#include "esphome.h"
int pin = 14; //DSM501A input D8
unsigned long duration;
unsigned long starttime;
unsigned long endtime;
unsigned long sampletime_ms = 30000;
unsigned long lowpulseoccupancy = 0;
float ratio = 0;
float concentration = 0; 

class DSM501A : public PollingComponent, public Sensor {
 public:
  DSM501A() : PollingComponent(30000) {}  
  float get_setup_priority() const override { return esphome::setup_priority::HARDWARE_LATE; }
  Sensor *ratio_out = new Sensor();
  Sensor *concentration_out = new Sensor();


  void setup() override {
    pinMode(pin,INPUT);
    delay(10);
  }
  void update() override {
  duration = pulseIn(pin, LOW);
  lowpulseoccupancy += duration;
  endtime = millis();
  if ((endtime-starttime) > sampletime_ms)
  {
    ratio = (lowpulseoccupancy-endtime+starttime + sampletime_ms)/(sampletime_ms*10.0);  // Integer percentage 0=>100
    concentration = 1.1*pow(ratio,3)-3.8*pow(ratio,2)+520*ratio+0.62; // using spec sheet curve mg/m3
    lowpulseoccupancy = 0;
    starttime = millis();

    ratio_out->publish_state(ratio);
    concentration_out->publish_state(concentration);  
  }
};

and

sensor:

  - platform: custom
    lambda: |-
      auto my_sensor = new DSM501A();
      App.register_component(my_sensor);
      return {my_sensor->ratio_out, my_sensor->concentration_out};
    sensors:
      - name: "DSM501A Ratio"
      - name: "DSM501A Concentration"

but I get this error

src/main.cpp:49:6: error: 'void DSM501A::setup()' cannot be overloaded with 'void DSM501A::setup()'
   49 | void setup() {
      |      ^~~~~
In file included from src/main.cpp:46:
src/DSM501A.h:19:8: note: previous declaration 'void DSM501A::setup()'
   19 |   void setup() override {
      |        ^~~~~
/config/A/Config/Device_Base.yaml:78:1: error: expected '}' at end of input
   78 | 
      | ^
In file included from src/main.cpp:46:
src/DSM501A.h:11:56: note: to match this '{'
   11 | class DSM501A : public PollingComponent, public Sensor {
      |                                                        ^
/config/A/Config/Device_Base.yaml:78:1: error: expected unqualified-id at end of input
   78 | 
      | ^
*** [.pioenvs/d1-mini/src/main.cpp.o] Error 1
========================= [FAILED] Took 48.27 seconds =========================
1 Like

I can’t help with cpp it breaks my brain;).

Good luck!

@Mahko_Mahko thank your for your previous help :wink:

1 Like

I’m curious if this works…

sensor:
  - platform: duty_cycle
    pin: GPIO12
    name: Line 4 (PM2.5)
    update_interval: 30s
    filters:
      - lambda: return (1 - x)* 100.0; #invert the percent (1-x) and then multiply by 100.
      - calibrate_linear:
          # Map the real values from the chart as per below.
          - 0.0 -> 0.0
          - 10.0 -> 12.1

not stable
I’m looking for help in Custom Sensor Component because it’s very stable and accurate

1 Like

I believe you’re missing a “};” at the very bottom to close the “class DSM501A” line. Still gives an error in my case, but I dont know how to progress

Looks like the function mentioned:
1.1pow(ratio,3)-3.8pow(ratio,2)+520*ratio+0.62
Is aproximation for another dust sensor PPD42NS from it’s specsheet.

I’d got the concentration curve from original DSM501a specsheet and aproximation I have is:
2.5302 * pow(ratio,2) + 86.1109*ratio - 11.7442

And here is the code with use of duty_cycle:

  - platform: duty_cycle
    pin:    
      number: D6
      mode:
        input: true
        pullup: true
    name: PM2.5    
    id: pm2
    unit_of_measurement: "µg/m3"
    update_interval: 8s
    filters:
      - median:
          window_size: 7
          send_every: 4
          send_first_at: 3        
      - lambda: |-
          const float ratio = 100.0 - x;
          const double res = ( 2.5302 * pow(ratio,2) + 86.1109*ratio - 11.7442 ); 
          return res < 0 ? 0.0 : res;

However duty_cycle works on interrupts, which may not be 100% accurate if some interrupts were missing due to processing on some other stuff (ex. display).
I decided to use median filter to smooth result in case of not accurate reading.

2 Likes

Hello, thank you for clearing up my misconception that the PPD42NS and DSM501a sensors are the same :slight_smile:
I used the code you provided for 2.5. It displays appropriate values.
I applied the same formula for 1.0. Unfortunately, the data is obviously incorrect.
Could you also provide the calculation formula for 1.0?
Thank you.